Introduction au fuzzing avec Fusil the fuzzer

Après avoir entendu parler un million de fois de l’efficacité du fuzzing sur la recherche de vulnérabilités, j’ai finalement décidé de m’intéresser un minimum au sujet. Oui, je sais, ça s’appelle être long à la détente. Pour rappel (ou pas), le fuzzing est une méthode de test logiciel qui permet de traquer les bugs et éventuellement les vulnérabilités. Ça consiste à injecter des données aléatoires (beaucoup !) en entrée et observer le comportement du programme ciblé. (Si il plante, c’est tout benef’ !). Après ça, on jette un oeil aux données passées en entrée et on essaie de comprendre ce qui a causé le problème. Bref, ça permet d’auditer un logiciel, même si on n’a pas sa source. Ce qui est plutôt intéressant.
Pour cet article, j’ai décidé d’utiliser Fusil: une bibliothèque Python écrite par Victor Stinner. Fusil permet d’écrire des fuzzers adaptés aux logiciels à tester.
Je débute complètement avec Fusil, donc cet article sera une vraie introduction. Je ferai peut-être des articles plus poussés dessus à l’avenir. En attendant, ce qui m’interesse, c’est d’être capable d’écrire un petit fuzzer fonctionnel.

Installation

Fusil est disponible dans les dépôts. J’ai préféré télécharger les sources: http://pypi.python.org/packages/source/f/fusil/fusil-1.4.tar.gz

On se rend dans le répertoire et on l’extrait:

tar xvzf fusil-1.4.tar.gz
cd fusil-1.4

 

Il nous faut les dépendances, on les installe si ce n’est pas déjà fait:

sudo apt-get install python python-ptrace

 

On lance ensuite l’installation:

sudo python setup.py install

 

Afin de faire fonctionner Fusil, il faut créer l’utilisateur « fusil » appartenant au groupe « fusil ».
Nous utiliserons cet utilisateur pour lancer nos scripts.

Création de l’utilisateur:

sudo adduser fusil

Puis, on remplie les champs demandés

Le groupe « fusil » a été créé dans le même temps et affecté à l’utilisateur.
Le répertoire « fusil » a été créé dans /home/
Vu qu’on va lancer notre script sous l’utilisateur « fusil » et que des répertoires et fichiers vont être créés, on va déplacer fusil dans son répertoire personnel de façon à ne pas avoir de problèmes de droits par la suite.

cd ..
sudo mv fusil-1.4/ /home/fusil/

 

On a également besoin de pouvoir utiliser sudo avec fusil.
On ajoute donc fusil au groupe sudo:

sudo adduser fusil sudo

Puis on se log sous fusil et on retourne dans le répertoire:

su fusil
cd ~/fusil-1.4/

On va maintenant pouvoir commencer à écrire le fuzzer.

 

Ecriture du fuzzer

On va commencer par créer un programme en C sur lequel lancer notre fuzzer et tenter de le faire planter.
Voilà le programme que j’ai décidé d’écrire pour ça:

#include <stdio.h>
#include <stdlib.h>
#define SIZE 256

int tab[SIZE];

void fillTab(void)
{
	int i;
	for (i = 0; i < SIZE; i++)
	{
		*(tab + i) = i;
	}

}

int main(int argc, char** argv)
{
	if (argc < 1 + 1)
	{
		printf("Usage: %s <tab index>\n", argv[0]);
		exit(1);
	}
	fillTab();
	printf("%d\n", tab[atoi(argv[1])]);

	return 0;
}
test.c

Si on code un peu en C, le problème avec ces lignes parait évident:
l’argument passé en entrée par la ligne de commande est utilisé comme indice pour accéder à l’entrée correspondante du tableau tab[] de taille 256.
Si on passe une valeur supérieure à 255, on risque de faire planter le programme.
Ce n’est cependant pas une certitude qu’il plante, surtout si la valeur de l’indice n’est pas très éloignée de 255: on peut continuer à accéder à une partie de la mémoire qui appartient à notre programme. Dans ce cas, on lit alors une valeur erronée mais le programme ne plante pas et se termine correctement. Bref: indétectable par un fuzzer qui recherche les anomalies sur la sortie standard ou sur les signaux reçus.

Exemple:

./test 100
100
./test 260
0

 

On crée notre script, donnons lui le nom de tabfuz.py
Note: il faut éviter de nommer son script « fusil.py », ça peut causer des conflits avec le nom des modules fusil et le fuzzer ne démarrera pas.

La première chose à faire est de créer une classe qui hérite de la classe Application et d’y définir une méthode « setupProject() ».
Cette méthode configure le fuzzer (lance le processus, détermine les arguments de la ligne de commande, indique les évènements à observer pour savoir si l’exécution se déroule correctement ou pas)
On appelle ensuite la méthode main() de la classe précédemment créée pour démarrer le tout.

#!/usr/bin/python
from fusil.application import Application
from fusil.process.create import ProjectProcess
from fusil.process.create import CreateProcess
from fusil.process.watch import WatchProcess

class Fuzzer(Application):
        def setupProject(self):
                process = ProjectProcess(self.project, ['./test', '50'])
		WatchProcess(process)
                WatchStdout(process)

if __name__ == "__main__":
        Fuzzer().main()
tabfuz.py

WatchProcess() indique au fuzzer de surveiller la valeur de retour du processus (Généralement: 0 si tout s’est bien passé et autre chose sinon, par exemple 139 en cas de violation de mémoire)
WatchStdout() surveille la sortie standard et détecte les anomalies.
ProjectProcess() démarre ainsi le processus ./test avec « 50 » en argument

On démarre notre fuzzer:

sudo python tabfuz.py --sessions=1

 

Le paramètre –sessions=1 indique au fuzzer de démarrer une seule session de fuzzing: le processus sera exécuté une seule fois.
L’exécuter plusieurs fois n’a aucun intérêt avec ce code puisque l’argument de la ligne de commande est pour l’instant fixe. Il ne change pas d’une exécution à une autre et vaut tout le temps « 50 ».
Si on ne précise pas le paramètre –sessions lors du lancement du fuzzer, celui-ci va démarrer autant de sessions que possible jusqu’à ce que le programme ciblé plante.
Bref, ce fuzzer est un début mais n’a pas trop d’intérêt. Ce qu’on veut, c’est pouvoir envoyer un grand nombre de valeurs différentes sur le paramètre de la ligne de commande.
Pour ça, on va définir une classe qui hérite de ProjectProcess()
Cette classe a deux caractéristiques essentielles:
– Un constructeur qui met en place les arguments de la ligne de commande (On peut y mettre n’importe quoi en fait, ça n’a pas d’importance puisque ces arguments vont être écrasés par la méthode suivante mais il faut qu’il y soit)
– Une méthode on_session_start(): elle est appelée à chaque fois que l’évènement « session_start » survient, c’est à dire à chaque démarrage d’une nouvelle session. Cette méthode doit ainsi recréer les arguments passés au programme et crée le processus.

Enfin, on va écrire deux autres méthodes pour générer aléatoirement les données de la ligne de commande.
Voilà ce que ça donne:

 

!/usr/bin/python
from fusil.application import Application
from fusil.process.create import ProjectProcess
from fusil.process.create import CreateProcess
from fusil.process.watch import WatchProcess
from fusil.process.stdout import WatchStdout
import random

class printTabProcess(CreateProcess):

        def __init__(self, project):
                CreateProcess.__init__(self, project, ["./blabla"])
                self.num = '0123456789'

        def chooseNumber(self, maxlength):
                number = ''
                i = random.choice(range(1, maxlength))
                while i > 0:
                        number += random.choice(self.num)
                        i = i - 1
                return number

        def createCmdLine(self):
                arguments = ["./test"]
                arguments.append(self.chooseNumber(5))
                return arguments

        def on_session_start(self):
                self.cmdline.arguments = self.createCmdLine()
                self.createProcess()


class Fuzzer(Application):
        def setupProject(self):
                process = printTabProcess(self.project)
                WatchProcess(process)
                WatchStdout(process)

if __name__ == "__main__":
        Fuzzer().main()
tabfuz.py

Ici la méthode chooseNumber() génère un nombre dont le nombre de chiffres est choisi aléatoirement entre 1 et maxlength – 1
C’est à dire une valeur entre 0 et 10^(maxlength – 1) – 1.
On appelle chooseNumber avec 5 en argument. On aura donc en retour un nombre choisi aléatoirement  entre 0 et 9999.

Démarrons notre fuzzer:

screenshot1

La session 8 a fait planter notre programme. Le fuzzer s’est donc arrêté.
Jetons un oeil au résultat:
(répertoire fusil-X avec X le nombre de fois où on a lancé le fuzzer dans le répertoire courant)
On découvre un répertoire « invalid-mem-access-XXX » avec XXX l’adresse qu’a tenté de lire notre programme.
Jetons un oeil au fichier session.log créé à l’intérieur du répertoire:

screenshot2

On a un ensemble d’informations utiles notamment l’entrée qui a provoqué l’erreur (4954) et le signal envoyé au processus (SIGSEV: c’est à dire violation de mémoire)
A partir de ce point, et dans une vraie séance de fuzzing, on va se mettre à la recherche du problème qui a causé le crash du programme. (Si on a accès au code source c’est encore mieux).

 

Pour aller plus loin

Lors d’une réelle séance, il faut déterminer les points d’entrée du programme et créer un fuzzer adapté.
Parmi ces points d’entrée, on trouve notamment:
– les arguments passés à la ligne de commande (ce qui a été illustré dans cet article)
– les variables d’environnement
– les fichiers chargés et interprétés par le programme cible
– les données reçues à travers le réseau

La doc de Fusil conseille d’utiliser des outils comme ltrace, strace ou netstat pour déterminer ces points d’entrée.
Fusil est capable de gérer toutes ces entrées.
La doc de Fusil est par ici: http://fusil.readthedocs.org/en/latest/

J’ai parlé d’injection de données « aléatoires »: ce n’est pas tout à fait juste. Il faut programmer le fuzzer de façon a ce que les données injectées respectent le protocole utilisé par le programme qui les traite.
Par exemple, sur le fuzzer écrit dans cet article, on injecte des données numériques. Injecter autre chose n’a aucun intérêt puisque l’argument est passé à travers la fonction atoi() qui renvoie 0 si elle reçoit autre chose qu’une chaine de caractères dont les codes ASCII ne sont pas compris entre 48 et 57 inclus (codes ASCII de 0 à 9)
En injectant autre chose, le fuzzer perd de son efficacité. C’est évidemment le cas lorsqu’on audite un programme plus complexe: un serveur qui reçoit une requête va peut-être effectuer une vérification de la syntaxe de celle-ci avant de la traiter. Il faut dans ce cas configurer le fuzzer de manière à correctement former des requêtes qui respectent cette syntaxe dans le but de détecter des bugs qui interviendraient plus tard, par exemple dans le parser. De la même façon, il peut être intéressant de configurer le fuzzer pour qu’il envoie d’abord des requêtes correctes puis incorrectes (de manières à placer la cible dans un autre état puis d’essayer de la faire planter à cet instant). Egalement, Un programme qui traite des images s’attendra à un format particulier dont il faudra respecter les specifications lors de la génération de « fausses » images avec notre fuzzer etc..

Le fuzzing est, à priori efficace. Par contre, je ne sais pas si c’est une méthode très utilisée en entreprise dans le développement logiciel ou s’il s’agit toujours d’une technique écartée des éditeurs.. Mais c’est une véritable arme pour la découverte de vulnérabilités zero day.

Références

Article Wikipédia sur le fuzzing: https://fr.wikipedia.org/wiki/Fuzzing

La documentation de Fusil: http://fusil.readthedocs.org/en/latest/

Une liste de fuzzers connus (à creuser) : http://www.infosecinstitute.com/blog/2005/12/fuzzers-ultimate-list.html 

 

 

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *