
Suite de fuzzing in-process baseada em Frida com proxy AFL++, modos ativo/passivo independentes e comunicação por memória compartilhada para descoberta de vulnerabilidades orientada por cobertura de alto desempenho em várias plataformas.
fpicker é um conjunto de fuzzing baseado em Frida que oferece uma variedade de modos de fuzzing para fuzzing em processo, como um modo AFL++ ou um modo de rastreamento passivo. Deve funcionar em todas as plataformas suportadas pelo Frida.
Algumas informações de contexto e os pensamentos e ideias por trás do fpicker podem ser encontrados em um post de blog que escrevi.
Fpicker é baseado em esforços anteriores em ToothPicker, que foi desenvolvido durante minha dissertação de mestrado. A maior parte do fpicker foi desenvolvida durante o horário de trabalho no meu empregador (ERNW).
Requisitos para executar o fpicker:
frida-core-devkit para a respectiva plataforma encontrado em Frida releases no GitHub.
libfrida-core-ios.a, libfrida-core-macos.a ou libfrida-core-linux.a.frida-core.h). Armazene-os como frida-core-linux.h ou frida-core-ios.h dependendo da plataforma.update_frida_version.sh antes de compilar.Requerido apenas ao executar no modo AFL++:
CFLAGS="-DUSEMMAP=1".CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS".O Fpicker pode ser compilado para macOS, iOS ou Linux. O Makefile atualmente suporta apenas a compilação para iOS no macOS, mas deve ser totalmente possível compilar o fpicker usando um toolchain do iOS no Linux.
Dependendo do alvo desejado, execute:
make fpicker-macos
make fpicker-ios
make fpicker-linux
para compilar o fpicker.
Após compilar o fpicker, o harness de fuzzing precisa ser compilado em seguida:
Veja a pasta de exemplos para diferentes casos de fuzzing de exemplo. A abordagem geral é a seguinte:
examples/test/test.js) (veja
aqui para mais informações sobre harnesses)frida-compile test.js -o harness.jsAgora o fpicker pode começar a fazer fuzzing. O comando exato depende muito da configuração e da montagem. A seguir, alguns casos de exemplo são fornecidos. Eles correspondem principalmente aos exemplos na pasta de exemplos.
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 alvo requer seu próprio harness de fuzzing. A parte mais importante deste harness é definir a função de entrada do Stalker do Frida, que efetivamente determina em que ponto a instrumentação é inserida. No modo in-process isso é simples. A função geralmente seria a que é chamada em cada iteração de fuzzing. No entanto, poderia ser uma diferente.
Uma implementação mínima de harness (no modo command) poderia 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 a instrumentação para seguir a função FUZZ_FUNCTION. A instrumentação começará quando esta função for inserida e para quando a função retornar. Esta função deve ser escolhida cuidadosamente, pois é cara e quanto mais partes (potencialmente não importantes) do processo forem instrumentadas, mais lento o fuzzer fica. Claro, isso é uma consideração entre velocidade e cobertura pretendida. Além disso, o fuzzer atualmente suporta apenas funções que são inseridas apenas uma vez durante uma iteração de fuzzing, ou seja, a função não deve ser chamada mais de uma vez durante um caso de fuzzing, caso contrário as informações de cobertura podem se tornar não confiáveis.
Quando o modo in-process é usado, outra função é necessária no script do fuzzer. O método fuzz. Ele será chamado em cada iteração. Será chamado com dois parâmetros, um ponteiro para um buffer e o comprimento do buffer. Nossa função alvo exemplar recebe dois parâmetros, um ponteiro para um buffer e seu comprimento. Assim, podemos simplesmente passar os parâmetros que recebemos no método fuzz.
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}