QEMU Mode for Binary-Only Fuzzing
Relevant source files
The following files were used as context for generating this wiki page:AFL++'s QEMU mode provides high-performance, binary-only instrumentation for fuzzing closed-source applications. It leverages QEMU's "user emulation" mode to dynamically instrument and trace black-box binaries, allowing afl-fuzz to gain coverage feedback without needing source code. This approach is significantly faster than other dynamic binary instrumentation (DBI) frameworks like PIN or DynamoRIO, with a typical performance overhead of 2-5x compared to native execution.
This mode is essential for security researchers and developers who need to test the robustness of third-party software, legacy systems, or any application where recompilation with compiler-based instrumentation is not feasible. It supports a wide range of architectures and includes advanced features like persistent mode, snapshot fuzzing, and comparison logging to maximize fuzzing efficiency.
Sources: qemu_mode/README.md:7-16
Setup and Building
QEMU mode is not built by default and requires a separate build process using a dedicated script.
Build Process
The core of the setup is the build_qemu_support.sh script located in the qemu_mode/ directory. This script automates the process of downloading, patching, and compiling a specific version of QEMU tailored for AFL++.
The following diagram illustrates the build flow:
This diagram shows the steps executed by the build_qemu_support.sh script to prepare QEMU for fuzzing.
Sources: qemu_mode/build_qemu_support.sh
Build Customization
The build script can be customized using several environment variables to target different architectures or build types.
| Variable | Description | Example |
|---|---|---|
CPU_TARGET | Specifies the target CPU architecture. If not set, it defaults to the host architecture (uname -m). Common values include i386, x86_64, arm, and aarch64. | CPU_TARGET=arm ./build_qemu_support.sh |
HOST | Sets the cross-compiler prefix for building on a different host architecture. | HOST=arm-linux-gnueabi ./build_qemu_support.sh |
STATIC | If set to 1, builds a statically-linked afl-qemu-trace binary. This is useful for running the fuzzer on a different system from where it was built. | STATIC=1 ./build_qemu_support.sh |
DEBUG | If set to 1, builds a debug version of QEMU with symbols and extra checks. | DEBUG=1 ./build_qemu_support.sh |
ENABLE_HOOKING | If set to 1, compiles the native hooking bridge plugin. | ENABLE_HOOKING=1 ./build_qemu_support.sh |
Sources: qemu_mode/README.md:31-44, qemu_mode/build_qemu_support.sh:109-195, qemu_mode/hooking_bridge/README.md:11-13
Once the build is complete, the afl-qemu-trace binary will be placed in the root AFL++ directory. This is the QEMU helper that afl-fuzz will invoke.
Basic Usage
To use QEMU mode, simply add the -Q flag to the afl-fuzz command line.
afl-fuzz -Q -i in -o out -- /path/to/target_binary [args]
This command tells afl-fuzz to use afl-qemu-trace to run the target.
Sources: src/afl-fuzz.c:1169-1175, qemu_mode/README.md:25-28
Environment Variables for Basic Control
| Variable | Description |
|---|---|
QEMU_LD_PREFIX | Specifies the path to the target's libraries, which is essential when fuzzing a binary for a different architecture (e.g., ARM on x86_64). |
QEMU_RESERVED_VA | For i386 targets, this may be needed to reserve memory and prevent forkserver handshake failures. A typical value is 0x1000000. |
AFL_QEMU_FORCE_DFL | Forces QEMU to ignore the target's registered signal handlers. |
Sources: qemu_mode/README.md:46-52, qemu_mode/README.md:214-215
Advanced Features
QEMU mode offers several advanced features to optimize and deepen the fuzzing process.
Persistent Mode
Persistent mode dramatically increases fuzzing speed by avoiding the overhead of repeatedly forking and re-initializing the target process. Instead, the target is reset to a clean state at the beginning of each fuzzing iteration within the same process. This is supported for x86, x86_64, arm, and aarch64 targets.
To enable it, you must specify the start address of the persistent loop.
This diagram shows the logic of QEMU's persistent mode, where the process state is saved and restored to avoid re-initialization.
Key Environment Variables:
| Variable | Description |
|---|---|
AFL_QEMU_PERSISTENT_ADDR | The memory address where the persistent loop begins. The fuzzer will execute up to this point, save the state, and then repeatedly execute the code inside the loop with new inputs. |
AFL_QEMU_PERSISTENT_GPR | If set to 1, enables saving and restoring of general-purpose registers, which is required by most targets. |
AFL_QEMU_PERSISTENT_EXITS | If set to 1, the persistent loop will exit when the instrumented code returns. |
AFL_QEMU_PERSISTENT_HOOK | Path to a shared library (.so) that provides a custom afl_persistent_hook function. This hook can be used to manually place the input into memory and reset the target's state. |
Sources: qemu_mode/README.md:68-72, utils/qemu_persistent_hook/README.md:5-9, test/test-qemu-mode.sh:119-125
Snapshot Mode
Snapshot mode extends persistent mode by snapshotting and restoring the entire memory state and brk() of the process. This provides a more complete state reset than persistent mode alone.
- Enable with:
AFL_QEMU_SNAPSHOT=<hex_address>where the address is the snapshot entry point. - Performance: It can be slower than
fork()mode but scales better with multiple cores. Performance is significantly improved if the AFL++ snapshot kernel module is loaded, making it faster thanfork().
Sources: qemu_mode/README.md:74-86
Deferred Initialization
Similar to LLVM mode, QEMU mode supports deferred initialization. This allows you to start the forkserver at a later point in the target's execution, bypassing slow or complex initialization routines (e.g., config parsing).
AFL_ENTRYPOINT=<hex_address>: Sets the address where the forkserver will be started.AFL_EXITPOINT=<hex_address>: Sets an address that, when reached, will terminate the forked QEMU instance.
Sources: qemu_mode/README.md:54-66, qemu_mode/README.deferred_initialization_example.md:43-47
CMPLOG Mode
CMPLOG mode, based on the Redqueen project, is a highly effective technique for solving magic-byte comparisons. It instruments CMP instructions, learns the immediate values being compared against, and automatically creates a dictionary of these values to use in mutations. This is available for x86, x86_64, arm, and aarch64.
Enable it by passing the -c option to afl-fuzz:
# For a target that is not compiled with CmpLog support
afl-fuzz -Q -i in -o out -c /path/to/your/target -- /path/to/your/target
# If the target is already CmpLog-instrumented or for QEMU/FRIDA mode
afl-fuzz -Q -i in -o out -c 0 -- /path/to/your/target
Sources: qemu_mode/README.md:106-116, src/afl-fuzz.c:918-933
CompareCoverage (libcompcov)
CompareCoverage is a lighter-weight alternative to CMPLOG. It provides feedback on multi-byte comparisons byte-by-byte.
- Enable with:
AFL_PRELOAD=/path/to/libcompcov.so - Levels:
AFL_COMPCOV_LEVEL=1: Instruments comparisons with immediate values/read-only memory.AFL_COMPCOV_LEVEL=2: Instruments all comparison instructions and memory comparison functions.AFL_COMPCOV_LEVEL=3: Same as level 2, but adds experimental floating-point comparison instrumentation on x86/x86_64.
Sources: qemu_mode/README.md:92-104
IJON Mode
IJON (Input-to-State JON) mode allows the fuzzer to learn about how the input affects the program's internal state. It works by capturing register or memory values at specific instruction addresses and feeding them back to the fuzzer's mutation engine.
- Enable with:
AFL_QEMU_IJON=/path/to/config.conf - Configuration File Format: Each line specifies a rule:
code_addr, ijon_method, memory_addr_or_register, data_len - Supported Methods:
ijon_max,ijon_min,ijon_set,ijon_inc.
Sources: qemu_mode/README.md:118-157, src/afl-fuzz.c:4252-4255
Wine Mode
QEMU mode can be combined with Wine to fuzz Win32 PE binaries on Linux.
- Enable with:
afl-fuzz -W ...
Sources: qemu_mode/README.md:162-167, src/afl-fuzz.c:1200-1206
Instrumentation Control
You can control which parts of the binary are instrumented to focus fuzzing efforts and improve performance.
| Variable | Description |
|---|---|
AFL_QEMU_INST_RANGES | A comma-separated list of address ranges (0x123-0x321) or module names (module.so) to instrument. |
AFL_QEMU_EXCLUDE_RANGES | A comma-separated list of address ranges or module names to exclude from instrumentation. Exclusions take priority. |
AFL_INST_LIBS=1 | Overrides the default behavior (instrumenting only the main binary's .text section) and instruments all executed code, including shared libraries. |
Sources: qemu_mode/READMEmd:88-100, qemu_mode/README.md:175-181
Gotchas and Limitations
- Sanitizers: Do not use QEMU mode with ASAN, MSAN, etc. QEMU's memory management conflicts with the shadow memory techniques used by sanitizers.
- Security: QEMU's user emulation is not a security boundary. The fuzzed binary can interact with the host OS. Always run the fuzzer in a sandboxed environment if the target is untrusted.
- CPU Features: QEMU may not support all modern CPU features (e.g., AVX2). If your target requires them, you may need to recompile it for an older CPU architecture (
-march=core2). - Checksums: If the target requires checksums to be valid, you may need to use a custom mutator with a post-processing function to fix them.
Sources: qemu_mode/README.md:217-231