AFL++
AFL++ ist ein Fork des ursprünglichen AFL-Fuzzers, der eine bessere Fuzzing-Leistung und erweiterte Funktionen bei gleichbleibender Stabilität bietet. Ein großer Vorteil gegenüber libFuzzer ist die stabile Unterstützung für das Ausführen von Fuzzing-Kampagnen auf mehreren Kernen, was es ideal für groß angelegte Fuzzing-Bemühungen macht.
Wann verwenden
| Fuzzer | Am besten geeignet für | Komplexität |
|---|---|---|
| AFL++ | Multi-Core-Fuzzing, diverse Mutationen, ausgereifte Projekte | Mittel |
| libFuzzer | Schnelle Einrichtung, Single-Threaded, einfache Harnesses | Niedrig |
| LibAFL | Benutzerdefinierte Fuzzer, Forschung, fortgeschrittene Anwendungsfälle | Hoch |
Wählen Sie AFL++, wenn:
- Sie Multi-Core-Fuzzing benötigen, um den Durchsatz zu maximieren
- Ihr Projekt mit Clang oder GCC kompiliert werden kann
- Sie diverse Mutationsstrategien und ausgereifte Werkzeuge wünschen
- libFuzzer stagniert und Sie mehr Coverage benötigen
- Sie Produktionscodebasen fuzzen, die von paralleler Ausführung profitieren
Schnellstart
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Rufen Sie Ihren Code mit den vom Fuzzer bereitgestellten Daten auf
check_buf((char*)data, size);
return 0;
}
Kompilieren und ausführen:
# Zuerst das AFL++-Wrapper-Skript einrichten (siehe 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++ hat viele Abhängigkeiten, darunter LLVM, Python und Rust. Wir empfehlen die Verwendung einer aktuellen Debian- oder Ubuntu-Distribution für das Fuzzing mit AFL++.
| Methode | Wann verwenden | Unterstützte Compiler |
|---|---|---|
| Ubuntu/Debian Repos | Aktuelles Ubuntu, nur Basisfunktionen | Ubuntu 23.10: Clang 14 & GCC 13Debian 12: Clang 14 & GCC 12 |
| Docker (von Docker Hub) | Bestimmte AFL++-Version, Apple Silicon Unterstützung | Stand 4.35c: Clang 19 & GCC 11 |
| Docker (aus dem Quellcode) | Unveröffentlichte Funktionen testen, Patches anwenden | Konfigurierbar im Dockerfile |
| Aus dem Quellcode | Docker vermeiden, bestimmte Patches benötigen | Einstellbar über die Umgebungsvariable LLVM_CONFIG |
Ubuntu/Debian
Überprüfen Sie vor der Installation von AFL++ die Clang-Versionsabhängigkeit des Pakets mit apt-cache show afl++ und installieren Sie die passende lld-Version (z. B. lld-17).
apt install afl++ lld-17
Docker (von Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker (aus dem Quellcode)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
Aus dem Quellcode
Lesen Sie das Dockerfile für die Ubuntu-Versionsanforderungen und Abhängigkeiten. Setzen Sie LLVM_CONFIG, um die Clang-Version anzugeben (z. B. llvm-config-18).
Wrapper-Skript Einrichtung
Erstellen Sie ein Wrapper-Skript, um AFL++ auf dem Host oder in Docker auszuführen:
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++
Die folgenden Beispiele verwenden den docker-Modus, mit Ausnahme der Systemkonfigurationsbefehle, die auf den Host-Kernel zugreifen müssen. Ersetzen Sie host, um einen davon gegen eine auf dem Rechner installierte AFL++-Version auszuführen. Der Wrapper verbindet alles nach dem Modus-Argument zu einer einzigen Shell-Zeichenfolge, sodass Anführungszeichen nicht erhalten bleiben: Ein Argument, das ein Leerzeichen enthält (-x "my dict.dict"), wird wortgeteilt. Benennen Sie solche Dateien ohne Leerzeichen um oder bearbeiten Sie den Wrapper für diesen Lauf.
Das fehlende -t ist beabsichtigt. docker run -ti bricht mit the input device is not a TTY ab, wenn stdin kein Terminal ist, was CI-Jobs und alles abdeckt, was ein Agent oder Skript ausführt. afl-fuzz erkennt, dass kein Terminal vorhanden ist, und gibt einfache Statuszeilen anstelle der Vollbild-Benutzeroberfläche aus. Ein Programm, das auf einem Terminal besteht, wie z. B. watch, muss auf der Host-Seite des Wrappers ausgeführt werden. $$ wird zur PID des Wrappers erweitert, sodass parallele Instanzen unterschiedliche Containernamen erhalten und nicht mit einem einzigen afl_fuzzing kollidieren. docker ps kürzt die COMMAND-Spalte, sodass jede Zeile gleich aussieht; verwenden Sie docker ps --no-trunc, um die Instanzen zu unterscheiden, bevor Sie eine stoppen.
Sicherheitswarnung: Die Skripte afl-system-config und afl-persistent-config benötigen Root-Rechte und deaktivieren Betriebssystem-Sicherheitsfunktionen. Fuzzen Sie nicht auf Produktionssystemen oder Ihrer Entwicklungsumgebung. Verwenden Sie stattdessen eine dedizierte VM.
Systemkonfiguration
Führen Sie nach jedem Neustart aus, um bis zu 15 % mehr Ausführungen pro Sekunde zu erzielen:
./afl++ host afl-system-config
afl-system-config optimiert den Kernel, auf dem es ausgeführt wird, also führen Sie es auf dem Rechner aus, der die Kampagne hostet. ./afl++ docker afl-system-config erreicht die gleichen Einstellungen durch den --privileged-Container des Wrappers, was die einzige Möglichkeit ist, wenn AFL++ nur über Docker installiert ist.
Für maximale Leistung deaktivieren Sie Kernel-Sicherheitsmaßnahmen (erfordert grub-Bootloader, nicht in Docker unterstützt):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ host afl-system-config
Überprüfen Sie mit cat /proc/cmdline - die Ausgabe sollte mitigations=off enthalten.
Schreiben einer Harness
Harness-Struktur
AFL++ unterstützt Harnesses im libFuzzer-Stil:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Überprüfen Sie bei Bedarf die Eingabegröße
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Rufen Sie die Zielfunktion mit den Fuzz-Daten auf
target_function(data, size);
// 3. Geben Sie 0 zurück (nicht Null für zukünftige Verwendung reserviert)
return 0;
}
Harness-Regeln
| Tun | Nicht tun |
|---|---|
| Globalen Zustand zwischen Durchläufen zurücksetzen | Auf Zustand vorheriger Durchläufe verlassen |
| Grenzfälle elegant behandeln | Bei ungültiger Eingabe beenden |
| Harness deterministisch halten | Zufallszahlengeneratoren verwenden |
| Allokierten Speicher freigeben | Speicherlecks verursachen |
| Eingabegrößen validieren | Unbegrenzte Eingaben verarbeiten |
Siehe auch: Ausführliche Techniken zum Schreiben von Harnesses, Muster für die Verarbeitung komplexer Eingaben und fortgeschrittene Strategien finden Sie im fuzz-harness-writing-Technik-Skill.
Kompilierung
AFL++ bietet mehrere Kompilierungsmodi mit unterschiedlichen Kompromissen.
Entscheidungsbaum für den Kompilierungsmodus
Wählen Sie Ihren Kompilierungsmodus:
- LTO-Modus (
afl-clang-lto): Beste Leistung und Instrumentierung. Versuchen Sie dies zuerst. - LLVM-Modus (
afl-clang-fast): Fallback, wenn LTO nicht kompiliert. - GCC-Plugin (
afl-gcc-fast): Für Projekte, die GCC erfordern.
Grundlegende Kompilierung (LLVM-Modus)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCC-Kompilierung
./afl++ docker afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Wichtig: Die GCC-Version muss mit der Version übereinstimmen, die zum Kompilieren des AFL++ GCC-Plugins verwendet wurde.
Mit Sanitizern
./afl++ docker AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
Siehe auch: Detaillierte Sanitizer-Konfiguration, häufige Probleme und erweiterte Flags finden Sie in den address-sanitizer- und undefined-behavior-sanitizer-Technik-Skills.
Build-Flags
Beachten Sie, dass -g nicht notwendig ist, es wird standardmäßig von den AFL++-Compilern hinzugefügt.
| Flag | Zweck |
|---|---|
-DNO_MAIN=1 | Hauptfunktion überspringen, wenn libFuzzer-Harness verwendet wird |
-O2 | Optimierungsstufe für die Produktion (empfohlen für Fuzzing) |
-fsanitize=fuzzer | Aktiviert den libFuzzer-Kompatibilitätsmodus und fügt die Fuzzer-Laufzeit beim Linken der ausführbaren Datei hinzu |
-fsanitize=fuzzer-no-link | Instrumentieren ohne Verknüpfen der Fuzzer-Laufzeit (für statische Bibliotheken und Objektdateien) |
Korpusverwaltung
Erstellen eines初始en Korpus
AFL++ benötigt mindestens eine nicht leere Seed-Datei:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
Für reale Projekte sammeln Sie repräsentative Eingaben:
- Laden Sie Beispieldateien für das Format herunter, das Sie fuzzen
- Extrahieren Sie Testfälle aus der Testsuite des Projekts
- Verwenden Sie minimale gültige Eingaben für Ihr Dateiformat
Korpusminimierung
Nach einer Kampagne minimieren Sie den Korpus, um nur eindeutige Coverage zu behalten:
./afl++ docker afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
Siehe auch: Korpus-Erstellungsstrategien, Wörterbücher und Seed-Auswahl finden Sie im fuzzing-corpus-Technik-Skill.
Ausführen von Kampagnen
Grundlegender Lauf
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
Umgebungsvariablen setzen
./afl++ docker AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
Ausgabe interpretieren
AFL++ meldet diese Statistiken auf beide Arten, aber wie Sie sie lesen, hängt vom Modus ab. Der docker run -i des Wrappers gibt dem Container kein TTY, daher lässt afl-fuzz die Vollbild-Benutzeroberfläche fallen und schreibt stattdessen einfache Statuszeilen in das Protokoll – die unten aufgeführten Felder erscheinen dort und in state/<instance>/fuzzer_stats. Um die interaktive Benutzeroberfläche zu erhalten, führen Sie den host-Modus in einem Terminal aus oder fügen Sie -t zum Wrapper für einen Lauf hinzu, den Sie von Hand überwachen.
| Ausgabe | Bedeutung |
|---|---|
| execs/sec | Ausführungsgeschwindigkeit - höher ist besser |
| cycles done | Anzahl der abgeschlossenen Warteschlangendurchläufe |
| corpus count | Anzahl der eindeutigen Testfälle in der Warteschlange |
| saved crashes | Anzahl der gefundenen eindeutigen Abstürze |
| stability | % der stabilen Kanten (sollte nahe 100 % sein) |
Ausgabeverzeichnisstruktur
out/default/
├── cmdline # Wie wurde die SUT aufgerufen?
├── crashes/ # Eingaben, die die SUT zum Absturz bringen
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Eingaben, die die SUT aufhängen
├── queue/ # Testfälle, die den endgültigen Fuzzer-Zustand reproduzieren
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Kampagnenstatistiken
└── plot_data # Daten zum Plotten
Ergebnisse analysieren
Live-Kampagnenstatistiken anzeigen:
./afl++ docker afl-whatsup out
Coverage-Plots erstellen. Das aflplusplus-Image enthält bereits gnuplot-nox; im Host-Modus installieren Sie gnuplot zuerst mit apt install gnuplot.
./afl++ docker afl-plot out/default out_graph/
Testfälle erneut ausführen
Geben Sie einen der Dateinamen aus out/default/crashes/ ein:
./afl++ docker ./fuzz out/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
Fuzzer-Optionen
| Option | Zweck |
|---|---|
-G 4000 | Maximale Testeingabelänge (Standard: 1048576 Bytes) |
-t 1000 | Timeout in Millisekunden für jeden Testfall (Standard: 1000ms) |
-m 1000 | Speicherlimit in Megabyte (Standard: 0 = unbegrenzt) |
-x ./dict.dict | Wörterbuchdatei verwenden, um Mutationen zu leiten |
Umgebungsvariablen, die wichtig sind
AFL++ hat viele Umgebungsvariablen, aber die meisten sind Nischen. Diese sind diejenigen, die in der Praxis wichtig sind.
Diese immer setzen
# Jede Kampagne sollte tmpfs verwenden — SSDs werden es Ihnen danken, und es ist schneller
AFL_TMPDIR=/dev/shm
AFL_TMPDIR ist ein kostenloser Leistungsgewinn ohne Nachteile — es nicht zu setzen, nutzt Ihre SSD ab und verlangsamt das Fuzzing.
Langsame Ziele
# Beschleunigt die Kalibrierung um ~2,5x — verwenden Sie, wenn Ziele langsam sind (z. B. >10 ms/exec)
AFL_FAST_CAL=1
AFL_FAST_CAL reduziert die Kalibrierungszeit bei vernachlässigbarem Genauigkeitsverlust. Empfohlen speziell für langsame Ziele, bei denen die Kalibrierung sonst lange dauern würde.
Multi-Core-Kampagnen
# Nur auf der primären (-M) Instanz — wird für afl-cmin benötigt, nicht für das Fuzzing selbst
AFL_FINAL_SYNC=1
# Auf allen Instanzen — Testfälle im Arbeitsspeicher zwischenspeichern (Standard: 50 MB, guter Bereich: 50-250 MB)
AFL_TESTCACHE_SIZE=100
AFL_FINAL_SYNC teilt der primären Instanz mit, beim Stoppen einen letzten Import von allen sekundären durchzuführen. Dies hat keinen Einfluss auf den Fuzzing-Prozess selbst — es ist nur wichtig, wenn Sie später afl-cmin zur Korpusminimierung ausführen, um sicherzustellen, dass die Warteschlange des Primären den vollständigen kombinierten Korpus enthält. AFL_TESTCACHE_SIZE speichert Testfälle im Arbeitsspeicher zwischen, um die Festplatten-E/A zu reduzieren; der Standardwert ist 50 MB, und Werte zwischen 50-250 MB funktionieren für die meisten Kampagnen gut.
CI/Automatisiertes Fuzzing
# Fehlschlagen, wenn das Fuzzing nichts findet
AFL_EXIT_ON_TIME=3600 # 1 Stunde ohne neue Pfade = stoppen
# Oder ausführen, bis "erledigt" (alle Warteschlangeneinträge verarbeitet)
AFL_EXIT_WHEN_DONE=1
# Headless-Umgebungen
AFL_NO_UI=1
Unbegrenztes Fuzzing in CI verschwendet Ressourcen. Setzen Sie Zeitlimits oder verwenden Sie Exit-Bedingungen.
Zu vermeidende Variablen
| Variable | Warum überspringen |
|---|---|
AFL_NO_ARITH | Kann die Coverage bei Binärformaten beeinträchtigen, aber für textbasierte Ziele nützlich sein |
AFL_SHUFFLE_QUEUE | Nur für exotische Setups, meist schädlich |
AFL_DISABLE_TRIM | Trimmen ist wertvoll, nicht ohne Grund deaktivieren |
Multi-Core-Fuzzing
AFL++ zeichnet sich durch Multi-Core-Fuzzing mit zwei Hauptvorteilen aus:
- Mehr Ausführungen pro Sekunde (skaliert linear mit physischen Kernen)
- Asymmetrisches Fuzzing (z. B. ein ASan-Job, der Rest ohne Sanitizer)
Starten einer Kampagne
Starten Sie den primären Fuzzer (im Hintergrund):
./afl++ docker afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
Starten Sie sekundäre Fuzzer (so viele, wie Sie Kerne haben):
./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 &
Das </dev/null ist erforderlich, nicht dekorativ. docker run -i hält den Client, der seine eigene stdin liest, und ein im Hintergrund laufender Prozess, der das Terminal liest, erhält SIGTSTP. Es gibt ein separates Konzept namens Fuzzer-Farmen, bei denen mehrere physische Maschinen über eine Netzwerkfreigabe zusammenarbeiten – mit einer primären Instanz, die die Seeds hostet – aber dieser Ansatz ist für die meisten Anwendungsfälle zu komplex und wird hier nicht behandelt.
Beenden einer Kampagne
Sanftes Beenden
Senden Sie SIGINT (Ctrl+C) an den Hauptprozess:
# Hauptinstanz und alle sekundären Instanzen beenden
kill -2 %1 # Oder: docker kill afl_fuzzing_12345
Es kann mehrere Minuten dauern, bis der Vorgang abgeschlossen ist, da der Fuzzer die Warteschlange in den persistenten Speicher schreibt und dem Primären Zeit für den letzten Synchronisierungsvorgang gibt (AFL_FINAL_SYNC). Hartes Beenden (SIGKILL) ist nicht empfehlenswert, da es zu Datenverlust führen kann.
Datenträgerbereinigung
# Entfernen des Docker-Containers, wenn er nicht mit --rm gestartet wurde
docker rm afl_fuzzing_12345
Crash-Triage
Grundlegende Triage
Sortieren Sie die Ausgabe von out/default/crashes/ nach Schweregrad:
- Überprüfen Sie auf unterschiedliche Absturzsignale und Adressen mit
afl-whatsup - Führen Sie jeden eindeutigen Crash erneut aus, um die Reproduzierbarkeit zu bestätigen
- Notieren Sie die Pfade, die zu jedem Crash führen
Erneutes Ausführen eines Crashes:
./afl++ docker ./fuzz out/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
Jeder Crash, der reproduziert werden kann, sollte einen Bug-Ticket erhalten; nicht reproduzierbare Crashes können temporäre Zustandsfehler sein (z. B. Stapelüberläufe).
Bekannte Fehler vermeiden
| Muster | Interpretation |
|---|---|
sig:06 (SIGABRT) | Aktivierter Sanitizer (ASAN, UBSan, etc.) |
sig:11 (SIGSEGV) | Speicherzugriffsfehler (wahrscheinlich gültiger Bug) |
sig:04 (SIGILL) | Ungültige Anweisung (kann auf Pufferüberlauf hinweisen) |
time:... | Timeout (legt Hänger in out/default/hangs/ ab) |
Nächste Schritte
- Mehr Testfälle hinzufügen – Sammeln Sie größere oder vielfältigere Eingaben für den Korpus
- Wörterbuchdatei erstellen – Verwenden Sie Schlüsselwörter, Token oder strukturelle Elemente für das Zielformat mit der Option
-x - Parallelisierungsstrategie optimieren – Passen Sie die Anzahl der sekundären Instanzen an Ihre Kernanzahl an
- Sanitizer-Kombinationen ausprobieren – Führen Sie eine ASAN-Instanz und den Rest ohne Sanitizer aus, um die Geschwindigkeit zu optimieren
- Neue Instrumentierung ausprobieren – Wechseln Sie zu LTO-Modus (
afl-clang-lto), wenn Sie LLVM-Modus verwenden