Fuzzing with AFL++: Exercise 1 (simple_crash)
In this post, I walk through Exercise 1 from the Fuzzing-Module repository by Alex Maleno, using AFL++ inside a containerized environment. The goal of this exercise is simple but powerful: compile a vulnerable program with AFL++ instrumentation, fuzz it, and analyze the resulting crashes.
Environment Setup
The exercise is executed inside a container that already contains AFL++. I mount the exercise directory into the container so that the source code is available at runtime.
podman run --rm -it -v $(pwd)/Fuzzing-Module/exercise1:/target <afl++-image>Inside the container, the project structure looks like this:
/target
├── CMakeLists.txt
└── simple_crash.cppUnderstanding the Target Program
The target binary is built from simple_crash.cpp. It reads user input from stdin and contains a deliberate crash condition. When a specific input pattern is reached, the program aborts, making it ideal for fuzzing.
The program is stdin-based, which means AFL++ can fuzz it without any special harness.
Building the Target with AFL++ Instrumentation
To enable coverage-guided fuzzing, the program must be compiled using AFL++ compilers.
mkdir build
cd build
CC=/AFLplusplus/afl-clang-fast \
CXX=/AFLplusplus/afl-clang-fast++ \
cmake ..
makeDuring compilation, AFL++ reports instrumentation details:
- LLVM PCGUARD mode is enabled
- 34 locations are instrumented
- No collisions detected
This confirms the binary is ready for fuzzing.
The final instrumented binary is:
./simple_crashSanity Check
Before fuzzing, I manually execute the binary to confirm it works:
./simple_crash
enter input string: testThe program accepts input and exits normally. This is a good sign.
Creating the Initial Seed Corpus
AFL++ requires at least one seed input to start fuzzing. I create a small corpus of random inputs using /dev/urandom:
mkdir seeds
cd seeds
for i in {0..4}; do dd if=/dev/urandom of=seed_$i bs=64 count=10; doneThis results in five initial seed files:
seed_0 seed_1 seed_2 seed_3 seed_4Launching AFL++
With the instrumented binary and seeds ready, I start fuzzing:
/AFLplusplus/afl-fuzz \
-i /target/build/seeds \
-o out \
-m none \
-d -- /target/build/simple_crashKey options:
-i– input corpus-o– output directory-m none– disable memory limit (container-friendly)-d– deterministic mode (deprecated but still works)
AFL++ immediately begins execution and coverage discovery.
AFL++ Runtime Observations
After a few minutes, AFL++ reports:
- Total crashes: 3
- Corpus count: 21
- Coverage density: ~40%
- Exec speed: ~60 exec/sec
This confirms that AFL++ successfully discovered crashing inputs very quickly.
Inspecting the Output Directory
The output directory follows the standard AFL++ structure:
out/default/
├── crashes/
├── queue/
├── hangs/
├── fuzzer_stats
└── plot_dataInside crashes/, I find multiple crashing inputs:
id:000000,sig:06,src:000003,time:221,execs:101,op:havoc,rep:1
id:000001,sig:06,src:000003,time:377,execs:114,op:havoc,rep:11
id:000002,sig:06,src:000003,time:1241,execs:198,op:havoc,rep:14Each file represents a unique crashing test case.
Reading the Crash Metadata
The README.txt inside the crashes/ directory explains how the crash was found and how to reproduce it:
Command line used to find this crash:
/AFLplusplus/afl-fuzz -i seeds -o out -m none -d -- simple_crashAFL++ also recommends using afl-tmin to minimize crashing inputs.
Reproducing the Crash Manually
To validate the crash outside AFL++, I feed the crashing input directly to the program:
cat out/default/crashes/id:000000* | ./simple_crashThanks for reading,
Can
