LibAFL
LibAFL ist eine modulare Fuzzing-Bibliothek, die Funktionen von AFL-basierten Fuzzern wie AFL++ implementiert. Im Gegensatz zu traditionellen Fuzzern stellt LibAFL alle Funktionalitäten modular und anpassbar als Rust-Bibliothek bereit. Es kann als Drop-in-Ersatz für libFuzzer oder als Bibliothek zum Erstellen eigener Fuzzer von Grund auf verwendet werden.
Wann verwenden
| Fuzzer | Am besten geeignet für | Komplexität |
|---|---|---|
| libFuzzer | Schnelle Einrichtung, Single-Threaded | Niedrig |
| AFL++ | Multi-Core, allgemeiner Zweck | Mittel |
| LibAFL | Benutzerdefinierte Fuzzer, erweiterte Funktionen, Forschung | Hoch |
Wählen Sie LibAFL, wenn:
- Sie benutzerdefinierte Mutationsstrategien oder Feedback-Mechanismen benötigen
- Standard-Fuzzer Ihre Zielarchitektur nicht unterstützen
- Sie neuartige Fuzzing-Techniken implementieren möchten
- Sie feingranulare Kontrolle über Fuzzing-Komponenten benötigen
- Sie Fuzzing-Forschung betreiben
Schnellstart
LibAFL kann als Drop-in-Ersatz für libFuzzer mit minimalem Setup verwendet werden:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Rufen Sie Ihren Code mit den vom Fuzzer bereitgestellten Daten auf
my_function(data, size);
return 0;
}
Erstellen Sie die libFuzzer-Kompatibilitätsschicht von LibAFL:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
Kompilieren und ausführen:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
Installation
Voraussetzungen
- Clang/LLVM 15-18
- Rust (via rustup)
- Zusätzliche Systemabhängigkeiten
Linux/macOS
Clang installieren:
apt install clang
Oder eine bestimmte Version über apt.llvm.org installieren:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Umgebung für Rust konfigurieren:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Rust installieren:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Zusätzliche Abhängigkeiten installieren:
apt install libssl-dev pkg-config
Für den libFuzzer-Kompatibilitätsmodus nightly Rust installieren:
rustup toolchain install nightly --component llvm-tools
Überprüfung
Erstellen Sie LibAFL, um die Installation zu überprüfen:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Sollte libFuzzer.a erzeugen
Schreiben einer Harness
LibAFL-Harnesses folgen dem gleichen Muster wie libFuzzer, wenn der Drop-in-Ersatzmodus verwendet wird:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Ihr Fuzzing-Zielcode hier
return 0;
}
Beim Erstellen benutzerdefinierter Fuzzer mit LibAFL als Rust-Bibliothek wird die Harness-Logik direkt in den Fuzzer integriert. Siehe den Abschnitt "Schreiben eines benutzerdefinierten Fuzzers" unten für das vollständige Muster.
Siehe auch: Detaillierte Techniken zum Schreiben von Harnesses finden Sie in der Fertigkeit harness-writing.
Verwendungsmodi
LibAFL unterstützt zwei primäre Verwendungsmodi:
1. libFuzzer Drop-in-Ersatz
Verwenden Sie LibAFL als Ersatz für libFuzzer mit vorhandenen Harnesses.
Kompilierung:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
Ausführung:
./fuzz corpus/
Empfohlen für lange Kampagnen:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Benutzerdefinierter Fuzzer als Rust-Bibliothek
Erstellen Sie einen vollständig angepassten Fuzzer mit LibAFL-Komponenten.
Projekt erstellen:
cargo init --lib my_fuzzer
cd my_fuzzer
cargo add [email protected] [email protected] [email protected] [email protected] \
--features "[email protected]/libfuzzer,[email protected]/sancov_pcguard_hitcounts"
Cargo.toml konfigurieren:
[lib]
crate-type = ["staticlib"]
Schreiben eines benutzerdefinierten Fuzzers
Siehe auch: Detaillierte Techniken zum Schreiben von Harnesses, Muster für die Handhabung komplexer Eingaben und erweiterte Strategien finden Sie in der Fertigkeit fuzz-harness-writing.
Fuzzer-Komponenten
Ein LibAFL-Fuzzer besteht aus modularen Komponenten:
- Observer - Sammeln von Ausführungsfeedback (Abdeckung, Timing)
- Feedback - Bestimmen, ob Eingaben interessant sind
- Objective - Definieren von Fuzzing-Zielen (Abstürze, Timeouts)
- State - Verwalten von Korpus und Metadaten
- Mutator - Generieren neuer Eingaben
- Scheduler - Auswählen, welche Eingaben mutiert werden
- Executor - Ausführen des Ziels mit Eingaben
Grundlegende Fuzzer-Struktur
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};
#[no_mangle]
pub extern "C" fn libafl_main() {
let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
// 1. Observer einrichten
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. Feedback definieren
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. Objective definieren
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. Zustand erstellen oder wiederherstellen
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. Mutator einrichten
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. Scheduler einrichten
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Fuzzer erstellen
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. Harness definieren
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. Executor einrichten
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Initiale Eingaben laden
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. Fuzzing starten
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// Fuzzer starten
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
Kompilierung
Ausführlicher Modus
Geben Sie alle Instrumentierungs-Flags manuell an:
clang++-15 -DNO_MAIN -g -O2 \
-fsanitize-coverage=trace-pc-guard \
-fsanitize=address \
-Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
main.cc harness.cc -o fuzz
Compiler-Wrapper (Empfohlen)
Erstellen Sie einen LibAFL-Compiler-Wrapper, um die Instrumentierung automatisch zu handhaben.
src/bin/libafl_cc.rs erstellen:
use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
let mut cc = ClangWrapper::new();
cc.cpp(is_cpp)
.parse_args(&args)
.link_staticlib(&dir, "my_fuzzer")
.add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
.add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
.run()
.unwrap();
}
Kompilieren und verwenden:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
Siehe auch: Detaillierte Sanitizer-Konfiguration, häufige Probleme und erweiterte Flags finden Sie in den Fertigkeiten address-sanitizer und undefined-behavior-sanitizer.
Durchführen von Kampagnen
Grundlegender Durchlauf
./fuzz --cores 0 --input corpus/
Multi-Core-Fuzzing
./fuzz --cores 0,8-15 --input corpus/
Dies führt 9 Clients aus: einen auf Kern 0 und 8 auf den Kernen 8-15.
Mit Optionen
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
Textuelle Benutzeroberfläche (TUI)
Aktivieren Sie die grafische Statistikansicht:
./fuzz -tui=1 corpus/
Ausgabe interpretieren
| Ausgabe | Bedeutung |
|---|---|
corpus: N | Anzahl interessanter Testfälle gefunden |
objectives: N | Anzahl gefundener Abstürze/Timeouts |
executions: N | Gesamtzahl der Zielaufrufe |
exec/sec: N | Aktuelle Ausführungsdurchsatz |
edges: X% | Codeabdeckungsprozentsatz |
clients: N | Anzahl paralleler Fuzzing-Prozesse |
Der Fuzzer gibt zwei Hauptereignistypen aus:
- UserStats - Regelmäßiger Herzschlag mit aktuellen Statistiken
- Testcase - Neue interessante Eingabe entdeckt
Erweiterte Verwendung
Tipps und Tricks
| Tipp | Warum es hilft |
|---|---|
-fork=1 -ignore_crashes=1 verwenden | Fuzzing nach erstem Absturz fortsetzen |
InMemoryOnDiskCorpus verwenden | Korpus über Neustarts hinweg beibehalten |
TUI mit -tui=1 aktivieren | Bessere Visualisierung des Fortschritts |
| Spezifische LLVM-Version verwenden | Kompatibilitätsprobleme vermeiden |
RUSTFLAGS korrekt setzen | Link-Fehler verhindern |
Crash-Deduplizierung
Vermeiden Sie das Speichern doppelter Abstürze vom selben Fehler:
Backtrace-Observer hinzufügen:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
Executor aktualisieren:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
Objective mit Hash-Feedback aktualisieren:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
Dadurch wird sichergestellt, dass nur Abstürze mit eindeutigen Backtraces gespeichert werden.
Dictionary-Fuzzing
Verwenden Sie Wörterbücher, um das Fuzzing auf bestimmte Token zu lenken:
Token aus Datei hinzufügen:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
Mutator aktualisieren:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
Hardcodierte Token-Beispiel (PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG-Header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
Siehe auch: Detaillierte Strategien zur Dictionary-Erstellung und formatspezifische Wörterbücher finden Sie in der Fertigkeit fuzzing-dictionaries.
Automatische Token
Extrahieren Sie automatisch magische Werte und Prüfsummen aus dem Programm:
Im Compiler-Wrapper aktivieren:
cc.add_pass(LLVMPasses::AutoTokens)
Auto-Token im Fuzzer laden:
tokens += libafl_targets::autotokens()?;
Token-Abschnitt überprüfen:
echo "p (uint8_t *)__token_start" | gdb fuzz
Leistungsoptimierung
| Einstellung | Auswirkung |
|---|---|
| Multi-Core-Fuzzing | Linearer Speedup mit Kernen |
InMemoryCorpus | Schneller, aber nicht persistent |
InMemoryOnDiskCorpus | Ausgewogene Geschwindigkeit und Persistenz |
| Sanitizer | 2-5x Verlangsamung, essenziell für Bugs |
Optimierungsstufe -O2 | Gleichgewicht zwischen Geschwindigkeit und Abdeckung |
Fehlersuche im Fuzzer
Führen Sie den Fuzzer im Single-Process-Modus aus, um die Fehlersuche zu erleichtern:
// Launcher durch direkten Aufruf ersetzen
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Auskommentieren:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
Dann mit GDB debuggen:
gdb --args ./fuzz --cores 0 --input corpus/
Beispiele aus der Praxis
Beispiel: libpng
Fuzzing von libpng mit LibAFL:
1. Quellcode herunterladen:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev
2. Compiler-Wrapper setzen:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. Statische Bibliothek erstellen:
./configure --enable-shared=no
make
4. Harness herunterladen:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. Fuzzer linken:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. Seeds vorbereiten:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. Dictionary herunterladen (optional):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. Fuzzing starten:
./fuzz --input seeds/ --cores 0 -x png.dict
Beispiel: CMake-Projekt
Integrieren Sie LibAFL mit dem CMake-Buildsystem:
CMakeLists.txt:
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)
Nicht instrumentierte Binärdatei erstellen:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
Fuzzer erstellen:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
Fuzzer bauen:
cmake -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX .
cmake --build . --target fuzz
Ausführen:
./fuzz --input seeds/ --cores 0
Fehlersuche
Häufige Probleme
| Problem | Symptom | Lösung |
|---|---|---|
| Link-Fehler | „undefined reference to __sanitizer_cov“ | -fsanitize-coverage=trace-pc-guard hinzufügen |
| Rust-Fehler | „linker cc not found“ | RUSTFLAGS exportieren, um clang als Linker zu verwenden |
| Laufzeitfehler | „fork() failed“ | -fork=0 verwenden oder system-ulimit prüfen |
| Performance | Langsame Ausführungen | Sanitizer deaktivieren oder Optimierungsstufe erhöhen |