
Suite de fuzzing in-process basada en Frida con proxy AFL++, modos activo/pasivo independientes y comunicación por memoria compartida para descubrimiento de vulnerabilidades guiado por cobertura de alto rendimiento entre plataformas.
fpicker es un conjunto de herramientas de fuzzing basado en Frida que ofrece una variedad de modos de fuzzing para fuzzing en proceso, como un modo AFL++ o un modo de rastreo pasivo. Debería funcionar en todas las plataformas compatibles con Frida.
Alguna información de contexto y las ideas detrás de fpicker se pueden encontrar en un blogpost que escribí.
Fpicker se basa en esfuerzos anteriores como ToothPicker, que fue desarrollado durante mi tesis de maestría. La mayor parte de fpicker se desarrolló durante horas de trabajo en mi empleador (ERNW).
Requerido para ejecutar fpicker:
frida-core-devkit para la plataforma respectiva que se encuentra en Lanzamientos de Frida en GitHub.
libfrida-core-ios.a, libfrida-core-macos.a o libfrida-core-linux.a.frida-core.h). Guárdalos como frida-core-linux.h o frida-core-ios.h según la plataforma.update_frida_version.sh antes de compilar.Requerido solo cuando se ejecuta en modo AFL++:
CFLAGS="-DUSEMMAP=1".CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS".Fpicker se puede compilar para macOS, iOS o Linux. El Makefile actualmente solo admite la compilación para iOS en macOS, pero debería ser totalmente posible compilar fpicker usando un toolchain de iOS en Linux.
Dependiendo del objetivo deseado, ejecute:
make fpicker-macos
make fpicker-ios
make fpicker-linux
para compilar fpicker.
Una vez que fpicker está compilado, el harness de fuzzing debe compilarse a continuación:
Consulte la carpeta de ejemplos para diferentes casos de muestra de fuzzing. El enfoque general es el siguiente:
examples/test/test.js) (consulte aquí para obtener más información sobre los harnesses)frida-compile test.js -o harness.jsAhora fpicker puede comenzar a fuzzear. El comando exacto depende en gran medida de la configuración y el entorno. A continuación se presentan algunos casos de ejemplo. Estos corresponden en su mayoría a los ejemplos en la carpeta de ejemplos.
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"
Cada objetivo requiere su propio harness de fuzzing. La parte más importante de este harness es definir la función de entrada de Stalker de Frida, que determina efectivamente en qué punto se inserta la instrumentación. En el modo in-process esto es simple. La función suele ser la que se llama en cada iteración de fuzzing. Sin embargo, también podría ser una diferente.
Una implementación mínima de harness (en modo command) podría ser esta:
// 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;
Este harness configura la instrumentación para seguir la función FUZZ_FUNCTION. La instrumentación comenzará cuando se ingrese a esta función y se detendrá cuando la función retorne. Esta función debe elegirse con cuidado, ya que es costosa y cuantas más partes (potencialmente sin importancia) del proceso se instrumenten, más lento se vuelve el fuzzer. Por supuesto, esto es una consideración entre velocidad y cobertura deseada. Además, el fuzzer actualmente solo admite funciones que se ingresan solo una vez durante una iteración de fuzzing, es decir, la función no debe ser llamada más de una vez durante un caso de fuzz, de lo contrario, la información de cobertura podría volverse poco confiable.
Cuando se usa el modo in-process, se requiere otra función en el script del fuzzer: el método fuzz. Se llamará en cada iteración. Se llamará con dos parámetros: un puntero a un búfer y la longitud del búfer. Nuestra función objetivo de ejemplo toma dos parámetros: un puntero a un búfer y su longitud. Por lo tanto, podemos pasar los parámetros que recibimos en el método fuzz.
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}