Mirai · Capítulo 17
Recién salido del horno
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.
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.
Un 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.
El 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.
Y 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.
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:
condi.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 programaEsos 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.
/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/haltNo 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.
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.
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:
29 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ésY 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.
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:
| Tipo | Valor |
|---|---|
| C2 y servidor de reparto | cc.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 dominio | 2026-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ña | top1hbt (top1hbt.arm … top1hbt.x86_64, lo que cada bot re-sirve) |
| Autoidentificación | /var/Condi · condi2 %s:%d · webserv |
| Clave de la config | table_key = 0x6d53d2c2 → XOR efectivo de 1 byte 0x2e |
| Protocolo bot↔C2 | cabecera 66 99 66 + longitud (2 B) + carga (ping, condi2 webserv:<puerto>) |
| Huella del httpd del bot | Server: Apache (falso) · cliente User-Agent: Update v1.0 · puerto alto aleatorio |
| Anti-reinicio | borra con unlink 16 rutas: {/sbin, /usr/sbin, /bin, /usr/bin} × {reboot, shutdown, poweroff, halt} |
| Módulos amputados | sin scanner_* · killer_init (76 B) y killer_kill (48 B) vaciados — los símbolos están, la lógica de Mirai no |
| Certificado | nhancute.site + *.nhancute.site · Google Trust Services WE1 · CT 2026-08-29 07:54:11 UTC |
| Vector | ADB abierto (tcp/5555). Sin CVE: es mala configuración, no vulnerabilidad |
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. 🍯
Comentarios