Fuzzing avec American Fuzzy Loop – Quickstart

Le codage sécurisé est une pratique très difficile. Habituellement, lorsque les programmeurs développent des logiciels, leur objectif est de faire fonctionner le logiciel plutôt que de le casser. Dans ce processus, des vulnérabilités peuvent se développer dans les cas où une fonction héritée a été utilisée au lieu d’une fonction plus sûre. Par conséquent, les logiciels hérités sont particulièrement vulnérables.

Le C est l’un de ces langages qui est intrinsèquement très polyvalent et puissant, mais il présente un inconvénient critique – la sécurité des logiciels basés sur le C dépend des connaissances du programmeur. Cela signifie que si le programmeur est bien au courant du codage sécurisé, son logiciel sera également sécurisé. D’autre part, et cela forme le gros morceau, si le programmeur n’est pas assez sophistiqué, il y aura des failles dans son logiciel conduisant finalement à un exploit.

Pour les personnes comme moi, qui connaissent la programmation mais sont nouvelles dans le secteur de la sécurité, il est très important d’étudier le code vulnérable et de comprendre les conséquences possibles. Cela permet d’affiner les compétences de codage et de développer une attitude d’attaquant PENDANT la phase de codage plutôt qu’APRÈS avoir codé l’ensemble du logiciel.

En toute honnêteté, il est assez lourd d’étudier le code source complet d’une application lorsqu’on recherche des vulnérabilités comme les débordements de mémoire tampon. Bien que cette méthode ait ses propres mérites, ce n’est pas la méthode la plus facile pour trouver des vulnérabilités simples qui peuvent être critiques. De telles vulnérabilités doivent être immédiatement résolues et la façon la plus simple de les trouver est de recourir à une technique appelée Fuzzing.

Le Fuzzing est une technique permettant de trouver des vulnérabilités « faciles » dans le code en envoyant des données générées « aléatoirement » à un exécutable. En général, il existe trois types de fuzzers :

  1. Mutation : Un type de fuzzing « muet » où des échantillons d’entrée malformés sont générés et fournis à l’exécutable. Cette entrée peut ou non être conforme au type d’entrée attendu par l’application, la probabilité de trouver de vrais bogues n’est donc pas élevée.
  2. Génération : Un type de fuzzing  » intelligent  » qui nécessite certaines données de test initiales à partir desquelles l’algorithme du fuzzer peut générer une entrée malformée à partir de zéro. Ce type de fuzzing est meilleur que le dumb fuzzing dans de nombreux cas car le programme reçoit l’entrée qu’il attend.
  3. Évolutionnaire : Ce type de fuzzers utilise le retour d’information de chaque  » fuzz  » pour apprendre au fil du temps le format de l’entrée.

Dans ce billet, nous allons nous intéresser au fuzzing avec American Fuzzy Loop (AFL). C’est un type de fuzzer évolutif qui est adapté pour fuzzer des programmes qui prennent des entrées depuis STDIN ou un fichier.

.

Il existe une foule de fuzzers dans la nature, notamment Peach et syzkaller. Alors, pourquoi AFL ?

  1. Le cas d’utilisation. C’est le point le plus important à considérer. Mon cas d’utilisation était de fuzzer une application qui prend l’entrée d’un fichier. Il est important de noter que l’AFL n’a pas la capacité de fuzzer sur des réseaux.
  2. Il est simple à installer.
  3. L’interface utilisateur de l’AFL emballe une tonne d’informations, y compris des statistiques en temps réel du processus de fuzzing.

Configuration de l’AFL

La configuration de l’AFL est facile et je vous ai facilité la tâche en écrivant un script shell simple (mais grossier) qui l’installera pour vous ! Exécutez le script avec vos privilèges d’utilisateur et il installera toutes les dépendances, AFL et les outils connexes. Le script shell peut être trouvé ici : https://github.com/nikhilh-20/enpm691_project/blob/master/install_afl.sh

Choisir l’application à fuzzer

Dans ce post, nous ne nous intéresserons qu’au fuzzing des applications dont nous avons le code source. En effet, l’AFL instrumente le code source pour surveiller l’exécution, les erreurs et autres trucs liés aux performances. Il est également possible de fuzzer directement un exécutable mais cela est expérimental et sort du cadre de ce post (conseil : cela nécessite QEMU).

Choisissez n’importe quel système open source de GitHub pour le fuzzing. Plus votre choix est connu, moins il y aura de vulnérabilités. D’autres personnes recherchent aussi des bogues ! Une méthode simple que j’utilise pour trouver du code vulnérable est d’utiliser GitHub Search. Voici ce que je fais :

  1. Recherchez une fonction vulnérable, disons strcpy.
  2. Les résultats se compteront par millions. Allez dans la catégorie commits des résultats. C’est là que vous trouverez les dépôts où strcpy a été utilisé (ou peut-être supprimé). Ces dépôts sont un bon point de départ pour commencer le fuzzing.

Instrumenter l’application

Pour des raisons de confidentialité, je ne peux pas divulguer le dépôt que j’utilise.

Clonez le dépôt git.

nikhilh@ubuntu:~$ git clone https://github.com/vuln ; cd vuln

Définissez une variable d’environnement, AFL_HARDEN=1. Cela active certaines options de durcissement du code dans AFL lors de la compilation, ce qui facilite la détection des bugs de corruption de la mémoire.

nikhilh@ubuntu:~/vuln$ export AFL_HARDEN=1

Définir certains drapeaux de compilateur, afin que l’application soit compilée d’une manière qui nous permette de trouver (et d’exploiter) facilement les vulnérabilités. Idéalement, nous utiliserions des variables d’environnement pour définir ce dont nous avons besoin, mais il y a pas mal de personnalisation. donc nous allons directement éditer le Makefile.

S’assurer que le compilateur utilisé est afl-gcc ou afl-clang au lieu de gcc et clang respectivement. C’est ce qui permet à AFL d’instrumenter le code source.

Ajouter des flags de compilation :

-fno-stack-protector désactive le protecteur de pile qui nous permettra d’exploiter les buffer overflows.

-m32 n’est nécessaire que si vous utilisez une machine 32 bits, sinon non.

Une fois que vous avez terminé ces modifications, il est temps de compiler l’application. Exécutez make. Lorsque vous le faites, vous DEVEZ voir des déclarations telles que celles-ci dans le journal :

Instrumenté 123 emplacements (32-bit, hardened-mode, ratio 100%).

Si vous ne voyez pas de telles déclarations, cela signifie que l’AFL n’a pas activé le code de l’application pour le fuzzing. En d’autres termes, il n’a pas instrumenté le code source avec succès.

Echantillons de test

AFL est un type de fuzzer évolutif. Cela signifie que, comme les fuzzers basés sur la génération, il a également besoin de données de test initiales pour comprendre quel type de données l’application cible attend. Lorsque l’on cible des systèmes open source, cela est facile à trouver. Il suffit de regarder dans leur répertoire de test et vous trouverez toutes les données de test dont vous avez besoin.

nikhilh@ubuntu:~/vuln$ mkdir afl_in afl_out

nikhilh@ubuntu:~/vuln$ cp test/* afl_in/

Le fuzzing commence

Maintenant que nous avons nos échantillons de test, nous sommes prêts à fuzzer !

Oh, attendez… nous devons aussi changer où vont les notifications de crash de l’application. By default, when an application crashes, the core dump (basically, the contents of RAM are stored in a file to help in debugging) notification is sent to the system’s core handler. We don’t want this. Why? By the time this notification reaches AFL, it’ll be classified as a timeout rather than a crash.

nikhilh@ubuntu:~/vuln$ sudo su

password for nikhilh:

root@ubuntu:/home/nikhilh/vuln# echo core > /proc/sys/kernel/core_pattern

root@ubuntu:/home/nikhilh/vuln# exit

NOW, we are ready to fuzz!

nikhilh@ubuntu:~/vuln$ afl-fuzz -i afl_in -o afl_out -S slaveX — ./vuln @@

Command line flags used:

  1. -i — This marks the test input directory. C’est là que nous avons stocké les données de test initiales.
  2. -o- C’est le répertoire où l’AFL écrit les informations utiles concernant les crashs, les hangs, etc.
  3. S – C’est le mode esclave. Fondamentalement, l’AFL va tweaker aléatoirement l’entrée causant un fuzzing non déterministe.
  4. L’option -M est le mode Maître qui est le fuzzing déterministe, ce qui signifie essentiellement que chaque bit de l’entrée est modifié d’une manière ou d’une autre. (C’est lent ! … Évidemment.)
  5. @@ – C’est la position où se trouvera le fichier de test en entrée. AFL le substitue pour vous automatiquement. Si votre exécutable prend l’entrée à partir de STDIN, alors ce n’est pas nécessaire.

Résultats du fuzzing

Cela va prendre un certain temps à montrer. Bien souvent, les gens fuzzent pendant plus de 24 heures (et peuvent se retrouver avec rien). Dans mon cas, je pense que l’application était un peu trop vulnérable, j’ai donc eu 516 crashs uniques dans l’heure. Cependant, cela ne signifie pas qu’il y a 516 vulnérabilités !

Vous pouvez quitter la session de fuzzing avec un Ctrl-C.

Analysis Phase

Now that we have results, we need to analyze them to see which ones are exploitable. To this end, we will use one of AFL’s utilities called afl-collect. This will have been installed through the installation script as well.

nikhilh@ubuntu:~/afl-utils$ afl-collect -d crashes.db -e gdb_script -r -rr ~/vuln/afl_out/slaveX ./output_dir — ~/vuln/vuln

To understand what each command line flag does, refer to its help section.

nikhilh@ubuntu:~/afl-utils$ afl-collect — help

If you see lines such as these in the output, celebrate! You’ve found something interesting to try and exploit.

*** GDB+EXPLOITABLE SCRIPT OUTPUT ***

slaveX:id:000000,sig:11,src:000000,op:havoc,rep:2……………:EXPLOITABLE

slaveX:id:000046,sig:11,src:000004,op:havoc,rep:4……………:EXPLOITABLE

AFL vous montrera quelle entrée a provoqué le crash de l’application. Dans ce cas, le fichier : id:000046,sig:11,src:000004,op:havoc,rep:4 a provoqué un StackBufferOverflow dans l’application. De tels fichiers peuvent être trouvés sous ../afl_out-slaveX/crashes/

Fait!

C’est tout pour un démarrage rapide dans le fuzzing ! Le processus est vraiment simple et très pratique puisque tout est automatisé. L’étape suivante serait d’analyser pourquoi l’entrée a provoqué un Buffer Overflow et de chercher un moyen de l’exploiter. N’oubliez pas que toutes les vulnérabilités ne peuvent pas conduire à un exploit.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.