Frida Mode for Dynamic Instrumentation
Relevant source files
The following files were used as context for generating this wiki page: - [frida_mode/README.md](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/frida_mode/README.md) - [frida_mode/Scripting.md](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/frida_mode/Scripting.md) - [frida_mode/src/main.c](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/frida_mode/src/main.c) - [frida_mode/ts/lib/afl.ts](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/frida_mode/ts/lib/afl.ts) - [frida_mode/DEBUGGING.md](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/frida_mode/DEBUGGING.md) - [src/afl-fuzz.c](https://github.com/lagraviereScience/AFLplusplus/blob/HEAD/src/afl-fuzz.c)Frida mode is a binary-only fuzzer for AFL++ that provides an alternative to QEMU mode. It uses the Frida dynamic instrumentation toolkit to instrument binary programs when source code is not available. The primary goal is to offer a user experience similar to QEMU mode, including the use of comparable environment variables for configuration.
The core of Frida mode is the injection of a shared library (afl-frida-trace.so) into the target process using LD_PRELOAD (or DYLD_INSERT_LIBRARIES on macOS). This library leverages Frida's Stalker component to dynamically instrument the running code, adding coverage tracking similar to that of afl-gcc. It also integrates with AFL++'s compiler-rt component to provide a fork server and communicate coverage information back to the fuzzer. This allows for effective fuzzing of closed-source applications on Linux and macOS for x86/x64 and aarch64 architectures.
Sources: frida_mode/README.md:3-6, frida_mode/README.md:46-52
Architecture and Design
Frida mode's architecture revolves around injecting afl-frida-trace.so into the target process. This library, built with the frida-gum devkit, uses Frida's Stalker to perform Just-In-Time (JIT) instrumentation of the target's code.
The key components of the design are:
- Shared Library Injection: The
afl-frida-trace.solibrary is preloaded into the target application at launch. - Dynamic Instrumentation: Frida's Stalker is used to trace execution and instrument basic blocks. On AARCH64 and x86/64, optimized assembly stubs are used for instrumentation to improve performance by avoiding the need to save and restore the full register context.
- Coverage Feedback: The instrumentation code updates a shared memory region with coverage information, which is then read by the main
afl-fuzzprocess. - Fork Server: The injected library also implements a fork server, similar to other AFL++ instrumentation methods, to speed up the fuzzing process by avoiding repeated process initialization.
- Prefetching: To optimize performance, instrumented code blocks from a child process are reported back to the parent via a shared memory region. This allows the parent to pre-instrument these blocks, so subsequent forked children inherit them, avoiding redundant JIT compilation.
Sources: frida_mode/README.md:46-64
The following diagram illustrates the high-level architecture of Frida Mode.
This diagram shows afl-fuzz launching the target, which preloads afl-frida-trace.so. The library uses Frida Stalker for instrumentation and communicates with afl-fuzz via a shared memory map.
Sources: frida_mode/README.md:46-52
Initialization Flow
The initialization process is managed within frida_mode/src/main.c. It begins with a constructor function init() that hooks the program's entry point (main or __libc_start_main).
- The
init()function, marked with__attribute__((constructor)), is executed before the target'smainfunction. - It calls
intercept_main()to hook the application's entry point. - When the original entry point is called, the
on_main()hook is executed. on_main()callsafl_frida_config()to set up all modules (instrumentation, scripting, persistent mode, etc.). This is where environment variables and JavaScript configurations are processed.- After configuration,
afl_frida_run()is called, which starts the Stalker (stalker_start()) and the main execution loop (entry_start()).
The sequence diagram below shows this flow.
Sources: frida_mode/src/main.c:170-259
Usage
To use Frida mode, afl-fuzz is invoked with the -O flag. This is analogous to the -Q flag for QEMU mode.
./afl-fuzz -i seeds_dir -o output_dir -O -- /path/to/tested/program
Frida mode aims to mimic QEMU mode's environment variables for configuration, typically by replacing QEMU with FRIDA in the variable name.
Sources: src/afl-fuzz.c:292, frida_mode/README.md:29-34
Feature Support
Frida mode supports many, but not all, of AFL++'s advanced features.
| Feature/Instrumentation | FRIDA mode | Notes |
|---|---|---|
| NeverZero | ✓ | |
| Persistent Mode | ✓ | x86/x64/aarch64 only |
| LAF-Intel / CompCov | - | CMPLOG is generally superior |
| CMPLOG | ✓ | x86/x64/aarch64 only |
| Selective Instrumentation | ✓ | |
| Non-Colliding Coverage | - | Not possible in binary-only instrumentation |
| Ngram prev_loc Coverage | - | |
| Context Coverage | - | |
| Auto Dictionary | - | |
| Snapshot LKM Support | - | |
| In-Memory Test Cases | ✓ | x86/x64/aarch64 only |
Sources: frida_mode/README.md:12-26
Configuration
Frida mode can be configured via environment variables or, more powerfully, through a JavaScript file.
Environment Variables
Many aspects of Frida mode can be controlled using environment variables.
Basic Configuration:
| Variable | Description |
|---|---|
AFL_FRIDA_DEBUG_MAPS | See AFL_QEMU_DEBUG_MAPS. |
AFL_FRIDA_EXCLUDE_RANGES | See AFL_QEMU_EXCLUDE_RANGES. |
AFL_FRIDA_INST_RANGES | See AFL_QEMU_INST_RANGES. |
AFL_FRIDA_PERSISTENT_ADDR | See AFL_QEMU_PERSISTENT_ADDR. |
AFL_FRIDA_PERSISTENT_CNT | See AFL_QEMU_PERSISTENT_CNT. |
AFL_FRIDA_PERSISTENT_HOOK | See AFL_QEMU_PERSISTENT_HOOK. |
AFL_FRIDA_PERSISTENT_RET | See AFL_QEMU_PERSISTENT_RET. |
AFL_FRIDA_JS_SCRIPT | Path to a JavaScript file for advanced configuration. |
Advanced Configuration:
| Variable | Description |
|---|---|
AFL_FRIDA_INST_COVERAGE_FILE | File to write DynamoRIO format coverage information. |
AFL_FRIDA_INST_DEBUG_FILE | File to write raw assembly of original and instrumented blocks. |
AFL_FRIDA_INST_NO_OPTIMIZE | Disables optimized inline assembly for coverage instrumentation. |
AFL_FRIDA_INST_NO_PREFETCH | Disables prefetching of instrumented blocks from child to parent. |
AFL_FRIDA_INST_TRACE | Logs the address of executed blocks to stdout. |
AFL_FRIDA_STALKER_NO_BACKPATCH | Disables Stalker's backpatching optimization. |
AFL_FRIDA_STATS_FILE | File to write instrumentation statistics. |
AFL_FRIDA_VERBOSE | Enable verbose output from Frida mode. |
Sources: frida_mode/README.md:35-43, frida_mode/README.md:83-195
Scripting with JavaScript
One of the most powerful features of Frida mode is the ability to configure it using JavaScript. By default, Frida mode looks for a file named afl.js in the target's working directory. An alternative script can be specified with the AFL_FRIDA_JS_SCRIPT environment variable.
These scripts have access to the standard Frida JavaScript API and a special Afl global object for interacting with the fuzzer. This allows for dynamic configuration of persistent loop addresses, instrumentation ranges, and even live-patching of the target binary.
The diagram below shows the interaction between a user-provided script and the Frida mode engine.
Sources: frida_mode/Scripting.md:1-7
Scripting API
The Afl object provides a comprehensive API to control the fuzzer's behavior from JavaScript.
| Method | Description |
|---|---|
Afl.addExcludedRange(address, size) | Excludes a memory range from instrumentation. |
Afl.addIncludedRange(address, size) | Includes a memory range for instrumentation. |
Afl.done() | Signals that script configuration is complete. Must be called at the end of the script. |
Afl.error(msg) | Triggers a fatal error with a custom message. |
Afl.getAflFuzzLen() | Gets a pointer to __afl_fuzz_len for in-memory fuzzing. |
Afl.getAflFuzzPtr() | Gets a pointer to __afl_fuzz_ptr for in-memory fuzzing. |
Afl.print(msg) | Prints a message to STDOUT. |
Afl.setEntryPoint(address) | Sets the program entry point. |
Afl.setInMemoryFuzzing() | Enables in-memory test case fuzzing. |
Afl.setInstrumentLibraries() | Equivalent to setting AFL_INST_LIBS=1. |
Afl.setPersistentAddress(address) | Sets the address for the persistent loop start. |
Afl.setPersistentCount(count) | Sets the number of iterations for the persistent loop. |
Afl.setPersistentHook(address) | Sets a native function hook for persistent mode. |
Afl.setStalkerCallback(callback) | Sets a function to be called for each instrumented instruction. |
Afl.setStdErr(file) | Redirects the target's stderr to a file. |
Afl.setStdOut(file) | Redirects the target's stdout to a file. |
Sources: frida_mode/Scripting.md:320-498, frida_mode/ts/lib/afl.ts:1-248
Example Script
The following example demonstrates how to set up a persistent fuzzing loop using symbols from the binary.
/* Use Afl.print instead of console.log */
Afl.print('Setting up persistent mode for fuzzing...');
/* Configure entry-point and persistence */
const persistent_addr = DebugSymbol.fromName('LLVMFuzzerTestOneInput');
Afl.print(`persistent_addr: ${persistent_addr.address}`);
Afl.setPersistentAddress(persistent_addr.address);
Afl.setPersistentCount(1000000);
/* Control instrumentation */
Afl.setInstrumentLibraries();
const mod = Process.findModuleByName("libc-2.31.so");
if (mod) {
Afl.addExcludedRange(mod.base, mod.size);
}
/* ALWAYS call this when you have finished all your configuration */
Afl.done();
Afl.print("Configuration complete. Starting fuzzing.");
Sources: frida_mode/Scripting.md:12-56
FASAN - Frida Address Sanitizer
Frida mode includes support for a binary-only Address Sanitizer, called FASAN. It works by preloading the real Address Sanitizer DSO (libclang_rt.asan-*.so) into the target process. FASAN then instruments all instructions with memory operands and inserts calls to the __asan_loadN and __asan_storeN functions provided by the ASAN DSO to validate memory accesses against the shadow memory.
This approach avoids the need to recompile standard libraries and provides significant performance benefits over instrumenting library functions at a more granular level.
To use FASAN, the ASAN DSO must be the first library in the AFL_PRELOAD environment variable.
AFL_PRELOAD=libclang_rt.asan-x86_64.so:afl-frida-trace.so \
ASAN_OPTIONS=detect_leaks=false,halt_on_error=0 \
AFL_USE_FASAN=1 \
./afl-fuzz -i in -o out -O -- ./target
Sources: frida_mode/README.md:200-226, frida_mode/DEBUGGING.md:120-128
Debugging
If you encounter issues with Frida mode, the following tips can help:
- Enable Diagnostics: Set
AFL_DEBUG_CHILD=1to see detailed diagnostic output from Frida mode. - Test Harness First: Run your harness outside of
afl-fuzzto ensure it is stable. - Isolate Frida Mode: Run the target with
LD_PRELOAD=afl-frida-trace.so ./harness <input>to see if the issue is with Frida mode itself, independent ofafl-fuzz. - Debug Persistent Mode: Use
gdbto manually step through the persistent loop initialization and sendSIGCONTsignals.
gdb \
--ex 'set environment __AFL_PERSISTENT=1' \
--ex 'set environment AFL_FRIDA_PERSISTENT_CNT=3' \
--ex 'set environment LD_PRELOAD=afl-frida-trace.so' \
--ex 'set environment AFL_FRIDA_PERSISTENT_ADDR=<entry_address>' \
--args ./harness <input>
Sources: frida_mode/DEBUGGING.md:1-4, frida_mode/DEBUGGING.md:19-21, frida_mode/DEBUGGING.md:52-61