Que fait AFL++ ?
AFL++ est un fork du fuzzer AFL original qui offre de meilleures performances de fuzzing et des fonctionnalités plus avancées tout en maintenant la stabilité. Un avantage majeur par rapport à libFuzzer est qu'AFL++ prend en charge de manière stable l'exécution de campagnes de fuzzing sur plusieurs cœurs, ce qui le rend idéal pour les efforts de fuzzing à grande échelle.
Quand l'utiliser
| Fuzzer | Idéal pour | Complexité |
|---|---|---|
| AFL++ | Fuzzing multi-cœur, mutations diverses, projets matures | Moyenne |
| libFuzzer | Configuration rapide, mono-thread, harnais simples | Faible |
| LibAFL | Fuzzers personnalisés, recherche, cas d'usage avancés | Élevée |
Choisissez AFL++ quand :
- Vous avez besoin de fuzzing multi-cœur pour maximiser le débit
- Votre projet peut être compilé avec Clang ou GCC
- Vous voulez des stratégies de mutation diverses et des outils matures
- libFuzzer a atteint un plateau et vous avez besoin de plus de couverture
- Vous fuzzez des bases de code de production qui bénéficient d'une exécution parallèle
Démarrage Rapide
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Appelez votre code avec les données fournies par le fuzzer
check_buf((char*)data, size);
return 0;
}
Compilez et exécutez :
# Configuration du script wrapper AFL++ d'abord (voir Installation)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Installation
AFL++ a de nombreuses dépendances, notamment LLVM, Python et Rust. Nous recommandons d'utiliser une distribution Debian ou Ubuntu récente pour le fuzzing avec AFL++.
| Méthode | Quand l'utiliser | Compilateurs supportés |
|---|---|---|
| Dépôts Ubuntu/Debian | Ubuntu récent, fonctionnalités de base uniquement | Ubuntu 23.10 : Clang 14 & GCC 13Debian 12 : Clang 14 & GCC 12 |
| Docker (depuis Docker Hub) | Version spécifique d'AFL++, support Apple Silicon | À partir de 4.35c : Clang 19 & GCC 11 |
| Docker (depuis les sources) | Tester des fonctionnalités non publiées, appliquer des correctifs | Configurable dans le Dockerfile |
| Depuis les sources | Éviter Docker, besoin de correctifs spécifiques | Ajustable via la variable d'environnement LLVM_CONFIG |
Ubuntu/Debian
Avant d'installer afl++, vérifiez la dépendance de version de clang du paquet avec apt-cache show afl++, et installez la version correspondante de lld (par exemple, lld-17).
apt install afl++ lld-17
Docker (depuis Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker (depuis les sources)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
Depuis les sources
Consultez le Dockerfile pour les exigences de version Ubuntu et les dépendances. Définissez LLVM_CONFIG pour spécifier la version de Clang (par exemple, llvm-config-18).
Configuration du Script Wrapper
Créez un script wrapper pour exécuter AFL++ sur l'hôte ou Docker :
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -i \
--privileged \
-v ./:/src \
--rm \
--name "afl_fuzzing_$$" \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Usage: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
Les exemples ci-dessous utilisent le mode docker, à l'exception des commandes de configuration système qui doivent atteindre le noyau hôte. Remplacez par host pour exécuter l'une d'entre elles avec une installation d'AFL++ sur la machine elle-même. Le wrapper concatène tout ce qui suit l'argument de mode en une seule chaîne shell, donc les guillemets ne survivent pas : un argument contenant un espace (-x "my dict.dict") arrive divisé en mots. Renommez ces fichiers sans espaces, ou modifiez le wrapper pour cette exécution.
L'absence de -t est délibérée. docker run -ti échoue avec the input device is not a TTY chaque fois que l'entrée standard n'est pas un terminal, ce qui couvre les tâches CI et tout ce qui est piloté par un agent ou un script. afl-fuzz détecte qu'il n'y a pas de terminal et affiche des lignes d'état simples à la place de l'interface plein écran. Un programme qui nécessite un terminal, comme watch, doit être exécuté côté hôte du wrapper. $$ se développe en PID du wrapper, donc les instances parallèles obtiennent des noms de conteneurs distincts plutôt que de se heurter sur un seul afl_fuzzing. docker ps tronque la colonne COMMAND, donc toutes les lignes se ressemblent ; utilisez docker ps --no-trunc pour distinguer les instances avant d'en arrêter une.
Avertissement de Sécurité : Les scripts afl-system-config et afl-persistent-config nécessitent des privilèges root et désactivent les fonctionnalités de sécurité du système d'exploitation. Ne faites pas de fuzzing sur des systèmes de production ou votre environnement de développement. Utilisez une VM dédiée à la place.
Configuration Système
Exécutez après chaque redémarrage pour jusqu'à 15% d'exécutions supplémentaires par seconde :
./afl++ host afl-system-config
afl-system-config règle le noyau sur lequel il s'exécute, donc exécutez-le sur la machine qui héberge la campagne. ./afl++ docker afl-system-config atteint les mêmes paramètres via le conteneur --privileged du wrapper, ce qui est la seule option quand AFL++ est installé uniquement via Docker.
Pour des performances maximales, désactivez les atténuations de sécurité du noyau (nécessite le chargeur d'amorçage grub, non pris en charge dans Docker) :
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ host afl-system-config
Vérifiez avec cat /proc/cmdline - la sortie doit inclure mitigations=off.
Écrire un Harnais
Structure du Harnais
AFL++ prend en charge les harnais de style libFuzzer :
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Validez la taille d'entrée si nécessaire
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Appelez la fonction cible avec les données de fuzzing
target_function(data, size);
// 3. Retournez 0 (non nul réservé pour une utilisation future)
return 0;
}
Règles du Harnais
| À faire | À ne pas faire |
|---|---|
| Réinitialisez l'état global entre les exécutions | Dépendre de l'état des exécutions précédentes |
| Gérez les cas limites avec élégance | Quitter sur une entrée invalide |
| Gardez le harnais déterministe | Utiliser des générateurs de nombres aléatoires |
| Libérez la mémoire allouée | Créer des fuites mémoire |
| Validez les tailles d'entrée | Traiter des entrées non bornées |
Voir Aussi : Pour des techniques détaillées d'écriture de harnais, des modèles pour gérer des entrées complexes, et des stratégies avancées, consultez la compétence technique fuzz-harness-writing.
Compilation
AFL++ offre plusieurs modes de compilation avec différents compromis.
Arbre de Décision du Mode de Compilation
Choisissez votre mode de compilation :
- Mode LTO (
afl-clang-lto) : Meilleures performances et instrumentation. Essayez ceci en premier. - Mode LLVM (
afl-clang-fast) : Repport si LTO ne parvient pas à compiler. - Plugin GCC (
afl-gcc-fast) : Pour les projets nécessitant GCC.
Compilation de Base (Mode LLVM)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Compilation GCC
./afl++ docker afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Important : La version de GCC doit correspondre à la version utilisée pour compiler le plugin GCC d'AFL++.
Avec Sanitizers
./afl++ docker AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Voir Aussi : Pour une configuration détaillée des sanitizers, les problèmes courants et les drapeaux avancés, consultez les compétences techniques address-sanitizer et undefined-behavior-sanitizer.
Drapeaux de Compilation
Notez que -g n'est pas nécessaire, il est ajouté par défaut par les compilateurs AFL++.
| Drapeau | Objectif |
|---|---|
-DNO_MAIN=1 | Ignorer la fonction main lors de l'utilisation du harnais libFuzzer |
-O2 | Niveau d'optimisation de production (recommandé pour le fuzzing) |
-fsanitize=fuzzer | Activer le mode de compatibilité libFuzzer et ajouter l'environnement d'exécution du fuzzer lors de l'édition de liens de l'exécutable |
-fsanitize=fuzzer-no-link | Instrumenter sans lier l'environnement d'exécution du fuzzer (pour les bibliothèques statiques et les fichiers objets) |
Gestion du Corpus
Créer un Corpus Initial
AFL++ nécessite au moins un fichier de départ non vide :
mkdir seeds
echo "aaaa" > seeds/minimal_seed
Pour de vrais projets, rassemblez des entrées représentatives :
- Téléchargez des exemples de fichiers pour le format que vous fuzzez
- Extrayez des cas de test de la suite de tests du projet
- Utilisez des entrées valides minimales pour votre format de fichier
Minimisation du Corpus
Après une campagne, minimisez le corpus pour ne garder que la couverture unique :
./afl++ docker afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
Voir Aussi : Pour les stratégies de création de corpus, les dictionnaires et la sélection des graines, consultez la compétence technique fuzzing-corpus.
Exécution de Campagnes
Exécution de Base
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Définir des Variables d'Environnement
./afl++ docker AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
Interpréter la Sortie
AFL++ rapporte ces statistiques dans les deux cas, mais la façon dont vous les lisez dépend du mode. Le docker run -i du wrapper ne donne pas de TTY au conteneur, donc afl-fuzz abandonne l'interface plein écran et écrit des lignes d'état simples dans le journal à la place — les champs ci-dessous y apparaissent, ainsi que dans state/<instance>/fuzzer_stats. Pour obtenir l'interface interactive, exécutez le mode host dans un terminal, ou ajoutez -t au wrapper pour une exécution que vous surveillez manuellement.
| Sortie | Signification |
|---|---|
| execs/sec | Vitesse d'exécution - plus c'est élevé, mieux c'est |
| cycles done | Nombre de passages de file d'attente terminés |
| corpus count | Nombre de cas de test uniques dans la file d'attente |
| saved crashes | Nombre de crashes uniques trouvés |
| stability | % de bords stables (devrait être proche de 100%) |
Structure du Répertoire de Sortie
out/default/
├── cmdline # Comment le SUT a-t-il été invoqué ?
├── crashes/ # Entrées qui font planter le SUT
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Entrées qui bloquent le SUT
├── queue/ # Cas de test reproduisant l'état final du fuzzer
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Statistiques de la campagne
└── plot_data # Données pour le traçage
Analyser les Résultats
Affichez les statistiques de campagne en direct :
./afl++ docker afl-whatsup out
Créez des graphiques de couverture. L'image aflplusplus contient déjà gnuplot-nox ; en mode hôte, installez d'abord gnuplot avec apt install gnuplot.
./afl++ docker afl-plot out/default out_graph/
Ré-exécuter des Cas de Test
Passez l'un des noms de fichiers de out/default/crashes/ :
./afl++ docker ./fuzz out/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
Options du Fuzzer
| Option | Objectif |
|---|---|
-G 4000 | Longueur maximale de l'entrée de test (par défaut : 1 048 576 octets) |
-t 1000 | Délai d'attente en millisecondes pour chaque cas de test (par défaut : 1000 ms) |
-m 1000 | Limite mémoire en mégaoctets (par défaut : 0 = illimité) |
-x ./dict.dict | Utiliser un fichier dictionnaire pour guider les mutations |
Variables d'Environnement Qui Comptent
AFL++ a de nombreuses variables d'environnement, mais la plupart sont de niche. Voici celles qui comptent en pratique.
Toujours Définir Celles-ci
# Chaque campagne devrait utiliser tmpfs — vos SSD vous remercieront, et c'est plus rapide
AFL_TMPDIR=/dev/shm
AFL_TMPDIR est un gain de performance gratuit sans inconvénient — ne pas le définir use votre SSD et ralentit le fuzzing.
Cibles Lentes
# Accélère la calibration d'environ 2,5x — à utiliser quand les cibles sont lentes (par exemple, >10 ms/exec)
AFL_FAST_CAL=1
AFL_FAST_CAL réduit le temps de calibration avec une perte de précision négligeable. Recommandé spécifiquement pour les cibles lentes où la calibration prendrait autrement beaucoup de temps.
Campagnes Multi-Cœur
# Sur l'instance principale (-M) uniquement — nécessaire pour afl-cmin, pas pour le fuzzing lui-même
AFL_FINAL_SYNC=1
# Sur toutes les instances — mettre en cache les cas de test en mémoire (par défaut : 50 Mo, bonne plage : 50-250 Mo)
AFL_TESTCACHE_SIZE=100
AFL_FINAL_SYNC indique à l'instance principale d'effectuer une importation finale de tous les secondaires lors de l'arrêt. Cela n'affecte pas le processus de fuzzing lui-même — cela importe seulement lorsque vous exécutez afl-cmin pour la minimisation du corpus, en s'assurant que la file d'attente du primaire contient le corpus combiné complet. AFL_TESTCACHE_SIZE met en cache les cas de test en mémoire pour réduire les E/S disque ; la valeur par défaut est de 50 Mo et des valeurs entre 50 et 250 Mo fonctionnent bien pour la plupart des campagnes.
Fuzzing CI/Automatisé
# Échouer rapidement si le fuzzing ne trouve rien
AFL_EXIT_ON_TIME=3600 # 1 heure sans nouveaux chemins = arrêt
# Ou exécuter jusqu'à "done" (toutes les entrées de file traitées)
AFL_EXIT_WHEN_DONE=1
# Environnements sans interface graphique
AFL_NO_UI=1
Le fuzzing illimité dans CI gaspille des ressources. Définissez des limites de temps ou utilisez des conditions de sortie.
Variables à Éviter
| Variable | Pourquoi l'éviter |
|---|---|
AFL_NO_ARITH | Peut nuire à la couverture sur les formats binaires, mais peut être utile pour les cibles textuelles |
AFL_SHUFFLE_QUEUE | Uniquement pour des configurations exotiques, généralement nuisible |
AFL_DISABLE_TRIM | La taille est précieuse, ne la désactivez pas sans raison |
Fuzzing Multi-Cœur
AFL++ excelle dans le fuzzing multi-cœur avec deux avantages majeurs :
- Plus d'exécutions par seconde (évolue linéairement avec les cœurs physiques)
- Fuzzing asymétrique (par exemple, un job ASan, le reste sans sanitizers)
Démarrer une Campagne
Démarrez le fuzzer principal (en arrière-plan) :
./afl++ docker afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
Démarrez les fuzzers secondaires (autant que vous avez de cœurs) :
./afl++ docker afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error </dev/null &
./afl++ docker afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error </dev/null &
Le </dev/null est requis, pas décoratif. docker run -i maintient le client lisant sa propre entrée standard, et un processus en arrière-plan qui lit le terminal reçoit SIGTTOU et s'arrête. La redirection depuis /dev/null évite cela. Pour la même raison, les redirections stdout et stderr vers des fichiers sont essentielles, sinon la sortie concurrente des instances interfère.
Vérifiez que tout fonctionne :
docker ps --no-trunc
./afl++ host afl-whatsup state
Arrêtez la campagne :
# Tout arrêter en un geste
kill %1 %2 %3 # ou pkill -f afl-fuzz
Nettoyage :
docker container prune --force
Équilibrer les Travaux Multi-Cœur
Évitez de surcharger le CPU avec trop de fuzzers. Utilisez cette formule comme guide :
Nombre de cœurs = CPU_logiques - 2 (réserve pour l'OS)
Exemple : 16 CPU logiques → 14 cœurs de fuzzing
Survie des Campagnes Multi-Cœur Redémarrées
L'interface d'état d'abandon (afl-fuzz sans -M/-S) ne peut pas être redémarrée : afl-fuzz arrête tous les processus de fuzzing s'il reçoit un signal ou détecte une panne. Utilisez -M/-S pour chaque instance. Pour reprendre une campagne :
./afl++ docker afl-fuzz -M primary -i- -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
./afl++ docker afl-fuzz -S secondary01 -i- -o state -- ./fuzz 1>secondary01.log 2>secondary01.error </dev/null &
Remplacer -i seeds par -i- signale à AFL++ d'utiliser la file d'attente existante dans le répertoire d'état plutôt que de redémarrer avec les graines.
Triage des Crashes
Identifier les bugs uniques après une campagne :
# Compter les crashes
find state/default/crashes -type f | wc -l
# Utiliser afl-tmin pour minimiser les crashes
./afl++ docker afl-tmin -i state/default/crashes/id:000000* -o minimized_crash -- ./fuzz
Pour un triage avancé, consultez la compétence technique crash-triage.
Dépannage
| Problème | Solution |
|---|---|
| "Hmm, your system is configured to forward tiny cores..." | Exécutez afl-system-config comme root |
| "Fatal Python error: Cannot recover from stack overflow" | Augmentez la pile avec ulimit -s 16384 |
| "Unable to load LTO plugin" | Vérifiez la compatibilité de version Clang/LLD |
| Core dump files non créés | ulimit -c unlimited && echo "/tmp/core.%p" > /proc/sys/kernel/core_pattern |