Open-source project
AFLplusplus/AFLplusplus avatar
AFLplusplus/AFLplusplus

AFL++: a coverage-guided fuzzer with compiler, QEMU, Frida and Unicorn modes

The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!

6,777 stars1,320 forksCAGPL-3.0

At a glance

What is it?
AFL++ is a fork of Google's AFL with community patches, several instrumentation backends and binary-only fuzzing modes. It suits teams that own the target's source or build, and it is a poor fit for anyone expecting a one-command scanner.
Who is it for?
Adopt AFL++ when you can rebuild the target with afl-cc or wrap a binary in QEMU, Frida or Unicorn mode, and when someone on the team will read docs/fuzzing_in_depth.md before trusting any crash. Do not adopt it as a drop-in scanner for a service you cannot restart, or for a JavaScript or Java codebase, because the compiler wrappers and the persistent-mode harnesses described here are C and C++ oriented.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What AFL++ actually solves, and for whom

AFL++ is a coverage-guided fuzzer. You give it a program and a small set of valid inputs, and it mutates those inputs while watching which edges of the program's control flow graph the mutated inputs reach. Inputs that reach new edges are kept and mutated further. The README frames the project as "a superior fork to Google's AFL" with more speed, more mutations and more instrumentation options, and the repository keeps the original AFL tooling names: afl-fuzz, afl-cc, afl-c++, afl-cmin, afl-whatsup, afl-plot.

The audience is narrow and technical. You need a target you can execute repeatedly, a seed corpus that gets past the input parser, and a machine you are willing to dedicate to the run. The README points readers at docs/fuzzing_in_depth.md and warns about "common sense risks of fuzzing" before the quick start, which is a fair signal of who this is for: people fuzzing their own code or code they are authorised to test, not people looking for a general vulnerability scanner.

How coverage, mutations and the mode split work

The mechanism starts at compile time. afl-cc is a compiler wrapper: point CC and CXX at it, and the resulting binary reports coverage back to afl-fuzz as it runs. The README's quick start uses exactly that pattern, and the repository also carries GNUmakefile.llvm and GNUmakefile.gcc_plugin, which correspond to two instrumentation backends: an LLVM-based one and a GCC plugin one. The project description mentions collision-free coverage, enhanced laf-intel and redqueen, AFLfast++ power schedules and MOpt mutators, so the mutation side is not a single fixed algorithm but a set of schedules and mutator stages selected at runtime.

Not every target can be rebuilt. For those, the repository ships qemu_mode, frida_mode, unicorn_mode, nyx_mode and coresight_mode. QEMU mode runs an uninstrumented binary under emulation and derives coverage from the emulator; Frida mode does something similar through dynamic instrumentation; Unicorn mode embeds the Unicorn CPU emulator so you can drive a library or a fragment of code directly. The trade-off is speed and fidelity: emulated coverage is slower and coarser than compiler-inserted instrumentation, which is why the README's own quick start is written for targets whose source you have.

One detail worth noting from the Dockerfile: it sets NO_CORESIGHT=1 and NO_NYX=1 by default, with comments saying Coresight is only available on specific ARM64 boards and Nyx is "possible but unlikely in a docker container". The container image therefore does not give you every mode the repository supports.

Installing AFL++ from Docker Hub or from source

The README offers two routes. The fastest is the published image, which the project says is available for x86_64 and arm64 and is republished automatically when a push to the stable branch happens:

bash
docker pull aflplusplus/aflplusplus
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus

With that command your target's source appears at /src inside the container. A second tag, aflplusplus/aflplusplus:dev, tracks the development state. If you would rather build it yourself, the README explicitly recommends that path and sends you to docs/INSTALL.md; the top-level Makefile is a thin wrapper that forwards to GNU make, and its targets include source-only, binary-only, distrib, install and clean. The Dockerfile builds on Ubuntu 24.04 and installs clang-20 and gcc-11, with comments explaining that GCC 11 is kept because genhtml for afl-cov dislikes GCC 12 and that some targets fail to compile under GCC 12.

A first fuzzing run, from seed to crash file

The README's quick start assumes you have source. First rebuild the target so the compiler wrapper inserts coverage instrumentation:

bash
CC=/path/to/afl-cc CXX=/path/to/afl-c++ ./configure --disable-shared
make clean all

Then collect a small set of valid inputs. The README suggests a dictionary as well when the input format is verbose, such as SQL or HTTP, and points at dictionaries/README.md for the format. With seeds in place, launch the fuzzer. If the program reads stdin:

bash
./afl-fuzz -i seeds_dir -o output_dir -- \
/path/to/tested/program [...program's cmdline...]

Add -x /path/to/dictionary.txt to the same command to supply a dictionary. If the program takes a file path instead, put @@ on its command line and AFL++ substitutes a generated file name. During the run, the README says to investigate anything shown in red in the fuzzer UI by reading docs/afl-fuzz_approach.md#understanding-the-status-screen. Crashes and hangs land in crashes/ and hangs/ under the output directory, and a crash can be replayed by piping it back into the target:

bash
cat output_dir/crashes/id:000000,* | /path/to/tested/program [...program's cmdline...]

For corpus minimisation the repository carries afl-cmin.bash, afl-cmin.py and afl-cmin.awk, and afl-whatsup and afl-plot summarise and graph a set of parallel fuzzing output directories. The README also recommends two companion projects, cov-analysis for source-based coverage reports and fuzz-reachability for static reachability analysis of a harness.

Where AFL++ is the wrong tool

The sharpest limitation is the corpus. Coverage-guided fuzzing only explores past a format check if some seed already passes it. If your seed set is empty or consists of one trivial file, the fuzzer spends its time rediscovering the header rather than reaching the parser, and no amount of mutation scheduling fixes that. The README's own emphasis on dictionaries and on reading docs/fuzzing_in_depth.md is an admission that the tool is not self-tuning.

Binary-only modes are a second boundary. QEMU, Frida and Unicorn modes exist precisely because instrumentation is not always possible, but they are slower and they observe the program at a different level than a compiled-in edge map. Choosing them is a compromise, not an equivalent path. And the Docker image disables Coresight and Nyx outright, so a container-based workflow silently lacks two modes the repository documents.

Finally, the licence is a real constraint for some organisations. AFL++ is AGPL-3.0-or-later with some files under Apache-2.0, and the README states that everything compiled into a fuzzing harness stays Apache 2.0. A commercial licence exists for organisations that cannot use the AGPL, and the README says it is obtained by donating to a good cause, with the project and its maintainers receiving no money. That is a genuinely unusual arrangement and worth reading in LICENSING.md before your legal team forms an opinion.

AFL++ compared with libFuzzer

libFuzzer is the natural alternative and the difference is architectural. libFuzzer is a library you link into the target: the fuzzer and the program share a process, and you write a function that consumes a byte buffer. That gives very fast in-process iteration, but it requires you to build the harness and to accept that the fuzzer lives inside your binary. AFL++ in its default mode is a separate process that launches the target, passes input through stdin, a file, or a persistent-mode harness, and reads coverage back from the instrumented binary. The separate-process model is slower per execution but works with programs you cannot restructure into a library call, and the persistent-mode harness recovers much of the speed when you can restructure.

The practical split: if you already have a libFuzzer target and it runs, there is little reason to move. If your target is a command-line program, a daemon, or a binary you cannot rebuild at all, AFL++ has the modes for it, and libFuzzer does not. The README itself points at Google's fuzzbench for comparisons and names the aflplusplus setup there, which is a more honest answer than any single benchmark number.

Maintenance, branches and what upgrading costs

The repository is not archived and the last push was on 2026-09-21, one day before this writing. Releases are frequent: v5.03c on 2026-09-02, v5.02c on 2026-06-29, v5.01c on 2026-06-18. The README names five maintainers plus a maintainer for frida_mode, and the project has been running long enough to have a papers page and a citation file.

The branch model matters more than the release cadence for upgrade planning. stable is the default branch and is synced from dev when the maintainers are satisfied with stability; dev is described as bleeding edge, with the README warning that you might catch a checkout that does not compile. Pull requests are accepted only against dev. In practice this means your fuzzing results are tied to the branch and the instrumentation backend you built against: switching from the GCC plugin to LLVM changes the edge map, and a corpus minimised under one build is not automatically equivalent under another. Plan to re-minimise with afl-cmin after a backend change rather than assuming the old corpus still represents the same coverage. On licence, the AGPL applies to the project itself while the README states that harness-compiled code stays Apache 2.0; each file carries its own SPDX-License-Identifier header, and the README says that header is the licence you must follow for that file. That is a description of the project's licensing, not legal advice for your situation.

Editorial conclusion

Adopt AFL++ when you can rebuild the target with afl-cc or wrap a binary in QEMU, Frida or Unicorn mode, and when someone on the team will read docs/fuzzing_in_depth.md before trusting any crash. Do not adopt it as a drop-in scanner for a service you cannot restart, or for a JavaScript or Java codebase, because the compiler wrappers and the persistent-mode harnesses described here are C and C++ oriented. Verify first that your build survives a full rebuild under CC=/path/to/afl-cc, that a seed corpus actually reaches the parser you care about, and that a crash found in output_dir/crashes/ still reproduces when replayed against the unmodified binary.

Frequently asked questions

How does AFL++ work?

It instruments a target so the binary reports which control-flow edges each input reaches, then mutates a seed corpus and keeps inputs that hit new edges. The README describes compiling with afl-cc, running afl-fuzz with -i seeds_dir -o output_dir, and collecting crashes and hangs in subdirectories of the output directory.

How do I install AFL++?

The README gives two routes: pull aflplusplus/aflplusplus from Docker Hub and mount your target at /src, or build it yourself, which the README recommends, following docs/INSTALL.md. The top-level Makefile forwards to GNU make and exposes targets including source-only, binary-only, distrib and install.

What is the difference between AFL++ and the original AFL?

The README calls AFL++ a fork of Google's AFL with more speed, more and better mutations, more and better instrumentation, and custom module support. The repository also carries the original tooling names such as afl-fuzz, afl-cc and afl-cmin.

Can AFL++ fuzz a target when I do not have the source code?

Yes, through the binary-only modes. The repository contains qemu_mode, frida_mode, unicorn_mode, nyx_mode and coresight_mode, and the README points to docs/fuzzing_binary-only_targets.md for that workflow. Note that the Dockerfile disables Coresight and Nyx by default.

What licence does AFL++ use?

AFL++ is AGPL-3.0-or-later and also contains files under Apache-2.0, with each file stating its own licence in an SPDX-License-Identifier header. The README says everything compiled into a fuzzing harness stays Apache 2.0, and that a commercial licence is available for organisations that cannot use the AGPL.

Official sources

  1. AFLplusplus/AFLplusplus on GitHub
  2. License: AGPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aflplusplus-aflplusplus.svg)](https://hysenlabs.com/projects/aflplusplus-aflplusplus)