Honeypot capture
mozi
The structural signature of this sample — DHT transport, a plaintext tag grammar, a node-ID seeded from 888888, config cached at /var/.config and peers at /var/.ipds, an embedded Mirai attack module, and a TR-069 password of acsMozi — identifies it as Mozi. A MIPS big-endian ELF of 135,784 bytes, it yields 19 command-and-control endpoints across 17 distinct hosts, and all 19 carry an agreement verdict from a second decompiler backend. The channel those endpoints serve has no central server. The bot joins the public BitTorrent DHT over udp/6881, bootstraps through eight hardcoded nodes, and takes its orders from bencoded KRPC traffic on that network. Tasking arrives as a plaintext [tag]/[/tag] grammar of 16 commands.
A honeypot sensor network captured the file, and the analysis pipeline loaded it as data and never executed it, reviewed 46 of its 543 functions in depth, contacted or resolved none of the recovered endpoints, and never ran the codec against a real capture.
The recovered profile records 13 stage-2 downloaders for 9 CPU architectures at 0x445060, while the embedded downloader set covers 10 architectures: mips, mipsel, arm, aarch64, x86, x86_64, powerpc, sparc, sh and m68k. mips, mipsel and arm come first in the list, the signature of a campaign aimed at routers, cameras and other embedded devices.
The bot spreads through router exploits — GPON, Netgear, Huawei, TR-064, HNAP, JAWS, Vacron, DrayTek, Realtek and Netlink — and through a telnet credential brute-forcer at 0x421654. It then serves its own binary to what it infects, and victims fetch Mozi.[arch] over HTTP from the infecting bot, which runs the payload server on a UPnP-mapped inbound port.
The sample has SHA-256 4293c1d8574dc87c58360d6bac3daa182f64f7785c9d41da5e0741d2b1817fc7, MD5 59ce0baba11893f90527fc951ac69912 and SHA-1 5857a7dd621c4c3ebb0b5a3bec915d409f70d39f. Execution requires only user privilege; persistence, iptables and the TR-069 sabotage need root.
The sample arrives UPX-packed for linux/mips with p_info.p_filesize and p_blocksize zeroed at file offsets 0x84 and 0x88, which breaks upx -d while leaving the stub able to unpack itself at runtime. The pipeline repaired those two fields from the trailing PackHeader.u_file_size and then unpacked the sample statically. The executable segment measures 7.8156 bits of entropy per byte, consistent with a packed or encrypted payload, and a byte sweep turned up one network indicator the recovered profile does not list: hxxp[://]upx[.]sf[.]net. The ELF section headers are stripped, so symbol names and section-based tooling are unavailable while the loadable segments still run. A candidate embedded ELF sits at offset 0x9c.
The bootstrap list
The four hostnames in the bootstrap list are dht[.]transmissionbt[.]com, router[.]bittorrent[.]com, router[.]utorrent[.]com and bttracker[.]debian[.]org, alongside four bare addresses — 212[.]129[.]33[.]59, 82[.]221[.]103[.]244, 130[.]239[.]18[.]159 and 87[.]98[.]162[.]88 — all on udp/6881, all resolved through the bootstrap routine at 0x4190fc. All eight are legitimate public DHT infrastructure, and the bot abuses the real BitTorrent DHT as its transport. Blocking udp/6881 egress from IoT segments is the control available to defenders.
KRPC framing
The wire format is bencode, with no proprietary framing on top of it. The builder at 0x418ec4 emits d1:ad2:id20:[nodeid]e1:q4:ping1:t%d:[tx]1:y1:qe and hands it to the sendto wrapper at 0x417d20; 0x4186d0 builds find_node with 6:target20:, 2:n4, 2:n6 and 4:wantl%s%se; the report assembler at 0x416a4c also emits get_peers and announce_peer. There is no magic value and no length prefix, because bencode is self-delimiting. The 4-byte 1:v version field is randomised at runtime at 0x4122b4, so the compiled-in default JBls is a weak pivot. The session runs bootstrap-resolve, ping and find_node, announce, then tag parse; the announce loop at 0x41a540 re-announces on a 300-second timer with no jitter and retries after 5 seconds without backoff, read from the static code at confidence 0.75, since the timing was never observed running. The surrounding DHT engine also uses 400, 600, 900 and 1800-second constants for node and bucket refresh.
The [tag] tasking grammar
The C2 path carries no transport encryption. Tasking is plaintext bencode, so anyone recording udp/6881 sees the orders. The profile also records C2 traffic as obfuscated with XOR; the analysis reads that as the in-binary string obfuscation described below.
All 16 tags dispatch from one substring routine at 0x409b58, each confirmed by the second backend at confidence 0.70.
| Tag | Handler | Behaviour |
|---|---|---|
[cnc] | 0x40a7e4 | C2 address list: host:port pairs, default port 8080 |
[atk] | 0x425e70 | DDoS attack selector consumed by the Mirai worker |
[dip] | 0x425e70 | download-IP host:port, default 7001, for stage-2 |
[hj] | 0x41f290 | HTTP traffic-hijack on/off; gates the JS/iframe injector |
[set] | 0x41f290 | override the compiled-in hijack config |
[ud] | 0x4161a4 | update: fetch and swap the bot binary |
[dr] | 0x416678 | download-and-run a file |
[hp] | 0x414928 | node-ID prefix override, default 888888 |
[ver], [count] | 0x4148b4, 0x414808 | version and counter fields |
[cpu], [ss], [sv], [rn] | 0x419384 | host info, status, service, run fields |
[nd], [idp] | 0x419ad8, 0x41a540 | node/DHT and announce fields |
String obfuscation
The obfuscation in the sample covers strings held in the binary. A repeating-XOR key of six bytes, 020304050607 at 0x441708, decodes the hijack-config blob at 0x441718 through the routine at 0x41f0fc; that routine restores the original byte whenever the XOR would produce a NUL, a self-imposed quirk that makes the scheme easy to recognise. The Mirai string table at 0x442088 is masked with an effective single-byte 0x22. The second set of endpoints — hxxp[://]d[.]b12u[.]com/xelf, hxxp[://]js[.]b12u[.]com/go.js, d[.]dns[.]la, 188[.]200[.]144[.]6 and others — decodes from that one blob, and at confidence 0.75 the analysis assigns these endpoints to the traffic-hijack feature rather than to bot tasking. Two further hosts in the Mirai table, cnc[.]changeme[.]com and report[.]changeme[.]com, read as unmodified Mirai source defaults left in the borrowed attack module, not registered operator infrastructure, at confidence 0.35.
Host sabotage
The bot firewalls ports 22, 23, 2323, 7547, 35000, 50023 and 58000 behind itself at 0x422d14, which blocks rival bots and the owner’s remote management through the same rules. At that same address it enumerates /proc and /proc/net/tcp to kill competing bots and to write their telnet victims to /tmp/.ips. It repoints the TR-069 ACS to loopback and sets ConnectionRequestPassword to acsMozi at 0x413844, a near-unique Mozi marker and the recommended fleet-wide check for CPE and routers.
The bot persists through /etc/init.d/S95baby.sh, rc.local, OpenWrt /overlay and JFFS2 scripts at 0x4143b0, renames itself to Runn (also baby), and holds /dev/watchdog open at 0x42d5a0 to keep the device from rebooting. Because it holds the watchdog and firewalls the management ports, an affected device should be treated as fully compromised and reflashed.
These host indicators carry no agreement verdict from the second backend, because the consensus round covered no part of this block. They stand at single-backend confidence between 0.70 and 0.90, and the renderer emitted an empty Sigma detection block.
Anti-analysis
The 46 functions reviewed in depth hold two anti-analysis techniques, the tampered packer header and string obfuscation in two schemes, with the watchdog hold a possible third at confidence 0.65. The bot may hold the watchdog to keep the device alive, to frustrate analysis on real hardware, or both, and a static read cannot tell which. None of the 46 checks for a debugger, a virtual machine or a clock.
Open questions
- The attack worker’s method selector.
0x425e70is 22,292 bytes, the largest function in the binary, and the analysis did not fully enumerate the per-method[atk]sub-dispatch inside it. - What the 384-bit signature check at
0x40e5b4authenticates. The check may be a P-384-shaped verify that gates config or update acceptance, and the on-disk primes do not identify the curve. If it gates config acceptance, an emulated[tag]document may be rejected. - The role of the
POST /cdn-cgi/path at0x4002d0. It is reached only through GOT slot0x49db90and the caller is unresolved, so the path may carry a C2 uplink or may be part of flood traffic. - The info-hash the botnet announces under. It is computed at runtime and appears nowhere in the file as a constant, so
reproduction/c2_emulator.pyanswers direct queries but cannot place itself in the exact swarm the bot searches. - Whether the eight bootstrap nodes still lead anywhere. Nothing in the file shows whether the recovered infrastructure is still live.
The embedded ELF at offset 0x9c
The candidate payload at 0x9c is worth carving and analysing separately. Dynamic analysis is not required to answer the C2 question, though it would settle four of the items above.
- Family
- mozi
- First seen
- August 2026
- Vector
- Runs as a ddos-bot/worm; the entry vector is not established by static analysis
- Format
- 133 KB mips ELF 32-bit MSB executable
- VirusTotal
- 47/75 engines: trojan.mirai/mozi
- Tags
- backdoor · ddos-bot · elf · mips · udp · worm
- Sample
- By request. Email [email protected]
SHA-256
-
4293c1d8574dc87c58360d6bac3daa182f64f7785c9d41da5e0741d2b1817fc7as captured -
f79df4d8a826deb35a68cde363b4bf87ddc158a4f71b2438a2f209dc69849dd8unpacked payload
Analysis performed using an automatic malware analysis pipeline using Binary Ninja