Skip to content

LibAFL - Modular Fuzzing Framework Cheatsheet

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

StepCommand
New projectcargo new my_fuzzer && cd my_fuzzer
Add LibAFLcargo add libafl libafl_bolts
Target instrumentationcargo add libafl_targets (or libafl_qemu, libafl_frida)
Buildcargo build --release

Core Concepts

ComponentRole
InputWhat gets fuzzed (bytes, grammar tree, syscalls)
ObserverRecords data during a run (coverage map, timing)
FeedbackDecides if a run was “interesting”
ObjectiveDecides if a run is a solution (a crash)
CorpusStorage for interesting inputs
MutatorTransforms inputs
SchedulerChooses the next input to run
StageA phase applied per input (mutate, trim, calibrate)
ExecutorRuns 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)

ExecutorUse
InProcessExecutorFastest; harness in the same process
ForkserverExecutorAFL-style forkserver for external binaries
CommandExecutorRun an external command per input
libafl_qemuEmulated targets / binary-only fuzzing
libafl_fridaDynamic instrumentation (binary-only)
libafl_nyxSnapshot-based VM fuzzing

Feedbacks

FeedbackInteresting when
MaxMapFeedbackNew coverage edges hit
TimeFeedbackExecution time changes
CrashFeedbackTarget crashed (objective)
TimeoutFeedbackTarget hung (objective)
NewHashFeedbackNew unique crash stack
Combinatorsfeedback_or!, feedback_and! to compose

Mutators

MutatorDoes
havoc_mutations()Standard AFL-style random mutations
tokens_mutations()Dictionary/token insertion
Grammar mutatorsStructure-aware generation
CustomImplement 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

MechanismNote
LlmpRestartingEventManagerLow-overhead message passing between cores
LauncherSpawn one fuzzer per core with shared corpus
Corpus syncInteresting inputs propagate across instances
RestartingSurvives crashes of the fuzzer process

LibAFL vs Ready-Made Fuzzers

AspectLibAFLAFL++honggfuzz
ModelBuild your ownReady to runReady to run
EffortHighLowLow
FlexibilityTotalConfigurableConfigurable
Best forUnusual targets, researchStandard binariesFast start, hardware feedback

Start with AFL++ or honggfuzz; move to LibAFL when the target’s input structure or execution model defeats them.

Resources