PENTESTING WINDOWS: HOOKS & MONITORING (PART 2)

|TIEMPO_LECTURA: 2 MIN

En la primera parte, vimos cómo los Direct Syscalls nos permiten evadir la mayoría de las telemetrías básicas de un EDR. Hoy profundizaremos en cómo las soluciones de seguridad monitorean realmente nuestro proceso: el Inline Hooking.

¿Qué es un Inline Hook?

Los antivirus modernos y EDRs inyectan una DLL en cada proceso que se inicia. Esta DLL "parchea" el inicio de funciones críticas en `ntdll.dll`.

Supongamos que queremos llamar a `NtOpenProcess`. La implementación original se vería así en ensamblador:

```asm mov r10, rcx mov eax, 0x26 ; ID de NtOpenProcess syscall ret ```

Sin embargo, si hay un EDR presente, los primeros 5-10 bytes suelen ser reemplazados por un salto (`JMP`) a la memoria de la DLL del EDR.

Identificando Hooks Manualmente

Podemos verificar si una función está hookeada comparando los primeros bytes de la función en memoria contra los bytes esperados.

Aquí un ejemplo en Rust para verificar los primeros bytes:

```rust use std::ffi::CString; use windows_sys::Win32::System::LibraryLoader::GetModuleHandleA; use windows_sys::Win32::System::LibraryLoader::GetProcAddress;

fn check_hook(func_name: &str) { let ntdll = unsafe { GetModuleHandleA(b"ntdll.dll\0".as_ptr()) }; let proc_addr = unsafe { GetProcAddress(ntdll, CString::new(func_name).unwrap().as_ptr()) };

textCopiar
if proc_addr.is_null() { return; }

let first_byte = unsafe { *(proc_addr as *const u8) };

// 0xE9 es el opcode para JMP (Jump) en x64
if first_byte == 0xE9 {
    println!("[!] Función {} está hookeada por un EDR.", func_name);
} else {
    println!("[+] Función {} parece limpia.", func_name);
}

}

fn main() { check_hook("NtOpenProcess"); } ```

Técnicas de Des-hookeo (Un-hooking)

Existen varias formas de "limpiar" estos ganchos:

  1. Recargar ntdll.dll: Leer ntdll del disco y sobrescribir la sección `.text` en memoria con la versión limpia.
  2. Direct Syscalls: Como vimos en la parte 1, simplemente ejecutando la instrucción syscall nosotros mismos evadimos el JMP.

En la próxima entrega, implementaremos un cargador que automatiza la limpieza de ntdll antes de ejecutar nuestro payload.