
Suite de fuzzing in-process basée sur Frida avec proxy AFL++, modes autonomes actif/passif, et communication par mémoire partagée pour une découverte de vulnérabilités guidée par la couverture et haute performance sur plusieurs plateformes.
fpicker est une suite de fuzzing basée sur Frida qui offre une variété de modes de fuzzing pour le fuzzing in-process, comme un mode AFL++ ou un mode de traçage passif. Il devrait fonctionner sur toutes les plateformes supportées par Frida.
Des informations de contexte ainsi que les réflexions et idées derrière fpicker peuvent être trouvées dans un article de blog que j'ai écrit.
Fpicker est basé sur des travaux antérieurs sur ToothPicker, développé pendant mon mémoire de master. La majeure partie de fpicker a été développée pendant les heures de travail chez mon employeur (ERNW).
Requis pour exécuter fpicker :
frida-core-devkit pour la plateforme respective disponible sur les versions de Frida sur GitHub.
libfrida-core-ios.a, libfrida-core-macos.a ou libfrida-core-linux.a.frida-core.h). Stockez-les sous frida-core-linux.h ou frida-core-ios.h selon la plateforme.update_frida_version.sh avant la compilation.Requis uniquement en mode AFL++ :
CFLAGS="-DUSEMMAP=1".CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS".Fpicker peut être compilé pour macOS, iOS ou Linux. Le Makefile ne supporte actuellement que la compilation pour iOS sur macOS, mais il devrait être tout à fait possible de compiler fpicker en utilisant une chaîne d'outils iOS sous Linux.
Selon la cible souhaitée, exécutez :
make fpicker-macos
make fpicker-ios
make fpicker-linux
pour compiler fpicker.
Une fois fpicker compilé, le harnais de fuzzing doit être construit ensuite :
Voir le dossier examples pour différents exemples de cas de fuzzing. L'approche générale est la suivante :
examples/test/test.js) (voir ici pour plus d'informations sur les harnais)frida-compile test.js -o harness.jsMaintenant fpicker peut commencer le fuzzing. La commande exacte dépend fortement de la configuration et de la mise en place. Ci-dessous, quelques exemples de cas sont donnés. Ceux-ci correspondent principalement aux exemples du dossier examples.
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"
Chaque cible nécessite son propre harnais de fuzzing. La partie la plus importante de ce harnais est la définition de la fonction d'entrée du Stalker de Frida, qui détermine effectivement à quel point l'instrumentation est insérée. En mode in-process, c'est simple. La fonction est généralement celle qui est appelée à chaque itération de fuzzing. Cependant, ce pourrait être une autre.
Un exemple d'implémentation minimaliste de harnais (en mode command) pourrait être celui-ci :
// 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;
Ce harnais configure l'instrumentation pour suivre la fonction FUZZ_FUNCTION. L'instrumentation démarre lorsque cette fonction est entrée et s'arrête lorsque la fonction retourne. Cette fonction doit être choisie avec soin car elle est coûteuse et plus les parties (potentiellement sans importance) du processus sont instrumentées, plus le fuzzer est lent. Bien sûr, c'est un compromis entre vitesse et couverture souhaitée. De plus, le fuzzer ne supporte actuellement que les fonctions qui ne sont entrées qu'une seule fois par itération de fuzzing, c'est-à-dire que la fonction ne doit pas être appelée plus d'une fois par cas de fuzz, sinon les informations de couverture pourraient devenir peu fiables.
Lorsque le mode in-process est utilisé, une autre fonction est requise dans le script du fuzzer. La méthode fuzz. Elle sera appelée à chaque itération. Elle sera appelée avec deux paramètres : un pointeur vers un tampon et la longueur du tampon. Notre fonction cible exemple prend deux paramètres : un pointeur vers un tampon et sa longueur. Ainsi, nous pouvons simplement passer les paramètres que nous recevons dans la méthode fuzz.
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}