fuzzing partie 1

Fuzzing Partie 1 : La théorie

Fuzzing

Tout d'abord, qu'est-ce que le fuzz exactement ? Lorsque nous testons fuzz un programme ou une fonction qui reçoit une entrée (tout type d'entrée), nous essayons différentes combinaisons d'entrée jusqu'à ce que nous obtenions un coup de cœur ou un autre résultat souhaité (fréquemment des fuites de mémoire). Lorsqu'un programme ne nettoie pas correctement son entrée, une entrée mal formée ou incorrecte peut provenir de l'utilisateur, ce qui peut provoquer des plantages et un comportement étrange ou indéfini. Un bon exemple de fuzzing dont vous avez probablement déjà entendu parler est dégoûtant, lorsque nous essayons différentes URL, en commençant par les plus courantes jusqu'à ce que nous trouvions des répertoires légitimes sur un site Web. Des outils comme fuff y faire un excellent travail.

Le fuzzing est particulièrement utile dans les programmes écrits dans des langages de bas niveau ou plus anciens, comme Basic, Assembly, C/C++, etc., qui n'empêchent pas le programmeur d'écrire des erreurs comme un transtypage incorrect, l'utilisation après la libération [d'un pointeur], ou un débordement de mémoire (tampon, pile, entier), qui n'entraînent normalement pas nécessairement des plantages à l'exécution ou des erreurs de compilation et pourraient être exploités par un attaquant astucieux. Habituellement, lorsque le terme "fuzzing" est mentionné, il fait référence à ce type de fuzzing.

Types de fuzz

Il existe trois approches du fuzzing que nous devrions énumérer.

Fuzzing en boîte blanche

Par "whitebox fuzzing", nous nous référons à un type de fuzzing dans lequel le fuzzer tente d'analyser la structure interne du programme afin de suivre et de maximiser la couverture du code. La couverture du code fait référence à la proportion de succursales que nous avons atteintes dans notre code ; chaque fois qu'une instruction conditionnelle comme if or tout en est exécuté, le code se divise en une branche où l'instruction est vraie et une autre où elle est fausse. La logique est que s'il y a une erreur cachée dans une branche, nous devons d'abord nous assurer que nous atteignons cette branche en premier lieu. Pour effectuer ce type d'analyse, le programme doit être instrumenté lors de la compilation.

L'instrumentation fonctionne en appelant une fonction spéciale chaque fois qu'une branche est exécutée, qui l'enregistre comme ayant été exécutée, parfois même à la résolution de quelle ligne, afin que la couverture puisse être suivie. L'instrumentation pourrait également aider à trouver des bogues. Par exemple, Address Sanitation (ASan) aide à détecter les fuites de mémoire qui ne provoquent normalement pas de plantage. Étant donné que l'analyse structurelle complète nécessite beaucoup de ressources, le fuzzing de la boîte blanche est lent, mais beaucoup plus efficace pour trouver les bogues et les vulnérabilités, en particulier ceux cachés profondément dans le programme. Bien sûr, pour effectuer une instrumentation poussée, il faut avoir accès au code source, qui n'est pas toujours disponible, surtout en tant qu'attaquant.

Fuzzing de boîte noire

Dans cette approche, en revanche, nous ne nous soucions pas de la structure du programme et le traitons comme une boîte noire. Pourtant, nous pouvons utiliser la sortie du programme pour essayer de comprendre ce qui se passe à l'intérieur et ainsi créer une entrée plus intelligente et plus efficace. Étant donné que nous évitons les frais généraux associés au fuzzing en boîte blanche, le fuzzing en boîte noire est beaucoup plus rapide mais moins efficace pour trouver les erreurs et les vulnérabilités.

Fuzzing en boîte grise

Cette approche tente de trouver un équilibre entre les avantages et les inconvénients respectifs des approches susmentionnées ; il utilise une instrumentation légère pendant la compilation au lieu d'une analyse complète afin de calculer la couverture du code et de suivre l'état du programme. Les fuzzers les plus populaires aujourd'hui, comme AFL, HongFuzz et LibFuzzer sont des fuzzers graybox. Pour exécuter un tel fuzzing, le code source est généralement nécessaire, mais il existe des solutions de contournement si seul un exécutable est disponible, mais elles nuisent gravement aux performances. Dans cet article, je n'aborderai pas ces techniques.

Techniques de Fuzzing

Comme dans le dirbusting, le fuzzing n'a pas besoin d'être fait en utilisant la force brute, c'est-à-dire en essayant une entrée aléatoire. En effet, la force brute n'est pas une bonne technique de fuzzing et cela se fait rarement. Heureusement, il existe un certain nombre de techniques plus intelligentes qui conduisent à de meilleurs résultats en un temps et des ressources raisonnables. Ici, j'aborderai deux d'entre eux :

Fuzzing basé sur les mutations

Cette technique est basée sur le fuzzing greybox. Nous commençons avec des entrées légitimes bien formées et appliquons quelques types de mutations dessus. À l'aide d'instruments, nous essayons de prendre les mutations les plus réussies, qui révèlent de nouvelles branches, et de les utiliser comme germe pour la génération suivante. Ce processus rappelle quelque peu la sélection naturelle darwinienne que nous voyons dans la nature. Nous avons également l'effet secondaire utile que les mutations filtrées par le programme deviennent inutilisables et il ne nous reste plus qu'une entrée de travail.

L'outil le plus connu (et sans doute le fuzzer le plus connu en général) est AFL, ou American Fuzzy Lop.

L'AFL utilise trois types de mutations de base :

  • Suppression d'un bit aléatoire.
  • Insertion d'un bit aléatoire.
  • Basculement d'un bit aléatoire (de 0 à 1 ou inversement)

L'AFL ne travaille pas avec une seule mutation à la fois, mais répartit ses ressources entre deux d'entre elles et préfère celles qui en ont le plus énergie. L'énergie de chaque mutation est basée sur les branches du code qu'elle exécute - les branches plus rares, que la plupart des entrées passent, se voient attribuer plus d'énergie. Chaque branche a un hachage unique, ce qui permet de suivre le nombre d'exécutions. Il est important de mentionner que l'entrée initiale dans cette technique peut provenir d'un dictionnaire contenant plus d'une variante.

Fuzzing grammatical

Vous savez probablement que de nombreux programmes ne peuvent pas recevoir d'entrées non structurées, mais ont des règles spécifiques sur la façon dont les entrées peuvent être formées. Ces règles sont appelées la syntaxe de l'entrée, car elles sont similaires aux règles syntaxiques des langues parlées. Afin de tester fuzz de tels programmes d'une manière qui couvre la plupart des options syntaxiques, le fuzzer doit être conscient de la syntaxe, sinon, les entrées produites à l'aide de mutations simples seraient filtrées et rejetées. De tels fuzzers existent déjà, et peuvent fuzzer JavaScript, WASM, URL et autres. mais au moment de la rédaction de cet article, la plupart d'entre eux sont tous expérimentaux et lents, et pour autant que je sache, tous sont écrits en Python, ce qui est idéal pour le prototypage et les démos, mais pas tellement pour les fuzzers de production optimisés. Par conséquent, ils sont rarement utilisés.

Suivant – Partie 2

Dans le chapitre suivant "Fuzzing avec AFL“, je montre comment fuzzer un programme simple utilisant AFL pour trouver des plantages qui pourraient être dangereux dans le monde réel.

Passer au contenu