
Frida-based in-process fuzzing suite with AFL++ proxy, standalone active/passive modes, and shared memory communication for high-performance coverage-guided vulnerability discovery across platforms.
fpicker is a Frida-based fuzzing suite that offers a variety of fuzzing modes for in-process fuzzing, such as an AFL++ mode or a passive tracing mode. It should run on all platforms that are supported by Frida.
Some background information and the thoughts and ideas behind fpicker can be found in a blogpost I wrote.
Fpicker is based on previous efforts on ToothPicker, which was developed during my master thesis. Most of fpicker was developed during working hours at my employer (ERNW).
Required for running fpicker:
frida-core-devkit for the respective platform found at Frida releases on GitHub.
libfrida-core-ios.a, libfrida-core-macos.a, or libfrida-core-linux.a.frida-core.h). Store them as frida-core-linux.h or frida-core-ios.h depending on the platform.update_frida_version.sh before building.Required only when running in AFL++ mode:
CFLAGS="-DUSEMMAP=1".CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS".Fpicker can be built for macOS, iOS or Linux. The Makefile currently only supports building
for iOS on macOS but it should be totally possible to build fpicker using an iOS toolchain on
Linux.
Depending on the desired target run:
make fpicker-macos
make fpicker-ios
make fpicker-linux
to build fpicker.
Once fpicker is built, the fuzzing harness needs to be built next:
See the examples folder for different sample fuzzing cases. The general approach is as follows:
examples/test/test.js) (see
here for more information on harnesses)frida-compile test.js -o harness.jsNow fpicker can start fuzzing. The exact command highly depends on the configuration and setup. In the following, a few example cases are given. These mostly correspond to the examples in the examples folder.
afl-fuzz -i examples/test-network/in -o ./examples/test-network/out -- \\
./fpicker --fuzzer-mode afl -e attach -p test-network -f ./examples/test-network/harness.js
./fpicker --fuzzer-mode standalone -e attach -p server-process -f harness.js --input-mode cmd \\
--command "./client-send @@" -i indir -o outdir
./fpicker --fuzzer-mode active --communication-mode shm -e attach -p server-process -f harness.js \\
-i indir -o outdir --standalone-mutator cmd --mutator-command "radamsa"
./fpicker --fuzzer-mode passive --communication-mode send -e attach -p server-process -o outdir -f harness.js
./fpicker --fuzzer-mode active -e attach -p test -D remote -o examples/test/out/ -i examples/test/in/ \\
-f fuzzer-agent.js --standalone-mutator cmd --mutator-command "radamsa"
Each target requires its own fuzzing harness. The most important part of this harness is defining
the entry function of Frida's Stalker, which effectively determines at which point the
instrumentation is inserted. In the in-process mode this is simple. The function would usually
be the one that is called on each fuzzing iteration. However, it could also be a different one.
A minimalist harness implementation (in command mode) could be this:
// Import the fuzzer base class
const Fuzzer = require("harness/fuzzer.js");
// The custom fuzzer needs to subclass the Fuzzer class to work properly
class TestFuzzer extends Fuzzer.Fuzzer {
constructor() {
// The constructor needs to specify the address of the targeted function and a NativeFunction
// object that can later be called by the fuzzer.
const FUZZ_FUNCTION_ADDR = Module.getExportByName(null, "FUZZ_FUNCTION");
const FUZZ_FUNCTION = new NativeFunction(
FUZZ_FUNCTION_ADDR,
"void", ["pointer", "int64"], {
});
super("test", FUZZ_FUNCTION_ADDR, FUZZ_FUNCTION);
}
}
const f = new TestFuzzer();
exports.fuzzer = f;
This harness configures the instrumentation to follow the function FUZZ_FUNCTION. The
instrumentation will start when this function is entered and stops when the function returns.
This function should be chosen carefully as it is expensive and the more (potentially
unimportant) parts of the process are instrumented, the slower the fuzzer gets. Of course, this is
a consideration between speed and intended coverage. Additionally, the fuzzer currently only
supports functions that are only entered once during one fuzzing iteration, i.e., the function
should not be called more than once during one fuzz case, otherwise the coverage information
might become unreliable.
When the in-process mode is used, another function is required in the fuzzer script. The fuzz
method. It will get called on each iteration. It will be called with two parameters, a pointer
to a buffer and the length of the buffer. Our exemplary target function takes two parameters, a
pointer to a buffer and its length. Thus, we can just pass the parameters were getting in the
fuzz method.
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}
In passive mode, a callback needs to be specified that processes the required data. The fuzzer
expects to receive a payload buffer and its length. Depending on the target function that is
fuzzed, this data needs to be extracted. In the following example, we again have a function that
has two parameters: a pointer to a buffer and its length. The args parameter contains all
potential parameters the target function receives, so the length parameter (which is the second
one in our case) can be accessed with args[1]. We then read the buffer as Uint8Array and send
it back to the fuzzer using the sendPassiveCorpus method.
passiveCallback(args) {
const len = args[1];
const data = new Uint8Array(Memory.readByteArray(args[0], parseInt(len)));
// this encodes the data and sends it back to the fuzzer
this.sendPassiveCorpus(data, len);
}