ES EN
Índice

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.

1

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.

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.
2

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.

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.
3

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.

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ño
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 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, 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ñana
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é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.armtop1hbt.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:<puerto>)
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. 🍯

Comentarios