ES EN
Índice

Superior · Capítulo 37

Memory safe

El mismo servidor que repartía Superior sirvió un día otro bot, escrito en Rust y con un lema en la puerta: «memory safe. system optimized.». Lo metí en una jaula sellada, le hice creer que su servidor le contestaba, y me contó todo. Lo más útil no es lo que hace cuando le dan órdenes, sino lo que hace sin ellas.

El 8 de noviembre, entre dos versiones del instalador de Superior, la misma dirección sirvió otra cosa. El servidor se presentaba como Server: rustbox y el guion era más corto:

el /s del 8 de noviembre · fragmentos
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"

Mira si está en Android o en Linux y en qué procesador, baja el binario que toca, lo lanza con la etiqueta rustbox y borra la copia del disco. Y lo explica en el mismo inglés cuidadoso de los guiones de Superior, en este caso pensando en quien lo lanza por ADB.

Este bot no llegó al cebo. Los ocho binarios de /rbx/ los reportó otra persona a URLhaus ese mismo día, y de ahí los saqué de MalwareBazaar; el guion es la copia que guardó urlscan al escanearlo. Lo que hice yo con ellos empieza después. Lo primero que imprime al arrancar es su lema:

la primera línea
rustbox: memory safe. system optimized.
ESPÉCIMEN · /rbx/linux-x86_64

RUSTBOX el segundo bot de la campaña de Superior

◈ VIVO · NO EJECUTAR
sha256
e77b2c68…25898
Tipo
Rust, rustc 1.99.1 del 13 de octubre de 2026; estático con musl en Linux, NDK en Android
Builds
ocho: Linux y Android para arm7, aarch64, x86 y x86_64, de 0,6 a 1 MB
De dónde
reportado por un tercero a URLhaus; recogido de MalwareBazaar
Los antivirus
«trojan.mirai», y «trojan.hajime/mirai» en otras builds: el mismo cajón genérico

01Lo que se ve sin encenderlo

Es un proyecto de Rust ordenado: un espacio de trabajo con dos piezas, el bot y una biblioteca común con la criptografía, y dentro del bot un módulo por tarea (las inundaciones, la persistencia, los informes, la autopropagación y la sesión con el servidor). Se compiló como root, con un compilador del 13 de octubre: RUSTBOX es de mediados de octubre o posterior. No hay nombres de usuario, rutas personales ni nada que señale a una persona.

A diferencia de Superior, no esconde su configuración: el servidor va en claro, coolcams.duckdns.org,77.239.124.167, al TCP 29444. Puesto al lado de Superior, se parece en lo que hace con la máquina y se separa en casi todo lo demás:

Superior y RUSTBOX
                    Superior                         RUSTBOX
lenguaje            C                                Rust
servidor            coolcams.duckdns.org             el mismo, y la IP de respaldo
ADB fijo            12108                            12108
supervisor          /data/local/tmp/.srvc            /data/local/tmp/.srvc
arranque            se re-ejecuta desde memoria      también
disfraz             hilos del núcleo                 kworker/u8:2
perro guardián      sí                               sí, y un vigilante aparte
configuración       cifrada con XOR                  en claro
informes            en claro, a puertos aparte       dentro de la sesión cifrada
su servidor         lo autentica                     no lo autentica
propagación         Telnet con 45 contraseñas        siete exploits de routers y cámaras
pantalla, apps,
robo de ficheros    sí                               no
órdenes             shell por su consola (apagada)   sin shell, sin descargas, sin robo

Esa última línea es importante: según su análisis estático, RUSTBOX no tiene orden de shell remota, ni de descarga, ni de ejecución arbitraria, ni de proxy, ni de robo de ficheros, ni de pantalla. Su servidor le puede pedir ataques, que los pare, cambios en su control interno, que repare su persistencia o que se retire, y comprobar que sigue vivo. Nada más.

Y la fila de su servidor, también. Superior lleva dentro la clave pública de su operador, y su sesión exige la clave privada de este. RUSTBOX no: no comprueba con quién habla. Cualquiera que ocupe el sitio de su servidor puede hablar con él. Eso es lo que me permitió hacerlo, y es lo que haría viable desviar sus conexiones a un sumidero y dejar al operador sin ellas. Cómo, exactamente, no lo cuento.

El mismo operador, con confianza alta: el mismo servidor y el mismo puerto de mando, el mismo puerto de ADB, el mismo supervisor, el mismo inglés y la misma dirección sirviendo un bot u otro según el día. Hay más: entre los puertos que RUSTBOX se niega a tocar está el 29596, el que Superior busca como superior-ui, y el instalador de Superior ya marcaba sus respuestas con RBX_. Que RUSTBOX descienda del código de Superior, en cambio, no lo puedo afirmar: el código es otro, y la criptografía, aunque usa las mismas piezas, está montada de otra manera. Puede ser una reescritura con el mismo diseño o un bot distinto que el mismo operador también maneja.

02En la jaula

La build de Linux para x86_64 la metí en una jaula hecha para esto: una máquina virtual sin tarjeta de red, con una red falsa dentro que contesta a todo, y un monitor dentro del núcleo que ve cada cosa que hace el bot, y protegido de él: cuando quiso mirar sus datos, el sistema se lo negó. La lancé como la lanza su guion, con la etiqueta rustbox, y la dejé cinco minutos. Casi dos millones de eventos, nueve procesos:

cinco minutos de RUSTBOX
arranque       imprime su lema, se copia a memoria y se re-ejecuta desde ahí
               como kworker/u8:2
persistencia   un servicio de systemd activado, init.d, rc.local, cron.d,
               crontab con @reboot y el crontab de root, profile.d, .profile,
               .bashrc, .zshrc y un autoarranque de escritorio
               todo lanza /usr/local/bin/.rustbox boot, casi todo con la marca «# rustbox»
copias         /usr/local/bin/.rustbox, /root/.rustbox, /tmp/.rbx, /var/tmp/.rbx
               y /var/tmp/.rbx_<pid>
vigilancia     un proceso aparte que vigila al principal por un latido en /dev/shm;
               el propio bot abre además el perro guardián del sistema
puertos        ocupa doce puertos que usan otros bots para que no los abran
rivales        revisa los procesos uno a uno, decenas de miles de veces
servidor       conecta a 77.239.124.167:29444, se presenta con un saludo de
               52 bytes que empieza por RBX1, y espera
propagación    811.261 destinos en cinco minutos, a los puertos 80, 8080, 37215 y 52869

Casi todo eso lo apunta en un registro propio, .rbx_ledger: las copias, los ficheros que crea y los que edita. Probablemente le sirve para reparar su persistencia si alguien la toca; seguro que le sirve, como veremos, para desinstalarse.

La propagación no es Telnet, como en Superior: son exploits web contra routers y cámaras. Además de direcciones al azar, prueba la red local: 192.168.0.1, 192.168.1.1 y 192.168.1.64, que es la IP de fábrica de las cámaras Hikvision. Cada exploit, si funciona, le hace a la víctima pedir un guion a coolcams.duckdns[.]org/s?t= con una etiqueta que nombra la vía por la que ha entrado. El mismo embudo de Superior, ahora por exploit.

Según su código, además, bloquea los intentos de reiniciar o apagar la máquina; en la jaula nadie lo intentó, así que eso no se vio. Tampoco probé que su persistencia aguante un reinicio, cosa que con Superior sí se vio en la tele.

El killer, que caza a 36 familias rivales, necesita una corrección mía. El monitor registró unas 74.000 comprobaciones sobre otros procesos y yo, al principio, las leí como intentos de depurarlos. No lo eran: son lecturas de datos del proceso (qué ejecutable corre, desde dónde, si su imagen está borrada o si corre desde memoria). Con eso puntúa a cada uno y, si decide que es un rival, lo mata. En una máquina limpia, no mató a nadie. Lo único que se le negó fue leer los datos del propio monitor.

Con el servidor no hubo más. La red falsa no habla su protocolo, así que pasó casi cuatro minutos sin respuesta. Para ver el resto, había que contestarle.

03Hablarle

Como RUSTBOX no comprueba con quién habla, escribí un servidor de mando de mentira y lo puse dentro de la misma jaula, donde la red falsa contestaba por su servidor. Ese programa no lo publico: el mismo código serviría para controlar bots reales. Y el guion que le di fue a propósito inofensivo: comprobar que estaba vivo, pedirle que reparara su persistencia y pedirle que parara. Ninguna orden de ataque.

Funcionó a la primera. El bot completó el saludo, aceptó a mi servidor como suyo y empezó a contarlo todo: unos 2.200 informes en una sesión. Estos son los que importan:

lo que le cuenta a su servidor
[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   # cada uno, «held»
[SELFREP]vec=…|name=…|ip=…|state=win

El [SELFREP] es el más útil, porque nombra cada vía de propagación con una etiqueta, y en cinco de las siete la etiqueta lleva el número del CVE. Siete vías, ocho CVE:

el arsenal de propagación
etiqueta          aparato                                   CVE
realtek_8361      routers con el SDK de Realtek (UPnP)      CVE-2014-8361
gpon_10561        ONT GPON (Dasan)                          CVE-2018-10561 y CVE-2018-10562
hikvision_36260   cámaras y grabadores Hikvision            CVE-2021-36260
draytek_8515      DrayTek Vigor 2960, 3900 y 300B           CVE-2020-8515
tplink_1389       TP-Link Archer AX21                       CVE-2023-1389
netgear_6077      Netgear DGN1000                           CVE-2024-12847
huawei_hg532      Huawei HG532 (la vía de Satori)           CVE-2017-17215

Todas son inyecciones de órdenes en routers y cámaras, y viejas. Y hay una errata del autor: la etiqueta netgear_6077 remite a otra vulnerabilidad, de otro programa y que exige estar autenticado, pero lo que hace el código es el fallo del setup.cgi del DGN1000, que no pide nada: CVE-2024-12847, divulgado en 2013 y con número desde 2024. Lo confirmé por tres caminos: su rutina en el binario, las bases públicas y la petición que lanzó dentro de la jaula. Las peticiones exactas no las publico.

Un aviso para quien vea su contador. Dentro de la jaula me mandó más de 2.100 state=win en cinco minutos, porque la red falsa contesta a todo. Cualquier honeypot o sandbox que le conteste como si el exploit hubiera funcionado le infla la cuenta (aunque descarta las respuestas que mencionan cowrie, kippo, dionaea, glastopf, conpot, tarpit, canary, shadowserver, honeypot o sinkhole). Sus victorias no son infecciones.

Lo que no le pedí fue atacar. Los ataques, según su código, son cuatro familias (inundaciones UDP, conexiones TCP, paquetes TCP fabricados a mano y un cliente TLS) con variantes: 9 identificadores en su build de Linux para ARM64 y 27 en la de Android, que no son 27 técnicas distintas. Una de las variantes es una inundación HTTP/2 que imita a Chrome 131 con bastante cuidado, y eso deja huella: sec-ch-ua "Chromium";v="131", Accept-Encoding: gzip, deflate, br, zstd, upgrade-insecure-requests y el preámbulo PRI * HTTP/2.0. Hay otra HTTP/1.1, no hay HTTP/3, y lanza como mucho ocho ataques a la vez. Uno de los generadores UDP usa una plantilla con aspecto de SSDP; que sirva para amplificar no está comprobado.

Y queda un cabo suelto. Le envié por la sesión cifrada la orden de reparar su persistencia, pero no contestó con el informe de reparación que tiene en su código, y no tocó nada que no hubiera tocado ya. Puede que en esta build esa orden lleve otro número, o que no diga nada si la persistencia está intacta. No lo sé.

04Sin amo, igual

Lo más útil salió de comparar las dos detonaciones: la de la red muda y la de mi servidor de mentira. Son casi idénticas. Los mismos siete programas lanzados, más o menos los mismos ficheros abiertos, unas 811.000 conexiones de propagación en las dos, las mismas comprobaciones de procesos.

Es decir: la persistencia, la copia en memoria, los vigilantes, el killer, la ocupación de puertos y toda la propagación ocurren al arrancar, sin ninguna orden. Lo que añade un servidor vivo es poco: que el bot le cuente lo que hace y que ataque cuando se lo pidan.

De aquí sale una consecuencia práctica: bloquear su servidor, o desviarlo a un sumidero, no limpia una máquina infectada. Le quita al operador la vista y los ataques bajo demanda, que no es poco. Pero el bot sigue instalado, sigue defendiéndose y sigue intentando infectar a otras máquinas. Para limpiarla hay que tocar la máquina.

05Retirado no es limpio

RUSTBOX tiene una orden para retirarse. Su rutina se puede leer en el código, cruzarla con su registro y con lo que dejó en la jaula. Hace esto: para sus trabajos, mata a su vigilante, recorre su .rbx_ledger, desactiva el servicio de systemd, borra los ficheros que creó y, en los que editó, quita sus líneas marcadas. Al terminar, informa:

lo que le dice a su dueño al irse
[PERSIST]state=cleared
Captura: la retirada, en GhidraGhidra
Dos trozos del listado de Ghidra de la rutina retirada_limpia de RUSTBOX. Arriba, las llamadas que leen su registro y deshabilitan rustbox.service. Abajo, el borrado del propio registro con unlink y varios saltos, entre ellos el del caso de error, que convergen en la misma etiqueta antes de cargar la cadena state=cleared. Recuadros ámbar marcan las dos llamadas, el salto de error y la cadena.
Retirado no es limpio. La rutina de retirada, en Ghidra: lee su registro, deshabilita el servicio, borra y quita sus líneas. Y al final dice state=cleared, también cuando no ha podido borrar su propio registro.

Y, según su código, no sería verdad del todo. Simulando su filtro sobre los ficheros de la jaula, esto es lo que quedaría:

lo que dejaría la retirada, fichero a fichero
/etc/crontab, /root/.bashrc, /root/.profile    restaurados tal como estaban
/etc/rc.local, /root/.zshrc                    vacíos: los creó él, pero los apuntó como editados,
                                               y al quitar sus líneas los deja en blanco
/var/tmp/.rbx_<pid>, el latido, el cerrojo     la rutina no los toca: no están en el registro
el spool de cron                               no vuelve a como estaba antes

Hay dos detalles más. Si encuentra dos marcas seguidas, se lleva también la línea que va detrás de la segunda. Y el state=cleared puede salir aunque no haya podido borrar su propio registro: quiere decir que la rutina llegó al final, no que la máquina haya quedado limpia. Que un bot diga que se ha ido no es lo mismo que que se haya ido.

Lo que se hizo, y lo que noRUSTBOX no llegó al cebo: lo recogieron otros, y lo saqué de un repositorio público de muestras. Se detonó en una máquina virtual sin tarjeta de red; todo su tráfico fue a una red falsa dentro de ella y se destruyó con la máquina. El servidor de mentira solo le dio órdenes inofensivas: ningún ataque, ninguna retirada. Ese programa no se publica. No se persiguió la orden de retirada ni se habló con su servidor de mando real.
Lo que no afirmoQue RUSTBOX venga del código de Superior. Comparten operador, servidor, puerto de ADB, supervisor y estilo, pero no el código, y la criptografía, con las mismas piezas, está montada de otra manera. Tampoco sé si el operador va a sustituir un bot por el otro o seguirá con los dos: lo que se ve en noviembre es que los alterna.

06Indicadores

Los de Superior están en los capítulos 35 y 36. Estos son los de RUSTBOX.

TipoValor
Repartohxxp://77.239.124[.]167:8080/rbx/{linux,android}-{arm7,aarch64,x86,x86_64} · cabecera Server: rustbox · guion /s con T="rustbox" que guarda el binario como .rbx.<plataforma>.<arquitectura> y lo borra tras lanzarlo
C2coolcams.duckdns.org y 77.239.124.167, TCP 29444 · saludo de 52 B que empieza por RBX1
Segunda etapa de la propagaciónhxxp://coolcams.duckdns[.]org/s?t=<vía> · vías realtek_8361, gpon_10561, hikvision_36260, draytek_8515, tplink_1389, netgear_6077, huawei_hg532
CVE explotadosCVE-2014-8361 · CVE-2017-17215 · CVE-2018-10561 · CVE-2018-10562 · CVE-2020-8515 · CVE-2021-36260 · CVE-2023-1389 · CVE-2024-12847
Persistencia Linuxrustbox.service (Restart=always) · /etc/init.d/rustbox y 99rustbox · crontab de root · /etc/rc.local · /etc/cron.d/rustbox · /etc/crontab (@reboot) · /etc/profile.d/rustbox.sh · .profile, .bashrc, .zshrc · ~/.config/autostart/rustbox.desktop · marca # rustbox · todo lanza /usr/local/bin/.rustbox boot
Persistencia Android (estático)/system/etc/init/rustbox.rc · módulo Magisk /data/adb/modules/rustbox (author=rustbox) · /data/local/tmp/.srvc · ADB en TCP 12108
Copias y ficheros/usr/local/bin/.rustbox, /root/.rustbox, /tmp/.rbx, /var/tmp/.rbx, /var/tmp/.rbx_<pid> (en Android también /data/local/tmp/.rbx, /data/adb/.rbx, /data/data/.rbx, /sdcard/.rbx) · .rbx_ledger, .rbx_lock, /dev/shm/.rbx_hb_<pid>, .rbx_wd.lock
Procesoskworker/u8:2 desde memoria · un segundo proceso con --watchdog · abre /dev/watchdog
Puertos ocupados23, 2323, 7547, 9001, 6667, 7777, 4444, 31337, 37215, 52869, 3333, 1337
Flood HTTP/2sec-ch-ua "Chromium";v="131" · Accept-Encoding: gzip, deflate, br, zstd · upgrade-insecure-requests · preámbulo PRI * HTTP/2.0
Cadenasrustbox: memory safe. system optimized. · etiquetas de informe [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 · otra build de .rbx.linux.x86_64 que VirusTotal vio el 7-nov (no analizada) 88466d25ef2de742baad70b68f65e0d3d0f1741723a2ee44043f273b8591b8f2

En una máquina infectada, que haya dejado de hablar con su servidor no quiere decir nada. Lo que lo delata está en la propia máquina: un kworker/u8:2 que no es del núcleo, doce puertos de bots ocupados por un mismo proceso y un .rbx_ledger que lleva la cuenta de todo lo que tocó.

Continuará - el cebo sigue encendido. Si el operador decide qué bot le toca a cada caja, la próxima que llame a la puerta dirá cuál. 🍯

Comentarios