
fpicker は Frida ベースのファジングスイートであり、AFL++ モードやパッシブトレーシングモードなど、プロセス内ファジングのためのさまざまなファジングモードを提供します。Frida がサポートするすべてのプラットフォームで動作するはずです。
fpicker の背景や考え方については、私が書いたブログ記事を参照してください。
fpicker は、修士論文の際に開発した ToothPicker のこれまでの取り組みに基づいています。fpicker の大半は、雇用先 (ERNW) での就業時間内に開発されました。
fpicker の実行に必要なもの:
frida-core-devkit
libfrida-core-ios.a、libfrida-core-macos.a、または libfrida-core-linux.a として保存します。frida-core.h) も同様です。プラットフォームに応じて frida-core-linux.h または frida-core-ios.h として保存します。update_frida_version.sh を実行するだけです。AFL++ モードで実行する場合のみ必要:
CFLAGS="-DUSEMMAP=1" でコンパイルします。CFLAGS="-DUSEMMAP=1 -DTARGET_OS_IOS" でコンパイルします。fpicker は macOS、iOS、または Linux 向けにビルドできます。現在の Makefile は macOS 上での iOS 向けビルドのみサポートしていますが、Linux 上の iOS ツールチェーンを使用して fpicker をビルドすることも十分可能です。
目的のターゲットに応じて、以下を実行します:
make fpicker-macos
make fpicker-ios
make fpicker-linux
fpicker をビルドします。
fpicker がビルドされたら、次にファジングハーネスをビルドする必要があります:
さまざまなサンプルファジングケースについては、examples フォルダ を参照してください。一般的なアプローチは次のとおりです:
examples/test/test.js) (ハーネスの詳細についてはこちらを参照)frida-compile test.js -o harness.jsこれで fpicker はファジングを開始できます。正確なコマンドは設定とセットアップに大きく依存します。以下にいくつかの例を示します。これらは主に 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"
各ターゲットには独自のファジングハーネスが必要です。このハーネスの最も重要な部分は、Frida の Stalker のエントリ関数を定義することです。これにより、インストルメンテーションが挿入されるポイントが実質的に決まります。in-process モードではこれは簡単です。通常、関数は各ファジングイテレーションで呼び出される関数になります。ただし、別の関数でも構いません。
最小限のハーネス実装 (command モード) は次のようになります:
// 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;
このハーネスは、関数 FUZZ_FUNCTION を追跡するようにインストルメンテーションを設定します。この関数が入力されたときにインストルメンテーションが開始され、関数が戻るときに停止します。この関数は慎重に選択する必要があります。なぜなら、コストが高く、プロセスの(潜在的に重要でない)部分が多くインストルメンテーションされるほど、ファザーは遅くなるからです。もちろん、これは速度と意図するカバレッジの間のトレードオフです。さらに、現在のファザーは、1回のファジングイテレーション中に1回だけ入力される関数のみをサポートしています。つまり、1つのファジケース中に関数が複数回呼び出されてはならず、そうしないとカバレッジ情報が信頼できなくなる可能性があります。
in-process モードを使用する場合、ファザースクリプトには別の関数が必要です。それが fuzz メソッドです。これは各イテレーションで呼び出されます。このメソッドは、バッファへのポインタとバッファの長さの2つのパラメータで呼び出されます。例示的なターゲット関数は、バッファへのポインタとその長さの2つのパラメータを受け取ります。したがって、fuzz メソッドで受け取ったパラメータをそのまま渡すことができます。
fuzz(payload, len) {
this.target_function(payload, parseInt(len));
}
passive モードでは、必要なデータを処理するコールバックを指定する必要があります。ファザーはペイロードバッファとその長さを受け取ることを期待します。ファジングされるターゲット関数に応じて、このデータを抽出する必要があります。次の例では、再び2つのパラメータ(バッファへのポインタとその長さ)を持つ関数があります。args パラメータにはターゲット関数が受け取るすべてのパラメータが含まれているため、長さパラメータ(この場合は2番目)には args[1] でアクセスできます。次に、バッファを Uint8Array として読み取り、sendPassiveCorpus メソッドを使用してファザーに送り返します。
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);
}
ターゲットがファザーを開始する前に何らかの準備を必要とする場合、fpicker は prepare メソッドを提供します。これはファザーの初期化中に呼び出されます。準備とは、例えばオブジェクトのインスタンス化など、状態を確立することです。そのような準備関数は次のようになります:
prepare() {
// the object can be attached to the fuzzer instance so that it can be used within the
// fuzz() method later on.
this.required_object = call_native_function_that_creates_object();
}
fpicker は、以下で説明する多数のモードと設定を提供します。これらのモードのほとんどはさまざまに組み合わせることができます。このセクションの最後に、どのオプションを組み合わせることができ、その実装状況を示す表があります。
fpicker には3つの異なる ファジングモード があります: AFL++ モード、スタンドアロンアクティブモード、スタンドアロンパッシブモード:
AFL++ モード: AFL++ モードでは、fpicker は AFL++ とターゲットプロセス間のプロキシとして機能します。Frida のインストルメンテーション機能を使用して、AFL のカバレッジビットマップが更新され、AFL++ によって生成された入力データでターゲットがファジングされます。
スタンドアロンアクティブモード: スタンドアロンアクティブモードでは、ファザーは Frida の Stalker コールサマリー を使用して、イテレーション中に実行された基本ブロックの形でカバレッジを収集します。これは新しいことではなく、これまでにもさまざまな形で実装されてきました。しかし、他のファザー設定と組み合わせることで、さまざまな利点が得られます。また、特定の環境やケースで AFL++ が適用できない、または望ましくない場合の良い代替手段でもあります。
スタンドアロンパッシブモード: パッシブモードは、ファザーというよりトレーサーに近いものです。基本的にはスタンドアロンアクティブモードと同じことを行います。ただし、独自の入力を送信 しません。特定の関数にアタッチしてカバレッジを収集するだけです。新しいカバレッジが観測されると、カバレッジと入力の両方が保存されます。
fpicker は主にプロセス内ファザーとして設計されていますが、外部コマンドを介したファジングもサポートしています。そのために、fpicker は2つの入力モードを提供します。
入力モード インプロセス: インプロセス入力モードでは、ハーネスがターゲットプロセスの指定された関数を直接呼び出します。ファザーはペイロードをハーネスに送信し、ハーネスはターゲット関数を呼び出せるようにペイロードを準備します。
入力モード CMD: コマンド入力モードでは、ペイロードは外部コマンドにリダイレクトされます。これは、ターゲット関数を直接呼び出すときにパラメータやその他の状態を準備するのが複雑すぎる場合に便利です。カバレッジ収集は依然として特定の関数にアタッチする必要があります。ペイロードを供給してターゲット関数をトリガーできるクライアントが存在するかもしれません。
通信モードは、注入されたハーネスがファザーとどのように通信するかを決定します。これはターゲットアプリケーションに大きく依存します。Frida は、注入されたエージェントスクリプトとの間でメッセージを送受信するための API を提供しています。このタイプの通信は非常にコストがかかります。要因の1つは、転送されるメッセージを JSON でエンコードする必要があることです。そのため、バイナリデータの送信は簡単です。したがって、fpicker は共有メモリを介した2つ目の通信モードを提供しています。ただし、これはファザーとターゲットアプリケーションの間で共有メモリを確立できる場合にのみ機能します。つまり、ターゲットが USB 経由でファザーホストに接続されている場合、このモードは使用できません。CMD 入力モードでは、通信モードはカバレッジ情報がファザーにどのように伝達されるかのみを指し、ペイロードの送信方法は指しません。これは外部コマンドに委ねられるためです。
通信モード 送信: 送信通信モードでは、ペイロードは Frida の RPC 呼び出しメカニズムを使用して送信されます。これにより、ファザーは注入されたハーネススクリプト内の JavaScript 関数を実行できます。ハーネス内のこの関数は、ターゲット関数を呼び出すために必要なすべての準備を行うことができます。ターゲット関数から戻ると、カバレッジ収集は停止し、ハーネスは Frida の send API を使用してカバレッジ情報をファザーに送り返すことで、イテレーションが終了したことを通知できます。
通信モード SHM: SHM 通信モードでは、ファザーとハーネススクリプトは共有メモリとセマフォを介して通信します。共有メモリ内のバッファを使用してペイロードを送信し、カバレッジ情報を受信します。送受信の代わりに、2つのコンポーネントはセマフォの待機とポストを使用します。システムとターゲットによっては、これによりかなりのパフォーマンス向上が得られます。特に、バイナリペイロードはメモリに一度書き込まれ、エンコード/デコードしたり、他のメモリ位置にコピーしたりする必要がないためです。残念ながら、このモードは AFL++ で実行すると安定性が低くなることがあります。理由はまだ不明です。
実行モードは spawn または attach のいずれかです。これはほぼ自明です。fpicker は実行中のプロセスにアタッチするか、プロセスを起動することができます。2つのモードの主な違いの1つは、アタッチしたターゲットがクラッシュした場合、fpicker は再起動を試みないことです。
スタンドアロンモードでは、fpicker は3つの異なる入力変異戦略を提供します。控えめに言っても、入力変異には改善の余地が多くあります。
スタンドアロンミューテーター NULL: このミューテーターはペイロードを変異させず、同じペイロードのコピーを返すだけです。主にテスト目的です。それ以外ではあまり有用ではありません。
スタンドアロンミューテーター Rand: 非常に悪いランダムミューテーターです。元のペイロードのランダムな位置の値をランダムに置き換えるだけです。ペイロード長は変更しません。
スタンドアロンミューテーター カスタム: このミューテーターは外部コマンドを呼び出してペイロードを変異させることができます。ペイロードを stdin に書き込み、変異されたペイロードを stdout から受け取ります。実装が浅いため、かなりのパフォーマンスへの影響があります。
-D usb デバイスオプションを使用すると、Frida は最初のローカル USB デバイスを選択します。例えば iPhone や Android フォンなどです。
-D remote オプションを使用すると、ネットワークデバイス上で実行中のプロセスをファジングすることができます。そのためには、リモートデバイスで frida-server が実行されている必要があります。設定例として、SSH とポートフォワーディングを使用して、リモートデバイス上の frida-server のデフォルト待受ポート 27042 をローカルクライアントのソケットにバインドします。
ssh -N [email protected] -L 127.0.0.1:27042:127.0.0.1:27042
iPhone では、iproxy を使用して USB 接続からポートを転送することもできます。これは、非脱獄デバイスで Frida ガジェットとともに Frida を非標準ポートで実行する場合に特に有用かもしれません。Frida ガジェットを使用する場合、利用可能なプロセスはターゲットアプリ名に関係なく Gadget という名前になります。
iproxy 27042 27042
次に、frida-ps を使用して、リモートデバイス上のプロセスを一覧表示することで設定を検証します:
frida-ps -R