Gafgyt / Bashlite · Capítulo 2

Abriendo el C2 con Ghidra

En el Capítulo 1 el C2 se quedó cifrado dentro del binario. Prometí destriparlo. Toca ingeniería inversa: Ghidra, la rutina de descifrado, y la configuración al desnudo — sin tocar la parte de ataque.

En el Capítulo 1 quedó un cabo suelto: el bot Gafgyt lleva su C2 escondido dentro del binario, cifrado con una tabla XOR, y con los métodos rápidos no cayó. Hoy lo abrimos como se debe — con Ghidra — y sale entero.

Aviso de siempre: esto es oficio de analista defensivo. Recuperar una configuración oculta para sacar IOCs no tiene nada que ver con reconstruir el arma. La parte de DDoS ni se toca; lo que buscamos es a quién llama el bicho.

01El binario en la mesa

Cargué la muestra en Ghidra (en headless, sin interfaz) y dejé que la auto-analizara. La pista para encontrar el descifrador salió de un simple strings sobre el binario: entre la basura aparecían, repetidas, la cadena BB2FA36AAA9541F0 y una tabla hexadecimal 0123456789ABCDEF. Basta pedirle a Ghidra quién referencia esos datos para dar con la rutina que los usa.

Un pequeño script recorre las referencias y decompila las funciones implicadas. Una destaca: FUN_00015d00 — corta, con un bucle y operaciones XOR. Ese es nuestro hombre.

02La rutina de descifrado

Así queda el corazón de FUN_00015d00 en el decompilador de Ghidra (recortado a lo esencial):

ghidra · FUN_00015d00 (decompilado)
do {
    *pbVar4      = *pbVar4      ^ (&DAT_00011070)[uVar5     & 0xf];
    pbVar4[1]    = pbVar4[1]    ^ (&DAT_00011070)[uVar5 + 1 & 0xf];
    pbVar4[2]    = pbVar4[2]    ^ (&DAT_00011070)[uVar5 + 2 & 0xf];
    pbVar4[3]    = pbVar4[3]    ^ (&DAT_00011070)[uVar5 + 3 & 0xf];
    uVar5  = uVar5  + 4;
    pbVar4 = pbVar4 + 4;
} while (param_2 != uVar5);   // param_2 = longitud

Se lee de un vistazo: XOR de cada byte del dato con una clave, indexada por posición & 0xf — es decir, una clave de 16 bytes que se repite. La clave vive en DAT_00011070.

El "ajá" que me tenía despistadoLa clave en DAT_00011070 resultó ser los caracteres ASCII de "BB2FA36AAA9541F0" (bytes 42 42 32 46 41 33 36 41…), no los bytes hexadecimales decodificados. Y por eso el relleno cantaba: la config lleva ceros de padding, y cero XOR clave = clave. Por eso en las strings del binario aparecía BB2FA36AAA9541F0 repetido — era el padding delatando la clave. Los datos reales no estaban hex-codificados; eran bytes crudos. Con eso claro, descifrar es una línea.

03La configuración al desnudo

Replico el algoritmo en Python (leer la clave de 0x11070, XOR con índice pos % 16) y aplico sobre los datos de config. Sale limpio:

config descifrada
# servidores C2 (con failover), puerto 1529
telemetry-pipe.sh:1529 | api-metadata-v6.is:1529 | sys-kernel-update.to:1529

# URL de configuración / actualización
https://api-metadata-v6.is/config.rar

Tres dominios de C2 en el puerto 1529, separados por | como lista de reserva, más una URL para bajar su configuración. Ese era el secreto que el cifrado guardaba.

04El disfraz

Fíjate en los nombres — no son casualidad. Están elegidos para parecer infraestructura legítima:

  • telemetry-pipe.sh — suena a "tubería de telemetría", y el TLD .sh (Santa Elena) hace que parezca un nombre de script de shell. Doble disfraz.
  • api-metadata-v6.is — parece un endpoint de API interno de lo más normal.
  • sys-kernel-update.to — huele a "actualizaciones del sistema", justo lo que un admin no mira dos veces.

Es esconderse a plena vista: en un log de conexiones, estos nombres no levantan la ceja. Y los TLDs de países pequeños (.sh, .is, .to) se eligen por disponibilidad y menos escrutinio.

05¿Sigue viva la infraestructura?

OSINT pasivo (resolución DNS + bases de datos de terceros; nunca toco los servidores):

Dominio C2Estado
telemetry-pipe.shno resuelve (dormido / retirado)
api-metadata-v6.isno resuelve (dormido / retirado)
sys-kernel-update.toVIVO → 141.98.11.51

Dos de los tres están dormidos — normal en una lista de C2 con reservas: no todos se activan a la vez. El que responde, sys-kernel-update.to, apunta a 141.98.11.51HostBaltic (AS209605, Lituania), otro hosting tolerante al abuso. Shodan lo ve como un servidor Windows (RDP, WinRM, SMB, MSSQL, cert autofirmado); el puerto real del C2 (1529) no sale en Shodan porque es poco común y no se escanea de rutina.

Un patrón que dice muchoCada pieza vive en un hosting offshore distinto: el loader entró desde DMZHost (NL), el binario se sirvió desde HostMayo (ZA), y el C2 vive en HostBaltic (LT). Reparten la infraestructura en tres proveedores de abuso para que tumbar uno no lo tire todo. Cutre en la ejecución, pero con cierta idea de resiliencia.

06IOCs y lo que me llevo

TipoValor
C2 (puerto 1529)telemetry-pipe.sh · api-metadata-v6.is · sys-kernel-update.to
C2 activo141.98.11.51 (HostBaltic, AS209605, LT)
URL confighttps://api-metadata-v6.is/config.rar
Cifrado configXOR · clave 16 bytes ASCII "BB2FA36AAA9541F0" · idx = pos & 0xf
  • El cifrado casero se abre leyendo el código. No hacía falta adivinar: la rutina de descifrado te dice el algoritmo. Ghidra convierte 15 minutos de suposiciones fallidas en una respuesta exacta.
  • El padding delata la clave. Zonas de ceros cifradas revelan el keystream — un clásico para atacar cifrados XOR.
  • Los nombres mienten. "telemetry", "api-metadata", "sys-update" son camuflaje. La reputación no está en el nombre, está en el comportamiento.

Y así se cierra el cabo suelto del Capítulo 1: el C2 estaba ahí, cifrado, y ahora está sobre la mesa — recuperado con la técnica del analista, no con la del atacante.

Continuará — el cebo sigue encendido. Cuando el próximo bicho traiga algo nuevo, habrá Capítulo 3. 🍯

Comentarios