Post

Análisis forense de un backdoor para macOS que se hace pasar por Claude Code

Análisis forense de un backdoor para macOS que se hace pasar por Claude Code

Hace unas semanas empezaron a circular páginas web fraudulentas que imitaban la página oficial de instalación de Claude Code, la herramienta de línea de comandos de Anthropic para desarrollo asistido por IA. El objetivo: que la víctima ejecutara voluntariamente un binario que, en realidad, era un backdoor completo para macOS. Este post es el resultado del análisis forense que realicé sobre ese binario usando Ghidra.

Fuente de contexto: Fake Claude Code Install Pages Spread Infostealer Malware – eSecurity Planet


Contexto: la campaña de distribución

La táctica es sencilla y efectiva. Los atacantes registraron dominios que imitan la presencia oficial de Anthropic y publicaron páginas que replican visualmente la documentación de Claude Code. Al hacer clic en “Instalar”, la víctima descarga y ejecuta un Mach-O firmado (o no firmado) para Apple Silicon.

El vector de ataque explota la confianza que los desarrolladores depositan en herramientas de IA: alguien que quiere instalar Claude Code no sospecha que la página que encontró en Google puede ser falsa. Según el reporte de eSecurity Planet, la campaña también utilizó typosquatting y anuncios pagados para posicionarse sobre los resultados orgánicos.

El binario analizado en este post es el payload que se descarga en esa cadena.


Perfil del binario

CampoValor
FormatoMach-O, ARM64 (Apple Silicon)
SO objetivomacOS
Símbolos de depuraciónNinguno
Strings visiblesPrácticamente ninguna (cifradas)
Tamaño aproximado de datos cifrados en .rodata~73 KB
ClasificaciónBackdoor / Dropper

Cadena de infección

infeccion

La infección sigue una cadena lineal desde la descarga hasta el establecimiento del canal C2:

  1. La víctima accede a una página falsa que imita claude.ai/code o similar.
  2. Descarga el binario disfrazado de instalador de Claude Code.
  3. Lo ejecuta manualmente (el social engineering hace el trabajo pesado aquí).
  4. El binario descifra sus strings en RAM, establece un pipe, hace fork() y lanza un subshell.
  5. Entra en un bucle C2 infinito donde recibe y ejecuta comandos arbitrarios.

Análisis técnico

1. Ausencia total de strings visibles

La primera señal de alarma al abrir el binario en Ghidra es que strings no devuelve nada útil. Ninguna URL, ningún path, ningún comando. Esto es intencional: todas las strings operativas están cifradas en .rodata y solo existen en RAM durante la ejecución.

La única string literal visible es "setup" — posiblemente el nombre de una tarea de instalación en una cadena de infección más larga — y el par de bytes 0x732D que en little-endian codifica "-s", el argumento que indica a sh que lea comandos desde stdin.

2. Motor de descifrado (RC4-like con S-box personalizada)

descifrado El corazón del ofuscador está en tres funciones (FUN_100001500, FUN_100082294, FUN_10008257c). Todas siguen el mismo patrón:

1
2
3
4
5
6
1. Buffer cifrado almacenado en .rodata
2. S-box de 256 bytes (precalculada, diferente en cada función)
3. Descifrado byte a byte:
      plaintext[i] = sbox[ ciphertext[i] ^ key_rolling ]
      key_rolling   = i + (key_rolling ^ plaintext[i])
   Semilla: 0x67, 0x99 o 0xB7 según la función

Es funcionalmente equivalente a RC4 con clave embebida. El resultado nunca toca el disco: se genera en heap, se usa, y se borra con _bzero().

El payload principal, construido por FUN_10008284c, usa una variante aritmética distinta sobre triplets (a, b, shift) almacenados en 57 bloques de 1284 bytes cada uno:

1
byte = ((b * 3 ^ a) >> (shift & 0x1f)) - b

3. Ejecución encubierta via fork + execvp + dup2

La función FUN_100000a10 es el ejecutor central. Su lógica es:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_pipe(&fds);          // crea canal IPC
pid = _fork();

if (pid == 0) {       // proceso HIJO
    _close(fds[1]);
    _dup2(fds[0], 0); // redirige stdin al pipe
    _close(fds[0]);
    _execvp(cmd, args); // reemplaza imagen con shell
    __exit(0x7f);
}

// proceso PADRE
_close(fds[0]);
// escribe datos en chunks ≤ 192 bytes con usleep(1) entre envíos
while (remaining > 0) {
    written = _write(fds[1], ptr, chunk);
    _usleep(1);
    remaining -= written;
}
_close(fds[1]);
_bzero(buffer, size); // borra evidencia en memoria
_waitpid(pid, &status, 0);

El hijo recibe el payload descifrado por stdin y lo ejecuta como comandos de shell. El padre borra el payload de memoria inmediatamente después. No queda rastro en RAM.

4. Bucle C2 con flujo de control ofuscado

La función FUN_1000db360 es el núcleo del malware: un bucle infinito que implementa una máquina de estados cuyas transiciones se calculan con XOR dinámico:

1
uVar12 = uVar5 ^ uVar12; // transición ofuscada

Esto hace que el flujo lógico sea opaco al análisis estático. La constante inicial 0x1095 guía la secuencia. Los estados relevantes son:

EstadoAcción
0Descifra la clave principal (FUN_1000010a4)
1Fork + execvp del payload descifrado
2–4XOR adicional sobre buffers intermedios
5Concatena segmentos del payload
6Descifra string C2 alternativa (FUN_100001500)
7Establece canal pipe → escribe payload → _bzero

Entre cada estado hay un _usleep(0x294) (~660 µs) para evitar saturar la CPU y dificultar la detección por comportamiento.

5. Verificación de integridad del payload

Antes de ejecutar cualquier cosa, FUN_1000007c8 valida que el payload no haya sido modificado. Combina:

  • Checksum aritmético: suma simple de todos los bytes.
  • CRC personalizado: usando tablas de lookup vectorizadas con la instrucción AArch64 TBL (table lookup SIMD).

Si la verificación falla, el malware aborta. Esto protege tanto contra análisis (modificar el binario para trazar la ejecución lo rompe) como contra tampering en tránsito.

6. Anti-forense activo

TécnicaImplementación
Strings cifradasRC4-like, S-box embebida, sin plaintext en disco
Borrado de memoria_bzero() tras cada uso de datos sensibles
Flujo ofuscadoTransiciones de estado vía XOR dinámico
Ejecución indirectaShell hijo vía dup2 + stdin, nunca args directos
Chunks pequeñosEscrituras en pipe ≤ 192 bytes con delays

Indicadores de compromiso (IOC)

1
2
3
4
5
6
Syscalls clave:   fork, execvp, execl, pipe, dup2, write, waitpid, usleep, bzero
Argumento shell:  -s  (0x732D, stdin mode)
String visible:   "setup"
Plataforma:       macOS ARM64 (Apple Silicon)
Datos cifrados:   57 bloques × 1284 bytes en .rodata (~73 KB)
S-box semillas:   0x67, 0x99, 0xB7

¿Qué hay después del descifrado?

La pregunta natural es: ¿qué strings aparecen cuando se descifra todo? El análisis estático permite reconstruir el algoritmo con precisión, pero no ejecutar el descifrado sin los bytes crudos de la S-box almacenados en .rodata. Esos bytes requieren un script de Ghidra (getBytes()) o una ejecución en sandbox.

Lo que sí se puede inferir del comportamiento:

  • El shell se invoca en modo -s (lee comandos de stdin) → ejecución de comandos arbitrarios remotos.
  • El estado 7 descifra una string antes de hacer el fork final → probable URL o dirección del servidor C2.
  • La string "K_nWpa" visible en .rodata tiene morfología de identificador o clave parcial.
  • El bucle no termina → persistencia en memoria mientras el proceso corre.

Para obtener el plaintext completo, el siguiente paso es un emulador de QEMU o un sandbox de macOS con frida hookeando el retorno de las funciones de descifrado.


Conclusión

Este malware es un ejemplo bien construido de backdoor moderno para macOS. No es sofisticado en el sentido académico — no explota vulnerabilidades del kernel ni usa rootkits — pero es efectivo porque no lo necesita. La víctima lo ejecuta voluntariamente, confiando en que está instalando una herramienta legítima de IA.

Las técnicas de evasión son sólidas:

  • Ningún antivirus basado en firmas de strings lo detectaría.
  • El análisis dinámico es dificultado por el borrado activo de memoria.
  • El análisis estático se complica por el XOR de estados y el payload cifrado.

La campaña de distribución demuestra que los atacantes están monitoreando activamente qué herramientas de IA son populares entre desarrolladores. Claude Code, Cursor, Copilot: cualquier herramienta con una base de usuarios técnicos y un proceso de instalación vía terminal es un vector atractivo.

Si descargás herramientas de IA desde fuera del canal oficial, verificá siempre el hash del binario y la firma del desarrollador.


Referencias

This post is licensed under CC BY 4.0 by the author.