LibAFL - Modular Fuzzing Framework Cheatsheet
LibAFL is a fuzzing framework, not a fuzzer. Written in Rust by the AFL++ team, it provides composable building blocks — observers, feedbacks, mutators, schedulers, stages, executors — that you assemble into a fuzzer tailored to your target. The motivation is that monolithic fuzzers work well on conventional targets and poorly on unusual ones (custom protocols, emulated firmware, kernels, grammars), where you end up fighting the tool. With LibAFL you build exactly the fuzzer the target needs, and get multi-core scaling for free.
Building a fuzzer is more work than running one. Reach for LibAFL when off-the-shelf tools genuinely do not fit.
Setup
| Step | Command |
|---|
| New project | cargo new my_fuzzer && cd my_fuzzer |
| Add LibAFL | cargo add libafl libafl_bolts |
| Target instrumentation | cargo add libafl_targets (or libafl_qemu, libafl_frida) |
| Build | cargo build --release |
Core Concepts
| Component | Role |
|---|
| Input | What gets fuzzed (bytes, grammar tree, syscalls) |
| Observer | Records data during a run (coverage map, timing) |
| Feedback | Decides if a run was “interesting” |
| Objective | Decides if a run is a solution (a crash) |
| Corpus | Storage for interesting inputs |
| Mutator | Transforms inputs |
| Scheduler | Chooses the next input to run |
| Stage | A phase applied per input (mutate, trim, calibrate) |
| Executor | Runs the target with an input |
A Minimal Fuzzer (shape)
// Conceptual structure — see LibAFL examples for complete code
let mut feedback = MaxMapFeedback::new(&edges_observer);
let mut objective = CrashFeedback::new();
let mut state = StdState::new(
StdRand::with_seed(current_nanos()),
InMemoryCorpus::new(),
OnDiskCorpus::new("./crashes")?,
&mut feedback,
&mut objective,
)?;
let scheduler = QueueScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let mut executor = InProcessExecutor::new(
&mut harness, tuple_list!(edges_observer), &mut fuzzer, &mut state, &mut mgr,
)?;
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
Executors (How the Target Runs)
| Executor | Use |
|---|
InProcessExecutor | Fastest; harness in the same process |
ForkserverExecutor | AFL-style forkserver for external binaries |
CommandExecutor | Run an external command per input |
libafl_qemu | Emulated targets / binary-only fuzzing |
libafl_frida | Dynamic instrumentation (binary-only) |
libafl_nyx | Snapshot-based VM fuzzing |
Feedbacks
| Feedback | Interesting when |
|---|
MaxMapFeedback | New coverage edges hit |
TimeFeedback | Execution time changes |
CrashFeedback | Target crashed (objective) |
TimeoutFeedback | Target hung (objective) |
NewHashFeedback | New unique crash stack |
| Combinators | feedback_or!, feedback_and! to compose |
Mutators
| Mutator | Does |
|---|
havoc_mutations() | Standard AFL-style random mutations |
tokens_mutations() | Dictionary/token insertion |
| Grammar mutators | Structure-aware generation |
| Custom | Implement the Mutator trait for your input type |
Custom input types plus custom mutators are the main reason to choose LibAFL — fuzzing a protocol or AST intelligently rather than flipping bytes.
Multi-Core Scaling
| Mechanism | Note |
|---|
LlmpRestartingEventManager | Low-overhead message passing between cores |
Launcher | Spawn one fuzzer per core with shared corpus |
| Corpus sync | Interesting inputs propagate across instances |
| Restarting | Survives crashes of the fuzzer process |
LibAFL vs Ready-Made Fuzzers
| Aspect | LibAFL | AFL++ | honggfuzz |
|---|
| Model | Build your own | Ready to run | Ready to run |
| Effort | High | Low | Low |
| Flexibility | Total | Configurable | Configurable |
| Best for | Unusual targets, research | Standard binaries | Fast start, hardware feedback |
Start with AFL++ or honggfuzz; move to LibAFL when the target’s input structure or execution model defeats them.
Resources