Il Return-Oriented Programming (ROP) è una tecnica di exploit che permette di eseguire comportamenti arbitrari riutilizzando frammenti di codice già presenti nel binario o nelle librerie caricate, aggirando le protezioni che rendono lo stack non eseguibile. Introdotto formalmente da Hovav Shacham nel 2007 come generalizzazione del return-to-libc, ROP è diventato la tecnica dominante nell’exploit development moderno.
Il problema che ROP risolve
Le protezioni NX (No-eXecute) / DEP (Data Execution Prevention) marcano le pagine di dati (stack, heap) come non eseguibili. Un attaccante non può più iniettare shellcode sullo stack e saltarci sopra: il processore solleva un’eccezione.
ROP aggira questo limite osservando che il codice già presente nel processo (eseguibile, librerie di sistema) è sempre eseguibile. L’attaccante non inietta codice: orchestra codice esistente.
Gadget: l’unità elementare
Un gadget è una breve sequenza di istruzioni che termina con una istruzione RET (o equivalente: JMP [reg], CALL [reg]):
; Gadget tipico: carica un valore in eax
pop eax
ret
; Gadget aritmetico
add eax, ebx
ret
; Gadget di sistema call (x86 Linux)
int 0x80
ret
Questi gadget non sono stati scritti con intenzione dall’autore del binario: emergono naturalmente dall’analisi del codice macchina a qualsiasi offset, anche nel mezzo di istruzioni multi-byte (gadget unintended).
Funzionamento della catena ROP
L’attaccante sfrutta un buffer overflow o heap overflow per sovrascrivere il return address con l’indirizzo del primo gadget. Al momento del RET, la CPU:
- Preleva il valore in cima allo stack → indirizzo del gadget 1.
- Esegue le istruzioni del gadget 1 fino al
RET. - Il
RETpreleva il valore successivo dallo stack → indirizzo del gadget 2. - Il processo si ripete per ogni gadget nella catena.
Lo stack dell’attaccante è una sequenza di indirizzi di gadget e dati intermedi:
[indirizzo gadget 1] [dato] [indirizzo gadget 2] [dato] ... [indirizzo execve/system]
Il risultato è equivalente a eseguire un programma arbitrario composto da micro-operazioni estratte dal binario originale.
Costruzione automatica: ROPgadget e ropper
Strumenti come ROPgadget, ropper e pwntools automatizzano la ricerca dei gadget in un binario:
ROPgadget --binary /usr/bin/target --rop
L’output elenca tutti i gadget disponibili con i loro offset. In binari di grandi dimensioni (libc, kernel) esistono migliaia di gadget, sufficienti a costruire qualsiasi computazione (ROP è Turing-completo in presenza di sufficienti gadget).
ROP e ASLR
La principale difesa contro ROP è l’ASLR, che randomizza gli indirizzi del binario e delle librerie ad ogni esecuzione. Se l’attaccante non conosce gli indirizzi base, non può costruire la catena.
Le tecniche per aggirare ASLR in presenza di ROP includono:
| Tecnica | Descrizione |
|---|---|
| Info leak | Sfruttare una vulnerabilità separata (es. format string) per leggere un indirizzo dalla memoria e calcolare l’offset base. |
| Partial overwrite | Sovrascrivere solo i byte meno significativi del return address (i nibble bassi non sono randomizzati). |
| Heap spray | Riempire l’heap con la catena ROP in molte posizioni; aumenta la probabilità che un salto arbitrario la colpisca. |
| JIT spraying | Nei browser JIT, spruzzare bytecode che produce gadget ROP nel codice compilato al volo. |
Varianti avanzate
- JOP (Jump-Oriented Programming): usa gadget terminanti con
JMPinvece diRET, aggirando i controlli sul ritorno delle funzioni (shadow stack, CET). - SROP (Sigreturn-Oriented Programming): sfrutta la syscall
sigreturnper caricare un intero contesto di registri controllato dall’attaccante con un solo gadget. - BROP (Blind ROP): costruisce la catena senza accesso al binario, sfruttando solo il comportamento del server (crash vs. no-crash) per scoprire gadget.
Contromisure
| Protezione | Descrizione |
|---|---|
| ASLR | Randomizza gli indirizzi base; necessaria ma non sufficiente senza PIE. |
| PIE (Position-Independent Executable) | Randomizza anche il base address dell’eseguibile stesso. |
| Stack canary | Rileva sovrascritture del return address prima del RET; non blocca info leak o heap-based ROP. |
| Intel CET / Shadow Stack | Mantiene uno stack separato dei return address in hardware; ogni RET verifica la corrispondenza. |
| CFI (Control Flow Integrity) | Restringe i target validi di JMP/CALL/RET a quelli legittimi determinati staticamente. |
ROP rimane la pietra angolare dell’exploit development su piattaforme x86/x64 e ARM. La comprensione dei suoi meccanismi è indispensabile tanto per chi costruisce exploit quanto per chi progetta difese.