Persistent Mode
Relevant source files
The following files were used as context for generating this wiki page:Persistent mode is a high-performance fuzzing strategy that significantly increases fuzzing speed by eliminating the overhead of forking a new process for each test case. Instead of restarting the target application for every input, persistent mode executes the target function multiple times within a single, long-lived process. This can result in a speed increase of 2x to 20x, making it a highly effective and professionally utilized fuzzing technique.
This mode is available for various instrumentation backends, including LLVM-based instrumentation (llvm_mode), QEMU-based binary-only fuzzing (qemu_mode), and Frida-based dynamic instrumentation (frida_mode). A critical requirement for using persistent mode is that the target's state can be completely reset between runs to ensure that one execution does not influence subsequent ones.
Sources: instrumentation/README.persistent_mode.md:4-11, qemu_mode/README.persistent.md:4-6
Detection and Activation
AFL++ detects or activates persistent mode in several ways:
- Binary Signature: The fuzzer scans the target binary for the signature
##SIG_AFL_PERSISTENT##. This signature is automatically injected when using the__AFL_LOOPmacro inllvm_mode. - Environment Variable: Setting the
AFL_PERSISTENTenvironment variable to1forces the activation of persistent mode. This is useful when the__AFL_LOOPmacro is located in a shared library. - Mode-Specific Configuration: Modes like Frida and QEMU use specific environment variables (e.g.,
AFL_FRIDA_PERSISTENT_ADDR,AFL_QEMU_PERSISTENT_ADDR) to enable and configure persistent mode.
When persistent mode is detected, afl-fuzz sets the PERSIST_ENV_VAR (__AFL_PERSISTENT) to 1 for the child process.
Sources: src/afl-fuzz-init.c:2804-2833, config.h:468-469
LLVM Persistent Mode
This is the most common and straightforward way to use persistent mode, applicable when you have the source code of the target. It is enabled by using a set of macros provided by afl-clang-fast.
Sources: instrumentation/README.persistent_mode.md
Implementation
The core of LLVM persistent mode is the __AFL_LOOP(count) macro, which creates the main fuzzing loop.
The following diagram illustrates the execution flow of a target compiled in persistent mode.
This diagram shows the typical lifecycle of a persistent mode application, from initialization to the repeated execution within the __AFL_LOOP.
Key Macros
| Macro | Description |
|---|---|
__AFL_FUZZ_INIT() | Initializes shared memory fuzzing. Must be placed after includes. |
__AFL_INIT() | Implements a deferred forkserver. Call this after most initialization is done but before any fuzzed input is processed. |
__AFL_LOOP(count) | The main persistent mode loop. The count parameter specifies the maximum number of iterations before the process is restarted. A value of 1000 is a good starting point. |
__AFL_FUZZ_TESTCASE_BUF | A pointer (unsigned char *) to the buffer containing the fuzzed input from shared memory. |
__AFL_FUZZ_TESTCASE_LEN | An integer (int) containing the length of the fuzzed input. |
Sources: instrumentation/README.persistent_mode.md:16-40, 116-129
Example Usage
Here is a minimal example of how to structure a target for LLVM persistent mode.
#include "your_target_header.h"
// For shared memory fuzzing
__AFL_FUZZ_INIT();
main() {
// Optional: deferred forkserver after one-time inits
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
// Buffer for the test case from shared memory
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
// Optional: check for minimum input length
if (len < 8) continue;
/*
* 1. Setup state for the target function
* e.g., struct target *tmp = libtarget_init();
*
* 2. Call the function to be fuzzed
* e.g., target_function(buf, len);
*
* 3. Reset state to avoid side-effects
* e.g., libtarget_free(tmp);
*/
}
return 0;
}
To compile this, you would use afl-clang-fast or afl-clang-lto.
Sources: instrumentation/README.persistent_mode.md:14-43
QEMU Persistent Mode
For binary-only targets, QEMU mode provides a way to achieve persistence without source code. It works by setting a start address for the loop and patching the program to jump back to this start address instead of exiting or returning. This is currently available for x86, x86_64, arm, and aarch64 targets.
Sources: qemu_mode/README.persistent.md:4-8
Configuration
QEMU persistent mode is configured entirely through environment variables.
| Variable | Description |
|---|---|
AFL_QEMU_PERSISTENT_ADDR | Required. The hexadecimal start address of the persistent loop. For PIE binaries, an offset must be added (e.g., 0x4000000000 for x86_64). |
AFL_QEMU_PERSISTENT_RET | Optional. The address of the last instruction of the loop. If not set, QEMU assumes the loop ends at the function's return. |
AFL_QEMU_PERSISTENT_CNT | The number of iterations in the loop before the process restarts. Defaults to 1000. |
AFL_QEMU_PERSISTENT_GPR | Set to 1 to save and restore general-purpose registers at the start of each loop. This is almost always necessary to prevent state corruption. |
AFL_QEMU_PERSISTENT_HOOK | Path to a shared object with a hook function to be executed at the start of each iteration for in-memory fuzzing. |
AFL_QEMU_PERSISTENT_EXITS | If set, forces QEMU to jump to the START address instead of executing an exit_group syscall. |
Sources: qemu_mode/README.persistent.md:13-91
In-Memory Fuzzing Hook
To further increase speed, QEMU persistent mode supports an in-memory fuzzing hook. This bypasses file I/O by writing the fuzzing input directly into the target's memory. This is configured via AFL_QEMU_PERSISTENT_HOOK.
The shared object must export a function with the following signature:
void afl_persistent_hook(struct ARCH_regs *regs,
uint64_t guest_base,
uint8_t *input_buf,
uint32_t input_buf_len);
The hook can also export int afl_persistent_hook_init(void) which, if it returns true, enables the shared memory fuzzing feature of AFL++.
Sources: qemu_mode/README.persistent.md:93-118
Frida Persistent Mode
FRIDA mode also supports persistent fuzzing, configured through its JavaScript API. This allows for fine-grained control over the persistent loop using scripting.
Sources: frida_mode/Scripting.md:1-8
Configuration
The following JavaScript functions, accessible via the global Afl object, are used to configure persistence.
| Function | Description |
|---|---|
Afl.setPersistentAddress(address) | Sets the start address of the persistent loop. Equivalent to AFL_FRIDA_PERSISTENT_ADDR. |
Afl.setPersistentReturn(address) | Sets the return address for the end of the loop. Equivalent to AFL_FRIDA_PERSISTENT_RET. |
Afl.setPersistentCount(count) | Sets the loop iteration count. Equivalent to AFL_FRIDA_PERSISTENT_CNT. |
Afl.setPersistentHook(address) | Sets a native function pointer as a persistent hook, which can be implemented in a shared object or a CModule. |
Afl.setInMemoryFuzzing() | Enables in-memory fuzzing, allowing access to __afl_fuzz_ptr and __afl_fuzz_len. |
Sources: frida_mode/Scripting.md:31-43, 401-440
Example Usage
This example shows how to set up a persistent loop in Frida mode using symbols from the binary.
// Find the start address of the function 'main'
const persistent_addr = DebugSymbol.fromName('main');
Afl.print(`persistent_addr: ${persistent_addr.address}`);
// Set the persistent loop to start at 'main'
Afl.setPersistentAddress(persistent_addr.address);
// Set the loop to run 1,000,000 times before restarting
Afl.setPersistentCount(1000000);
/* ALWAYS call this when you have finished all your configuration */
Afl.done();
Sources: frida_mode/Scripting.md:18-45
Stateful Fuzzing with AFL_PERSISTENT_RECORD
For stateful targets where resetting the state is difficult, persistent mode can still be used with a special recording feature. If AFL_PERSISTENT_RECORD is set to a number, AFL++ will save that number of testcases leading up to and including a crash. These are saved in the crashes/ directory with a RECORD:... prefix. This helps in reproducing crashes in stateful applications. This feature is disabled by default and must be enabled at compile time by uncommenting #define AFL_PERSISTENT_RECORD in config.h.
Sources: config.h:110-115, instrumentation/README.persistent_mode.md:133-143
Summary
Persistent mode is a crucial optimization for effective fuzzing with AFL++. By avoiding the costly process of forking for each input, it dramatically increases execution speed. It is supported across source-based (LLVM) and binary-only (QEMU, Frida) fuzzing modes, each with its own method of configuration. While it requires careful implementation to ensure the target's state is properly reset, the performance benefits make it an indispensable feature for serious fuzzing campaigns.