ES EN
Índice

shem · Capítulo 29

Me dijo su nombre tres veces

Cayó en el cebo otro ELF de IoT con pinta de Mirai del montón. Lo bauticé «zbot» por sus ficheros de cron, y resultó que ni se llamaba así: por tres caminos distintos (las rutas que dejó en el disco, los banners con que habla SSH y HTTP, y los primeros bytes de cada mensaje a su central) el bicho me fue diciendo su verdadero nombre. Detrás había algo más interesante que un bot de tumbar servidores: una máquina para alquilar la línea de casa de su víctima como proxy. Y un operador que, con todo montado, rompía sus propias direcciones de descarga. XLab ya la había bautizado «Shemzbot» en un tuit; esto es el destripe que aquel tuit no traía.

Cayó en la trampa un ELF de 64 bits, estático, sin símbolos, de 1 MB. La presentación estándar de un bot de IoT, de los que veo a diario. Entró por telnet, no por SSH, y el matiz no es cosmético: telnet en el puerto 23 con la contraseña de fábrica es el vector de IoT de toda la vida, el que muchos ya ni miran. La credencial que le abrió la puerta fue root/xc3511: la de las cámaras XiongMai, la misma que encendió Mirai en 2016. Diez años después sigue funcionando.

Lo bauticé «zbot» porque así se llaman los ficheros de cron que deja al instalarse. Spoiler: ni siquiera se llama así. Pero la gracia de este bicho no está en el final, sino en el camino, en cómo me fue corrigiendo el nombre él solo una y otra vez. Así que vamos por orden.

ESPÉCIMEN · bot.x86_64

shem alias «Shemzbot» (XLab)

◈ VIVO · NO EJECUTAR
sha256 (con relleno)
1d3c4108…9f46cf8b
sha256 (ELF sin cola)
82ab1202…0a2d8124
BuildID
8e7a8299…3ded94c
Tipo
ELF 64-bit LSB, x86-64, estático, stripped
Tamaño
1.088.003 B (ELF 1.086.272 + 1.731 de cola)
Nombre interno
shem · «zbot» solo en sus crons

01El operador cambia el hash en cada descarga

El reflejo de siempre: saco el hash del fichero y lo busco, a ver si ya está catalogado. No lo estaba: ni en VirusTotal, ni en MalwareBazaar. Y eso, en un bot de reparto masivo y activo, no cuadra. Un bicho que infecta a mansalva deja muestras por todas partes; que el suyo no figurara en ningún sitio olía mal. Olía mal porque el hash mentía.

Miré el fichero por dentro: el ELF terminaba en su último segmento cargable y, sin embargo, el fichero seguía 1.731 bytes más allá. Una cola pegada al final, de máxima entropía (casi 7,9 bits por byte): ruido puro, nada de código ni de texto. La corté, rehíce el hash de lo que quedaba, y apareció al instante: catalogado, veinte motores marcándolo.

fin del fichero: la cola que no es ELF
# el ELF de verdad acaba aquí
  offset 0x109340  (1.086.272)      ┤ fin del ELF
  offset 0x109340 … 0x109A02        ┤ 1.731 B (0x6C3) de basura, entropía 7,88 b/byte
  tamaño del fichero = 0x109A03     (1.088.003 B)
  
  sha256(fichero completo) = 1d3c4108…   → no catalogado (único por descarga)
  sha256(ELF sin la cola)  = 82ab1202…   → MalwareBazaar / VirusTotal 20/…

Bajé todo lo que el servidor repartía y comprobé el truco: a cada descarga le añade una cola aleatoria distinta (las vi de 182 a 2.020 bytes). O sea que cada fichero que sirve su caja sale con un sha256 distinto. La consecuencia para quien defiende es dura: el hash del fichero completo no sirve de nada como indicador. Lo que aguanta es el hash del ELF sin la cola, y sobre todo el BuildID, la huella que la compilación deja grabada dentro y que sobrevive a la cola y al renombrado.

Un matiz, porque importa para cazarlo La cola no la pone el bot: la pone la caja del operador al servir el fichero. Cuando un nodo ya infectado reparte su propia copia al siguiente, la reenvía tal cual, con el mismo hash. El relleno cambia entre descargas del operador, no dentro de una rama de propagación. Aun así, la moraleja no cambia: no te fíes del hash del fichero entero.

02Esperaba una tabla cifrada. Estaba todo en claro.

Los forks de Mirai guardan su configuración en una tabla cifrada con un XOR que, en la práctica, es de un byte; uno la busca por reflejo, casi sin pensar. Aquí no había tabla: la definición entera del protocolo estaba en claro en .rodata, en inglés, a la vista con un simple strings. Las órdenes, sus formatos, las plantillas. (El tráfico en el cable es otra historia: ese sí va cifrado, y lo abro en el §05. Pero el vocabulario del bicho estaba servido.) La lista completa de las órdenes que acepta de su central, con sus formatos scanf, que de regalo me cantaban los argumentos de cada una:

OrdenFormatoQué hace
PING / PONG-latido
PROXYON / PROXYOFF-enciende / apaga el proxy (§06)
UPDATE%255s → /tmp/.updautoactualización
SHELLSHELL %s %s %sshell remota interactiva (/bin/sh -i)
UDPUDP %63s %d %dinundación UDP (destino, puerto, duración)
AMPAMP %63s %hu %d %15sDDoS por amplificación (chargen, ntp)
TGTS / VTGT%23s %23s %ucarga objetivos para el escáner
SCAN / STOPSCAN-arranca / detiene el escaneo
YOURIP%63sla central le dice su IP pública
NOREPL / NOTOR / NOIDIRECT-modificadores de transporte
DIE / LOLDONE / LOL-terminar / acuse

Y lo que emite de vuelta: ZBOT %s %s (el registro), ZCR1 %s (credenciales cosechadas), y los hallazgos del escáner como %s|%s|telnet-brute. Entre las cadenas, algo que un bot no debería llevarse jamás a producción: las rutas absolutas del disco de su autor.

strings: el escritorio del desarrollador, compilado dentro
/root/shem/targets.txt
  /root/shem/web_targets.txt
  /root/shem/targets_adb.txt      ← también guarda objetivos ADB
  
  SSH-2.0-sheM                    ← el banner de su cliente SSH
  User-Agent: sheM                ← en sus peticiones HTTP
  SHEM_INST  SHEM_LAB  SHEM_NOMASQ  SHEM_RAM_MB

shem. Ese es el nombre, y lo deja por todo su propio disco: las rutas /root/shem/… y las variables SHEM_INST, SHEM_LAB, SHEM_NOMASQ, SHEM_RAM_MB que dejó el desarrollador. «zbot» era solo la etiqueta de sus crons; shem es como lo llama su autor en su máquina. Primera vez que me dice cómo se llama.

Y lo repite en cómo se presenta al hablar con el mundo: su cliente SSH se anuncia SSH-2.0-sheM, y sus peticiones HTTP van con User-Agent: sheM. Firmas que valen mucho más que «zbot»: el operador puede cambiar de servidor mañana, pero esas no se tocan sin recompilar. Segunda vez, por otro camino.

03El error que enseñó el panel del operador

En el registro del ataque había URLs imposibles. Esta, que troceo para que no sea clicable:

el loader: una URL que no debería existir
hxxp://176.65.134[.]121:36023PROXYONUPDATE:7416/bot.$m
                          └── un puerto, un comando, y otro puerto ──┘

Un puerto, la palabra PROXYONUPDATE, y otro puerto. Eso no es una dirección. Mi primera lectura fue que el bot se auto-aplicaba PROXYON y UPDATE como argumentos; monté un test, con el pegote y sin él, y daba exactamente igual. El test no detectó nada. Me quedé con que la pista no valía, hasta que entendí que el fallo era mío, no de la pista. Estaba midiendo la cosa equivocada; por qué, lo entiendo en el §06.

Porque la URL rota, bien mirada, contaba algo mucho mejor. Puse en fila varias descargas y el patrón cantó. La explicación que mejor encaja con las capturas: su panel maneja una variable «host» con {IP}:{puerto aleatorio}, y a veces se le concatena detrás la cola de comandos pendientes (PROXYON, UPDATE), que acaba colándose donde solo debería ir la dirección de descarga. Los días que el campo va limpio, la URL de descarga es válida; los días que arrastra comandos, esas URLs de descarga fallan. PROXYONUPDATE no es del bot: es, casi con seguridad, una fuga del panel del atacante, y deja entrever cómo tiene montada la consola.

04A la jaula: habla UDP, y dice «SH»

Dónde está la línea, y por qué la muevo Hasta aquí todo es análisis en frío. Pero para ver el protocolo vivo tenía que encenderlo, y eso lo hago donde siempre: en una jaula aislada, en el escritorio del laboratorio, nunca en el cebo. Red cerrada, su propia central apuntada a una máquina mía, y la prueba de fuego antes de arrancar: no sale ni un paquete a internet. Observar un bicho en una caja sin salida no es soltarlo: es mirarlo por dentro sin que pueda tocar a nadie. Esa es la línea, y es la misma de siempre.

Lo detoné apuntando su C2 a mi propia máquina (el binario lo acepta por argumento, sin parchear nada). Y lo primero ya corrigió dos cosas que yo tenía mal apuntadas. No habla TCP con su central: habla UDP, por el puerto 6881, el de BitTorrent. Si lo eligió a propósito o es casualidad no lo sé, pero el efecto le viene bien: su tráfico UDP en el 6881 puede pasar por ruido de P2P, y quien lo busque por TCP no lo verá pasar. Y los tres primeros bytes de cada mensaje:

tcpdump: la baliza a la central (UDP/6881)
→ UDP  53 48 01                                  "SH" + tipo (hola)
  → UDP  53 48 06  e1 f0 46 e1 dc 85 60 52 c6 …    "SH" + registro (cifrado)
            │
            └── 0x53 0x48 = "SH"  →  las iniciales de shem

53 48 es SH en ASCII: las iniciales de shem. El propio protocolo firma con el nombre. Tercera vez que me lo dice, y van tres caminos independientes: el disco, cómo se presenta en SSH y HTTP, y ahora los dos bytes que abren cada datagrama con su central. (En claro van solo esos tres bytes: la cabecera SH + tipo. De ahí en adelante todo viaja cifrado, empezando por un identificador de sesión de 9 bytes, aleatorio en cada ejecución salvo su último byte, que se mantiene constante. No sirve como indicador, y en el §05 se verá por qué importa que cambie.)

05RC4 con clave fija, roto sin romperlo

Tras la cabecera SH, el cuerpo iba cifrado. Pasé el binario por capa y me dio el algoritmo: RC4, un cifrado de flujo. La regla de oro de RC4 es sencilla: el chorro de bytes que genera, el keystream, no se reutiliza jamás. Dos mensajes cifrados con el mismo keystream y el cifrado se desmorona.

Sospeché que se la saltaba, y comprobarlo fue barato: capturé el mismo mensaje de registro en dos ejecuciones distintas, cada una con su identificador de sesión aleatorio. Con un cifrado decente, ese cuerpo cifrado tendría que salir distinto las dos veces. No salió.

dos ejecuciones, sesión distinta, mismo cuerpo cifrado
# datagrama: [SH+tipo 3B] · [sesión 9B, desde offset 3] · [cuerpo, desde offset 12]
  # la sesión (9B) SÍ difiere entre tiradas; aquí comparo solo el CUERPO (offset 12+):
  tirada 1  (sesión e1f046…):  cuerpo = 01 2f f4 dc 8b 06 6e dd df 4b d3 e0 60 …
  tirada 2  (sesión ef368c…):  cuerpo = 01 2f f4 dc 8b 06 6e dd df 4b d3 e0 60 …
                                               ↑ idéntico byte a byte, pese a sesiones distintas
  
  ⇒ keystream reutilizado: misma clave en cada tirada, ajena a la sesión

El experimento demuestra directamente una cosa: keystream reutilizado. Con nonces distintos, el mismo texto claro da el mismo cifrado, así que el flujo de cifrado no cambia entre ejecuciones ni depende del nonce; la clave, por tanto, es constante (no abrí el KSA para extraer sus bytes, pero el efecto es el de una clave fija). El error de manual de RC4. Lo bonito es que no hace falta atacar RC4 con matemáticas: basta con recuperar el keystream. Puse un punto de ruptura en la función de RC4, volqué el búfer antes (en claro) y después (cifrado) de la llamada, y con un simple K = claro ⊕ cifrado tenía el chorro. El keystream entero me lo guardo: publicarlo sería regalar la llave para descifrar y falsear el tráfico de cualquier nodo, y esto es un análisis, no un kit. Con dos mensajes distintos confirmé que K es el mismo siempre, aplicado a todo lo que sigue a la cabecera de 3 bytes (el offset 3: la cifra cubre el identificador de sesión y el cuerpo). A partir de ahí, el canal con la central se lee en claro:

con el keystream, el canal C2 en claro
# K recuperado con gdb (break @ 0x40a410). Lo muestro recortado a propósito:
  K = 0e7aeeba 7f3c91a0e4d28b651af09c37b802e5d49c6a1f08d3b47e52a0c9 …   ← keystream (redactado por OPSEC)
  
  # con K, el canal C2 se lee en claro:
  SH 06  <9B sesión> "ZBOT x86_64 overlay"   ← registro: arquitectura + transporte
  SH 06  <9B sesión> "ZCR1 <32hex>"          ← reporte de credenciales cosechadas

Y con el protocolo en la mano monté un sumidero que habla shem, todo dentro de la jaula. Aquí el bot se delató solo: le mandé un SH 02 por UDP y me apuntó como peer (se quedó con la IP y el puerto de origen de mi datagrama), me llamó de vuelta por TCP y me habló SOCKS5 como cliente. La prueba en vivo de que, en modo overlay, encamina su salida a través de sus propios peers.

sumidero: inyecto un peer y el bot me relaya
→ UDP 6881  53 48 02            respondo "SH" 02 a la baliza
              → el bot me apunta como peer (mi IP:puerto de origen)
  ← TCP       05 01 → CONNECT 127.0.0.1:1338   me habla SOCKS5 como cliente
  →           05 00 …            completo el handshake: túnel aceptado
              (127.0.0.1:1338 = el destino que pide alcanzar por el túnel; yo haría de salida)

06PROXYON: el interruptor que lo explica todo

Al detonarlo abría tres puertos: el 6881/UDP de la central y dos TCP, el 7331 y el 7332. El 7331 respondía por fin a la pregunta que arrastraba desde el comando PROXYON. Porque ese puerto siempre se abre y siempre escucha. La diferencia no está en abrir: está en el accept.

el interruptor, en el binario (decompilado)
conexion = accept(...);
  if (interruptor == 0)  close(conexion);      // apagado: te cuelga sin contestar
  else                   crear_hilo(conexion);  // encendido: te atiende
  
  // interruptor (global) ← lo mueven exactamente dos comandos de la central:
  PROXYON  → interruptor = 1        PROXYOFF → interruptor = 0

No es un modo de arranque: es un mando a distancia. Y aquí está por qué mi test del §03 falló: desde fuera, encendido y apagado tienen los mismos puertos abiertos. Yo estaba midiendo la cerradura en vez de mirar si la puerta se abre. Con el interruptor a 1, le mandé al 7331 un saludo de SOCKS5 y me respondió con el «adelante»; con el interruptor a 0, me colgó sin una palabra.

Y esto cierra la duda del §03: el proxy se enciende por el canal C2, no por un argumento de arranque. Así que el PROXYONUPDATE pegado en la URL no puede ser el bot auto-aplicándose comandos, y encaja con la fuga del panel. Honestamente, es deducción y no prueba cerrada: no relancé el bot con el pegote midiendo el handshake del 7331, solo comparé puertos (que era el error). Pero con PROXYON ya demostrado como comando del C2, es la única lectura que cuadra con todo.

hablándole al 7331 con el interruptor a 1
→ 05 01 00   saludo SOCKS5 (versión, 1 método, sin auth)
  ← 05 00      responde: es un proxy SOCKS5 sirviendo

La palabra «SOCKS» no aparece ni una vez en el binario: está implementado a mano, byte a byte. Ese «no hay cadenas de proxy» que me despistó al principio no era ausencia de proxy: era ausencia de literales. Y para salir, el bot maneja cuatro modos de transporte (direct, relay, tor, overlay): habla SOCKS5 por los dos lados, lo ofrece en el 7331 y lo consume para encaminar lo suyo, con Tor entre las opciones.

aparato infectado(cámara, router, línea de casa)puertos = base + 2·N  (N = SHEM_INST)C2176.65.134.121UDP 6881magic «SH» · RC4cliente del proxy(el que lo usa)TCP 7331SOCKS5 · solo si PROXYONnueva víctimale pide bot.<arch>TCP 7332sirve su propio binario por HTTP
Un nodo shem abre tres puertos, y son una fórmula: 6881/UDP (mando, cifrado), 7331/TCP (el proxy SOCKS5, con interruptor remoto) y 7332/TCP (se reparte a sí mismo). Con N = 0: 6881 / 7331 / 7332.

07No hay servidores de reparto dedicados. Hay víctimas.

El tercer puerto, el 7332, es un servidor web. Le pedí bot.x86_64 y me sirvió su propio binario, idéntico byte a byte a la muestra. Es decir: cada aparato infectado es también el que infecta al siguiente. No hace falta un servidor de reparto dedicado: el grueso del reparto recae en las propias víctimas. Pero la central también sirve (los .se/.sk salen de su IP, §10), así que no es todo víctima-a-víctima.

Y los tres puertos no son fijos: se calculan, base + 2N con N = getenv("SHEM_INST"). Los puertos de reparto de los loaders de la central (7416 → N=42, 7420 → N=44, 7428…) encajaban todos en 7332 + 2N: no era «rotación de puertos», era aritmética. Su caja reparte sobre todo por el 8080 por defecto, pero sirve el binario también en varios de esos puertos altos. Shodan vio abierto el 7434 (= 7332 + 2·51), lo que apuntaría a una instancia con N=51 corriendo en la propia central. Lo dejo como indicio, no como hecho: un N=51 completo abriría además 7433/TCP (SOCKS5) y 6983/UDP, y la ficha de Shodan (22/8080/7434) no los recoge, probablemente porque no barre esos puertos. Lo firme es que el operador también sirve desde su caja.

C2 (Pfcloud)coordina y también sirvevíctima Alínea residencial (HK)sirve bot en :7332víctima Binfectada por Aahora sirve en :7332víctima C…la siguienteórdenes (UDP/6881)el grueso del reparto recae en las víctimas; la central también sirve
La propagación es autorreplicante. La segunda IP del caso, que yo tenía apuntada como «servidor de reparto», resultó ser una línea residencial de Hong Kong: una víctima haciendo de repartidora. Y una IP doméstica es, exactamente, la mercancía que se vende como «proxy residencial».

Ahí se ve para qué está montado: shem infecta líneas de casa, abre un SOCKS5 en ellas bajo demanda, y de paso las usa para infectar a las siguientes. El producto no es el ataque: es tu conexión.

08El arsenal de propagación

Por telnet reparte el bot.<arch> en claro. Pero para propagarse por la red trae, además, un juego de exploits contra aparatos del borde, embebidos como plantillas.

Aviso de rigor El binario no contiene ni una cadena CVE-…. El mapeo de abajo es identificación mía de cada endpoint, no una etiqueta que traiga el bicho. Los módulos van rotulados por dentro od_hnap1, od_netgear, od_fourfaith, od_thinkphp…
Endpoint (verificado en el binario)AparatoIdentificación mía
/ctrlt/DeviceUpgrade_1Huawei HG532CVE-2017-17215
/GponForm/diag_Formrouters GPONCVE-2018-10561/10562
SetNTPServers (SOAP)TR-064 / CWMPinyección NTP (Mirai 2016)
SetNTPServerCredentials (HNAP1)D-Link HNAPinyección por HNAP/SOAPAction (p.ej. CVE-2015-2051)
/setup.cgi?…todo=syscmdNetgear DGNRCE setup.cgi syscmd
/cgi-bin/luci/;stok=/locale?form=countryTP-Link Archer AX21CVE-2023-1389
/cgi-bin/nas_sharing.cgi?user=message…D-Link NASCVE-2024-3273
/apply.cgi (od_fourfaith)Four-FaithCVE-2024-12856 (prob.)
/tmUnblock.cgiLinksys E-series«TheMoon»
thinkphpThinkPHP 5RCE ThinkPHP
/ftptest.cgi?loginuse=…DVR / cámarainyección ftptest.cgi

Lo relevante no es que use CVE viejos (el de Huawei es de 2017; los de GPON y TR-064, de 2016-18, son de dominio público): es que mezcla clásicos con otros relativamente recientes: TP-Link Archer de 2023, D-Link NAS y Four-Faith de 2024. Es un arsenal mantenido, no un copia-pega abandonado.

Y un detalle fino: hay dos estilos de entrega. Algunas plantillas sueltan el bot en claro; otras entregan una cadena cifrada, partida en tres piezas.

A · por telnetbot.<arch>EN CLARO/tmp/.z → runB · por exploit (más sigiloso)stub.<arch>bot.<arch>.sebot.<arch>.sk.se = bot CIFRADO (~1 MB).sk = clave RC4 (128 bits, rota)stub descifra.se con .skbot en claro/tmp/.z → execve
Por telnet, el bot viaja en claro. Por exploit, viaja cifrado: el stub es un cargador que lee el bot cifrado (.se) y su clave (.sk), descifra y ejecuta. Solo el stub recibe chmod +x.

09Persistencia, y lo que trae de más

Persistencia quíntuple, más terca de lo habitual: se engancha al arranque por cinco sitios a la vez: crontab -, /etc/cron.d/zbot, /var/spool/cron/zbot, /etc/crontab y /etc/rc.local. Y el cron no vigila un proceso: cada 5 minutos comprueba [ -x /tmp/.z ] y, si falta, lo restaura desde /var/.z o /etc/.z. Para tener de dónde restaurar, se copia (%s/.z) en seis rutas: /tmp, /var, /etc y además /jffs, /rw, /data (particiones escribibles de routers, firmware embebido y Android), lo que confirma, sin que lo diga, que el objetivo es IoT.

Y de regalo: un barrido por los binarios SUID del sistema que lanza el bot desde cada uno, un intento de cargar un módulo de kernel (el .ko no venía embebido todavía), un intento de autoborrado (unlink de /proc/self/exe y de sus descriptores) que en Linux ni siquiera borra el fichero del disco, camuflaje con nombres [kworker/…], la apertura de /dev/watchdog para que el perro guardián del hardware no reinicie el aparato (un reinicio se llevaría el bot, que solo vive en RAM), y un matador de competidores. Este último trae un desliz precioso de linaje:

killer list: la genealogía asomando por un descuido
# bots de Linux (esperable):
  mirai.arm  tsunami  hakai  sora  qbot  astaroth  …
  # …y de pronto, antivirus de WINDOWS en un bot de Linux:
  msmpeng  avast  avgnt  kavsvc  clamd  msseces.exe
  # aquí no hay nada de Windows que matar → indicio de código reciclado (de origen Windows)

No hay un solo proceso de Windows que matar en un router Linux. Esos nombres parecen una lista heredada de otro sitio y nunca limpiada, probablemente de código pensado para Windows, que es donde esos antivirus tienen sentido. De qué familia exactamente no lo sé: es un buen indicio de código reciclado, no una prueba del ancestro.

10La nombraron de pasada; aquí va entera

Antes de cerrar hice la comprobación de siempre, la aburrida: ¿esto ya lo ha contado alguien? Porque un bot-proxy tan completo y tan activo bien podría estar documentado y yo llegando tarde. Miré en los sitios públicos (MalwareBazaar, VirusTotal, ThreatFox, URLhaus) y la respuesta tiene matiz: la muestra sí circula, pero casi nadie sabe qué es.

El ELF sin cola está catalogado, pero como trojan.mirai a secas, uno más del montón. El fichero entero, con su cola aleatoria, no existe en ningún repositorio, que es justo lo que buscaba el operador. La central la tienen fichada como «Bashlite». Ninguna de esas etiquetas dice shem, ni proxy, ni describe el protocolo. Para la automatización es ruido de Mirai; el nombre de familia no lo pone una máquina. No está sin catalogar por sigilosa: está mal catalogada porque rompe justo la pieza con la que se cataloga, el hash.

Con una excepción, y conviene decirla: XLab, el laboratorio de Qi'anxin, la bautizó Shemzbot el 23 de septiembre, en un tuit con el C2 correcto y el diagrama de su cadena RC4 .se/.sk/.z. Un vistazo certero, y de gente que sabe. Pero un tuit no es un destripe: lo que estás leyendo es lo que aquel mensaje no traía, desde la cola anti-IOC del §01 hasta el keystream reutilizado del §05 y el interruptor del proxy del §06. Y un guiño que me hizo gracia: ellos juntaron en un solo nombre los dos que yo había sacado por separado, mi shem de las cadenas y el zbot de los crons.

Lo que confirman los registros públicos, sin tocar nada Dos de mis hallazgos de la jaula se pueden comprobar desde fuera, con datos de terceros. Un buscador de aparatos ve, en la central, la misma clave SSH que yo apunté y el puerto 7434 abierto (= 7332+2·51, §07). Y un repositorio de URLs maliciosas lista, en esa misma IP, las piezas .se y .sk de la entrega cifrada del §08. No necesité volver al bicho: la red ya lo estaba contando, solo que sin saber a quién.

11Familia propia, con herencia (y en obras)

Propia en lo que importa: protocolo inventado (UDP/6881, magic SH, RC4), sin la tabla XOR de Mirai, arquitectura de cuatro transportes, y los marcadores shem por todas partes. No es un Mirai maquillado.

Heredada en los detalles: la killer-list con antivirus de Windows y el arsenal de exploits de dominio público. Y en obras: comparada con una compilación de apenas cinco días antes, en ese margen el autor había añadido los transportes relay y overlay, el propagador por SSH, la numeración de instancia SHEM_INST, el reporte ZCR1 y el intento de módulo de kernel. Es un proyecto que su dueño mejora semana a semana.

En el fondo, shem no es «o proxy o DDoS»: es una plataforma de acceso que hace las cuatro cosas (proxy SOCKS5 bajo demanda, DDoS, backdoor y gusano autorreplicante). El diseño apunta a que la monetización principal puede ser la reventa de proxies residenciales; el DDoS, la puerta trasera y la propagación son capacidades adicionales de la plataforma.

La tendencia, más allá de este bicho, está clara: el borde de la red se está convirtiendo en mercancía de proxy, y shem es un ejemplar particularmente completo de esa ola.

12Indicadores (IOCs)

Los estables, los que sobreviven a que el operador cambie de servidor mañana:

TipoValor
Familiashem (nombre interno) · alias externo «Shemzbot» (XLab/Qi'anxin) · «zbot» = sus ficheros de cron
Firma en redSSH-2.0-sheM · User-Agent: sheM · magic 53 48 («SH») sobre UDP/6881, cuerpo RC4
Puertos (nodo)6881/UDP · 7331/TCP (SOCKS5) · 7332/TCP (reparto) · fórmula base+2N (N=SHEM_INST)
Detecciónhost que escuche P y P+1 TCP + (P−450) UDP
Vectortelnet 23 / 2323, credenciales de fábrica (root/xc3511, admin/admin, admin/1234)
Marcadores/root/shem/{targets,web_targets,targets_adb}.txt · SHEM_INST · SHEM_LAB · SHEM_NOMASQ · SHEM_RAM_MB
ComandosPROXYON PROXYOFF UPDATE SHELL UDP AMP SCAN STOPSCAN TGTS VTGT YOURIP NOREPL NOTOR NOIDIRECT DIE · modos direct/relay/tor/overlay
En el hostcopias %s/.z en /tmp /var /etc /jffs /rw /data · cron 5 min que restaura · barrido SUID · insmod /tmp/zbot.ko · intento de autoborrado unlink /proc/self/exe (no efectivo en Linux)
Etapas por exploitstub.<arch> (cargador+descifrador) · bot.<arch>.se (bot cifrado) · bot.<arch>.sk (clave RC4 128b, rota)
Claves .sk vistas (RC4 128b)b2134c3e17e0a9b77119ebbf05f122b5 · 659ed8e9cee54f7127358ff92a96f6d2  (descifran su propio .se; rotan por build, ya caducadas)
HashIOC = BuildID + sha256 del ELF SIN la cola de relleno  (el del fichero completo NO vale)
SHA-256 · ELF sin cola (x86_64, la que atacó el cebo)82ab1202a5941b9d2947e99b2b03bc469e2a97f590c6a4efea819da20a2d8124  (catalogado en MalwareBazaar/VT)
BuildID (x86_64)8e7a8299899cf8dcb9b64116645a236a63ded94c  (pivote estable, sobrevive a la cola y al renombrado)
SHA-256 · copia con relleno1d3c4108464cd2370ee428c296f64552af89fdebf19051e4bb1666db9f46cf8b  (ejemplo de UNA descarga; no agrupa: relleno aleatorio por entrega)
ELF sin cola · multi-arch (lote de reparto 23-sep)bot.x86_64 674107dcf1a876489123240add215cbb92649c2bbb321780057822232891ac10 (build del reparto, distinta de la capturada)
bot.arm64 ef7466d1507430fc0e0f99c29d7184ee726e68934895bfdc37ad855c0c488b91
bot.armv7 74af9d637530df497444e47bfc12e4aba66a084546d413d1e0b54d7282c2a777
bot.mipsel fe12480efddd9603004a4165265e57192242a79144b124a936bfe95e66de2cb4 (= mips64el, mismo ELF)
stub.x86_64 37dd737a342729c7b8c4e6f28140f2ff977a433793482b6bd488efd56a20812e (catalogado en MB desde 20-sep)
Fingerprint C2clave SSH ce:cd:4c:9e:86:a2:76:40:40:88:f9:50:85:a8:21:01 (ecdsa) · hassh 671c5858… · puertos vistos por Shodan 22 / 8080 / 7434 (7434 = 7332+2·51; ficha parcial)
Infra C2176.65.134.121  (datacenter, AS51396 Pfcloud)
Repartovía víctimas (p. ej. una línea residencial de HK); NO listar como infraestructura del operador
Firma del actorbug de plantilla: hxxp://<IP>:<puerto>PROXYONUPDATE:<puerto>/bot.$m
Documentación públicaXLab (Qi'anxin) la nombró «Shemzbot» el 23-sep-2026 · el tuit (@Xlab_qax)

Continuará - el cebo sigue encendido, y shem sigue en obras. Cuando el operador suelte la próxima versión, volveré a mirar qué le ha puesto. 🍯

Comentarios