La carrera armamentista entre EDR y evasión
Cada vez que ejecutas un beacon de Cobalt Strike en una estación de trabajo con SentinelOne o CrowdStrike Falcon, desencadenas una cascada de eventos en el kernel de Windows: callbacks de object manager interceptan la apertura de procesos, hooks en userland registran cada syscall, y ETW transmite eventos de creación de procesos y carga de módulos en tiempo real. Todo llega al motor de detección, que compara tu comportamiento contra heurísticas y modelos de machine learning.
El problema para los defensores es que esta cadena de vigilancia tiene puntos de inyección vulnerables. El EDR no es omnisciente: depende de hooks en userland, registros en kernel y telemetría que puede ser interceptada o eliminada. Este artículo diseña, nivel por nivel, las técnicas que los operadores de Red Team utilizan para evadir la detección de endpoint.
¿Qué es un EDR y cómo detecta amenazas?
Un Endpoint Detection and Response (EDR) es una solución de seguridad que monitoriza en tiempo real la actividad de endpoints para detectar, investigar y responder a amenazas. A diferencia de un antivirus tradicional, que se basa en firmas estáticas, un EDR analiza el comportamiento del sistema.
| Capa | Mecanismo | Qué captura |
|---|---|---|
| Userland hooks | Hooks inline en ntdll.dll | Llamadas a CreateRemoteThread, VirtualAllocEx |
| Kernel callbacks | PsSetCreateProcessNotifyRoutine, ObRegisterCallbacks |
Creación/modificación de procesos |
| ETW providers | Microsoft-Windows-Threat-Intelligence |
Carga de DLLs, inyección de código |
| AMSI integration | AmsiScanBuffer |
Contenido de scripts en runtime |
Level 0: Process Injection
La inyección de procesos ejecuta código arbitrario dentro del espacio de memoria de un proceso legítimo, aprovechando que el EDR tiene mayor dificultad para distinguir comportamiento malicioso dentro de procesos de sistema o aplicaciones conocidas.
DLL Injection clásica
BOOL InjectDll(DWORD dwPid, const char* szDllPath) {
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProc) return FALSE;
size_t pathLen = strlen(szDllPath) + 1;
LPVOID pRemoteMem = VirtualAllocEx(hProc, NULL, pathLen,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
WriteProcessMemory(hProc, pRemoteMem, szDllPath, pathLen, NULL);
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
FARPROC pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProc, NULL, 0,
(LPTHREAD_START_ROUTINE)pLoadLibrary, pRemoteMem, 0, NULL);
WaitForSingleObject(hThread, 5000);
CloseHandle(hThread); CloseHandle(hProc);
return TRUE;
}
Detección EDR: Las APIs VirtualAllocEx + WriteProcessMemory + CreateRemoteThread forman una cadena de indicadores que todos los EDR correlacionan como IoC.
Process Hollowing (RunPE)
Crea un proceso legítimo suspendido, vacía su memoria y reemplaza su contenido con código malicioso antes de reanudar la ejecución. Los EDR lo detectan monitoreando NtUnmapViewOfSection seguido de escrituras al espacio de memoria del proceso, y verificando la consistencia entre el PE en disco y el PE en memoria.
APC Injection
Inserta una llamada asincrónica en la cola de APC de un thread alertable. Es menos invasiva que CreateRemoteThread. La variante Early Bird inyecta APC en el thread principal durante la inicialización, antes de que el EDR complete su instrumentación.
Level 1: Unhooking — Eliminación de hooks en Userland
Los hooks que el EDR instala en ntdll.dll son su primera línea de visibilidad. Eliminarlos deja ciega la telemetría a nivel de syscall.
Refresh de módulo desde disco
Carga una copia limpia de ntdll.dll desde disco (sin hooks) y sobrescribe la sección .text en memoria:
import ctypes, os, struct
def unhook_ntdll():
ntdll_path = os.path.join(os.environ['SystemRoot'], 'System32', 'ntdll.dll')
with open(ntdll_path, 'rb') as f:
ntdll_clean = f.read()
ntdll_base = ctypes.WinDLL('kernel32').GetModuleHandleA(b'ntdll.dll')
pe_off = struct.unpack('<I', ntdll_clean[0x3C:0x40])[0]
sec_off = pe_off + 0x18 + struct.unpack('<H', ntdll_clean[pe_off+14:pe_off+16])[0]
num_sec = struct.unpack('<H', ntdll_clean[pe_off+6:pe_off+8])[0]
for i in range(num_sec):
sec = sec_off + i * 40
if ntdll_clean[sec:sec+8].rstrip(b'\x00') == b'.text':
vaddr = struct.unpack('<I', ntdll_clean[sec+12:sec+16])[0]
rawsz = struct.unpack('<I', ntdll_clean[sec+16:sec+20])[0]
rawoff = struct.unpack('<I', ntdll_clean[sec+20:sec+24])[0]
old = ctypes.c_ulong(0)
ctypes.WinDLL('kernel32').VirtualProtect(
ntdll_base + vaddr, rawsz, 0x40, ctypes.byref(old))
ctypes.memmove(ntdll_base + vaddr, ntdll_clean[rawoff:rawoff+rawsz], rawsz)
return True
return False
Direct Syscalls
Saltan completamente ntdll.dll ejecutando el syscall directamente:
NtAllocateVirtualMemory proc
mov r10, rcx
mov eax, 18h ; Syscall number
syscall
ret
NtAllocateVirtualMemory endp
Los números de syscall cambian entre builds de Windows. Bibliotecas como SysWhispers3 y HellsGate resuelven dinámicamente el número correcto inspeccionando los stubs en memoria.
Indirect Syscalls
Resuelven la dirección del stub en ntdll.dll pero ejecutan solo la instrucción syscall desde una dirección no hookeada, mitigando las protecciones que detectan la ausencia total de llamadas a ntdll.dll.
Level 2: BYOVD — Bring Your Own Vulnerable Driver
BYOVD carga un driver legítimo pero vulnerable con firma válida para obtener acceso al kernel, donde la mayoría de EDRs tienen menor visibilidad granular.
kdmapper utiliza el driver iqvw64e.sys (Intel Ethernet, firma válida) para mapear un driver arbitrario en kernel. Una vez cargado, el driver puede:
- Mapear memoria arbitraria sin restricciones
- Desactivar callbacks del EDR en kernel (
ObRegisterCallbacks,PsSetCreateProcessNotifyRoutine) - Leer/escribir en el espacio de memoria de procesos kernel
Ejemplo: Desactivar callbacks de proceso
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
UNREFERENCED_PARAMETER(pRegistryPath);
// Enumerar y eliminar callbacks de ObRegisterCallbacks
// Esto deja ciego al EDR a nivel de notificaciones de proceso
return STATUS_SUCCESS;
}
Protección: Microsoft mantiene una blocklist de drivers vulnerables que Windows carga automáticamente. Los operadores deben verificar que el driver no esté en la blocklist de la versión target y considerar que Microsoft puede revocar firmas WHQL.
Level 3: Direct Kernel Object Manipulation (DKOM)
DKOM manipula directamente las estructuras de datos del kernel para ocultar procesos, threads y conexiones de red.
Ocultación de procesos
Cada proceso tiene una estructura EPROCESS con un ActiveProcessLinks doubly-linked list. DKOM elimina el proceso de esta lista:
PEPROCESS targetProcess;
PsLookupProcessByProcessId(targetPid, &targetProcess);
PLIST_ENTRY currentEntry = &targetProcess->ActiveProcessLinks;
currentEntry->Blink->Flink = currentEntry->Flink;
currentEntry->Flink->Blink = currentEntry->Blink;
ObDereferenceObject(targetProcess);
Aunque el proceso no aparece en pslist, técnicas de escaneo basadas en pools de memoria (psscan en Volatility) aún lo pueden encontrar.
Ofuscación de memoria y tráfico C2
Ofuscación de memoria
- XOR/RC4 de payloads: el shellcode permanece cifrado y se descifra solo en runtime
- Sleep masking: durante inactivity, el beacon cifra su imagen en memoria y la descifra al ejecutar
- Stack strings: las cadenas se construyen char por char en el stack, evitando detección por patrones estáticos
Ofuscación de tráfico C2
| Técnica | Descripción | Herramientas |
|---|---|---|
| Malleable C2 profiles | Perfiles HTTP que imitan tráfico legítimo | Cobalt Strike |
| Domain fronting | Usa CDN legítimos para enmascarar el C2 | Covenant, Sliver |
| DNS over HTTPS | Canaliza C2 por DNS cifrado | Brute Ratel, Nighthawk |
| Steganography | Oculta payloads en imágenes PNG/JPG | Custom tooling |
Perspectiva defensiva
Los EDR modernos han evolucionado para contrarrestar estas técnicas:
- Module stitching: verifican que cada
syscallapunte a un stub válido enntdll.dll - Stack trace validation: inspeccionan la pila para detectar retornos desde direcciones fuera de módulos conocidos
- HVCI: el hypervisor fuerza integridad de código en kernel, impidiendo ejecución de código no firmado
- Cross-view comparison: comparan estructuras del kernel con datos obtenidos por múltiples fuentes para detectar inconsistencias
- ETW integrity monitoring: monitorean si providers de ETW han sido deshabilitados o parcheados
El principio fundamental: la detección efectiva depende de correlación de telemetría desde múltiples capas. Ninguna técnica de evasión es perfecta — cada una introduce alguna anomalía que un defensor sofisticado puede correlacionar.
Conclusiones
La evasión de EDR es una disciplina técnica que requiere comprensión profunda de la arquitectura de Windows y de las implementaciones específicas de cada solución. Para los equipos de Red Team, estas técnicas evalúan realísticamente la postura de seguridad de una organización. Para los defensores, entender estas tácticas es esencial para construir detecciones resilientes y estrategias de defensa en profundidad.
Aviso legal: Este artículo es exclusivamente educativo y está dirigido a profesionales de ciberseguridad que realizan actividades de Red Team autorizadas. La aplicación de estas técnicas sin autorización explícita es ilegal en la mayoría de jurisdicciones. En CyberFlows promovemos la investigación defensiva y el uso responsable de estas técnicas en entornos controlados.
Artículos Relacionados
- Introducción al Ethical Hacking: Fundamentos y Marco Legal — Conoce las metodologías, el marco legal y las fases de una prueba de penetración profesional.
- Incident Response y DFIR: Metodología NIST para Responder a Ciberataques — Aprende a detectar, contener y erradicar amenazas que evaden los controles de endpoint.
- Threat Hunting y Blue Team: La Pirámide del Dolor — Entiende cómo los defensores cazan activamente amenazas que logran evadir EDR y otras herramientas de detección.
- Tipos de Malware: Guía Completa con Ejemplos Reales — Ransomware, troyanos, gusanos, spyware, rootkits, fileless y wipers