
Un fuzzer basé sur la grammaire avec retour d'information
Nautilus est un fuzzer guidé par la couverture et basé sur une grammaire. Vous pouvez l'utiliser pour améliorer votre couverture de test et découvrir plus de bogues. En spécifiant la grammaire d'entrées semi-valides, Nautilus est capable d'effectuer des mutations complexes et de découvrir des cas de test plus intéressants. De nombreuses idées derrière ce fuzzer sont documentées dans un article publié à NDSS 2019.
La version 2.0 a apporté de nombreuses améliorations à ce prototype précoce et est désormais 100 % compatible avec AFL++. Outre les améliorations générales de convivialité, la version 2.0 inclut de nombreuses nouvelles fonctionnalités brillantes :
Vous spécifiez une grammaire à l'aide de règles comme EXPR -> EXPR + EXPR ou EXPR -> NUM et NUM -> 1. À partir de ces règles, le fuzzer construit un arbre. Cette représentation interne permet d'appliquer des mutations bien plus complexes que des octets bruts. Cet arbre est ensuite transformé en une entrée réelle pour l'application cible. Dans les grammaires context-free normales, ce processus est simple : toutes les feuilles sont concaténées. L'arbre de gauche dans l'exemple ci-dessous produirait l'entrée a=1+2 et celui de droite a=1+1+1+2. Pour augmenter l'expressivité de vos grammaires, avec Nautilus vous pouvez fournir des fonctions Python pour le processus de désérialisation afin de permettre des spécifications bien plus complexes.
# cloner le dépôt git
git clone '[email protected]:nautilus-fuzz/nautilus.git'
cd nautilus
/path/to/AFLplusplus/afl-clang-fast test.c -o test # afl-clang-fast tel que fourni par AFL
# tous les arguments peuvent également être définis dans le fichier config.ron
cargo run --release -- -g grammars/grammar_py_example.py -o /tmp/workdir -- ./test @@
# ou si vous voulez utiliser le mode QEMU :
cargo run /path/to/AFLplusplus/afl-qemu-trace -- ./test_bin @@
Ici, nous utilisons Python pour générer une grammaire pour des entrées valides de type XML. Remarquez l'utilisation d'une règle de script pour garantir la correspondance des balises d'ouverture et de fermeture.
#ctx.rule(NONTERM: string, RHS: string|bytes) ajoute une règle NONTERM->RHS. Nous pouvons utiliser {NONTERM} dans RHS pour demander une récursion.
ctx.rule("START","<document>{XML_CONTENT}</document>")
ctx.rule("XML_CONTENT","{XML}{XML_CONTENT}")
ctx.rule("XML_CONTENT","")
#ctx.script(NONTERM:string, RHS: [string], func) ajoute une règle NONTERM->func(*RHS).
# Contrairement à `rule` normale, RHS est un tableau de non-terminaux.
# C'est à la fonction de combiner les valeurs retournées pour les NONTERMINAUX avec tout contenu fixe utilisé.
ctx.script("XML",["TAG","ATTR","XML_CONTENT"], lambda tag,attr,body: b"<%s %s>%s</%s>"%(tag,attr,body,tag) )
ctx.rule("ATTR","foo=bar")
ctx.rule("TAG","some_tag")
ctx.rule("TAG","other_tag")
# Parfois, nous ne voulons pas explorer plus en détail l'ensemble des entrées possibles. Par exemple, si nous fuzzons un interpréteur
# de scripts, nous ne voulons pas passer du temps à fuzzer tous les différents noms de variables. Dans ce cas, nous pouvons utiliser
# des terminaux regex. Les terminaux regex ne sont mutés que pendant la génération, mais pas pendant les étapes de mutation normales,
# ce qui permet d'économiser beaucoup de temps.
# Le fuzzer explore toujours différentes valeurs pour le regex, mais il ne pourra pas apprendre de valeurs intéressantes de manière
# incrémentale. Utilisez ceci lorsque l'exploration incrémentale ferait très probablement perdre du temps.
ctx.regex("TAG","[a-z]+")
Pour tester vos grammaires, vous pouvez utiliser le générateur :
$ cargo run --bin generator -- -g grammars/grammar_py_exmaple.py -t 100
<document><some_tag foo=bar><other_tag foo=bar><other_tag foo=bar><some_tag foo=bar></some_tag></other_tag><some_tag foo=bar><other_tag foo=bar></other_tag></some_tag><other_tag foo=bar></other_tag><some_tag foo=bar></some_tag></other_tag><other_tag foo=bar></other_tag><some_tag foo=bar></some_tag></some_tag></document>
Vous pouvez également utiliser Nautilus en combinaison avec AFL. Pointez simplement AFL avec -o vers le même répertoire de travail, et AFL se synchronisera avec Nautilus. Notez que cela est unidirectionnel. AFL importe les entrées de Nautilus, mais pas l'inverse.
# Terminal/Écran 1
./afl-fuzz -Safl -i /tmp/seeds -o /tmp/workdir/ ./test @@
# Terminal/Écran 2
cargo run --release -- -o /tmp/workdir -- ./test @@