Saltearse al contenido

Anti-debugging y anti-análisis

El software que no quiere ser analizado —malware, protecciones de licencia, DRM, anti-cheat— incorpora técnicas para detectar y obstaculizar al analista: comprobar si hay un debugger adjunto, si corre en una máquina virtual o sandbox, si se está instrumentando, o si el propio binario ha sido modificado. Al detectarlo, cambia su comportamiento, se cierra, o ejecuta lógica señuelo. Para el reverser es un juego del gato y el ratón: reconocer estas defensas y sortearlas.

# Linux
ptrace(PTRACE_TRACEME) # un proceso solo puede ser traceado una vez;
# si el programa se traza a sí mismo, un debugger no puede adjuntarse
/proc/self/status # el campo TracerPid != 0 indica debugger adjunto
# Windows
IsDebuggerPresent() / CheckRemoteDebuggerPresent() / PEB->BeingDebugged
NtQueryInformationProcess (ProcessDebugPort, etc.)
# genéricas
timing checks # el código bajo debugger es MUCHO más lento (rdtsc, GetTickCount)
detección de breakpoints # buscar bytes 0xCC (int3) en el propio código
señales/excepciones # manejar SIGTRAP de forma que delate al debugger

Anti-VM / anti-sandbox (detectar el entorno de análisis)

Sección titulada «Anti-VM / anti-sandbox (detectar el entorno de análisis)»
# artefactos de virtualización
instrucciones/registros de CPU (cpuid hypervisor bit), direcciones MAC de VMware/VBox
ficheros/procesos/claves de registro característicos de VM/sandbox
# detección de sandbox
poca actividad de usuario, poco tiempo de uptime, pocos núcleos/RAM
sleeps largos (las sandboxes limitan el tiempo de análisis -> dormir para evadir)
interacción humana ausente (no hay movimiento de ratón)
# checksums/hashes del propio código -> si alguien parchea el binario, no coincide
# comprobaciones de integridad en runtime
# código automodificante / cifrado (el estático no ve el código real, ver rev-static)
control flow flattening # aplanar el flujo para que el grafo sea ilegible
opaque predicates # condiciones siempre-verdaderas/falsas que confunden
string encryption # las cadenas se descifran solo en runtime
dead code / junk # instrucciones basura que no hacen nada
virtualización de código # un intérprete propio ejecuta bytecode custom (muy duro)
packers/protectores # VMProtect, Themida, UPX... (ver rev-dynamic para desempacar)
# anti-debug
- parchear la comprobación (NOP sobre el if, forzar el valor con GDB: set $rax=0)
- LD_PRELOAD de un ptrace falso (Linux) que siempre devuelve éxito
- plugins: ScyllaHide (Windows), hooks que ocultan el debugger
# anti-VM
- VMs más "realistas" (ocultar artefactos), bare-metal para muestras muy evasivas
- parchear las comprobaciones de VM
# anti-sandbox (sleeps)
- acelerar el tiempo / parchear los sleeps / hookear GetTickCount
# ofuscación
- dinámico (rev-dynamic): ejecutar y volcar lo descifrado; deofuscadores; angr
# anti-tampering
- parchear también la comprobación de integridad (o antes que el resto)

El principio: estas protecciones encarecen el análisis, no lo impiden. Casi todas se reducen a una comprobación que, localizada, se puede parchear o forzar.

# combinar varias capas (una sola se parchea fácil): anti-debug + anti-VM + integridad + ofuscación
# protectores comerciales (VMProtect/Themida) para elevar mucho el coste
# recordar: NINGUNA protege del todo contra un analista decidido ->
# para malware, la detección (EDR/sandbox) es la respuesta; para IP, retrasar al pirata
  • Detectar anti-debug (ptrace/IsDebuggerPresent/timing)
  • Sortearlo (parchear/forzar/LD_PRELOAD/ScyllaHide)
  • Detectar anti-VM/anti-sandbox (artefactos, sleeps)
  • Evadir (VM realista / parchear / acelerar tiempo)
  • Detectar anti-tampering (checksums) y parchearlo
  • Identificar ofuscación/packing y combatirla (dinámico/angr)
  • Combinar estático + dinámico para el código protegido
  • Documentar las protecciones encontradas