
当初は Michal Zalewski [email protected] によって開発されました。
このファイルを読む時間がない場合は、 QuickStartGuide.txt を参照してください。
ファジングは、実世界のソフトウェアにおけるセキュリティ問題を特定するための、 最も強力で実証済みの戦略の1つです。これまでにセキュリティ上重要なソフトウェアで 発見されたリモートコード実行や権限昇格のバグの大部分は、 この手法によるものです。
残念ながら、ファジングは比較的浅い手法でもあります。盲目的でランダムな変異では、 テスト対象コードの特定のコードパスに到達する可能性が非常に低く、 一部の脆弱性はこの手法の及ばない範囲に取り残されます。
この問題を解決しようとする試みは数多く行われてきました。初期のアプローチの1つで あるコーパス蒸留(corpus distillation)は、Tavis Ormandy によって先駆けられました。 この方法は、カバレッジシグナルを利用して、大規模で高品質な候補ファイルの コーパスから興味深いシードのサブセットを選択し、それを 従来の手段でファジングするものです。このアプローチは非常にうまく機能しますが、 そのようなコーパスが容易に入手可能であることが必要です。さらに、ブロックカバレッジの 測定では、プログラムの状態について非常に単純化した理解しか得られず、 長期的にファジングの取り組みを導く上ではあまり役に立ちません。
その他のより高度な研究では、プログラムフロー解析(「concolic execution」)、 シンボリック実行、静的解析などの技術に焦点が当てられてきました。 これらの方法はすべて実験環境では非常に有望ですが、 実用面では信頼性とパフォーマンスの問題を抱える傾向があり、 現時点では「dumb」ファジング技術に代わる実用的な選択肢にはなっていません。
American Fuzzy Lop は、極めてシンプルながら盤石な計装ガイド付き遺伝的アルゴリズムを 備えたブルートフォースファザーです。プログラムの制御フローに対する 微妙で局所的な変化を簡単に検出するために、修正されたエッジカバレッジを 使用します。
少し単純化すると、アルゴリズム全体は次のようにまとめられます。
ユーザーが指定した初期テストケースをキューに読み込む。
キューから次の入力ファイルを取り出す。
テストケースを、プログラムの測定された動作を変えない 最小サイズにトリムする。
バランスが取れ、十分に研究されたさまざまな従来のファジング戦略を 使用してファイルを繰り返し変異させる。
生成された変異のいずれかが、計装によって記録された新しい状態遷移を もたらした場合、変異した出力をキューの新しいエントリとして 追加する。
2に戻る。
発見されたテストケースはまた、より新しく、より高いカバレッジを持つ発見によって 陳腐化したものを除外するために定期的に間引かれ、さらにいくつかの 計装駆動の労力最小化ステップを受けます。
ファジングプロセスの副次的な結果として、このツールは興味深いテストケースの 小規模で自己完結したコーパスを作成します。これらは、労力やリソースを 大量に消費する他のテスト体制のシードとして非常に役立ちます。例えば、 ブラウザ、オフィスアプリケーション、グラフィックススイート、または クローズドソースツールのストレステストなどです。
このファザーは、すぐに使えるパフォーマンスが盲目的ファジングや カバレッジのみのツールをはるかに上回ることを保証するため、徹底的にテストされています。
ソースコードが利用可能な場合、サードパーティコードの標準的なビルドプロセスにおいて gcc や clang のドロップイン置換として機能するコンパニオンツールによって、 計装を注入できます。
この計装によるパフォーマンスへの影響はかなり小さいものです。 afl-fuzz が実装する他の最適化と組み合わせることで、ほとんどのプログラムは 従来のツールと同程度か、それ以上に高速にファジングできます。
ターゲットプログラムを再コンパイルする正しい方法はビルドプロセスの詳細によって 異なる場合がありますが、ほぼ普遍的なアプローチは次のとおりです。```shell $ CC=/path/to/afl/afl-gcc ./configure $ make clean all
C++プログラムの場合、`CXX=/path/to/afl/afl-g++` も設定する必要があります。
clangラッパー(afl-clangおよびafl-clang++)も同様に使用できます。clangユーザーは、llvm_mode/README.llvmで説明されているように、より高性能なインストルメンテーションモードを利用することもできます。
ライブラリをテストする場合、stdinまたはファイルからデータを読み取り、テスト対象ライブラリに渡す単純なプログラムを見つけるか作成する必要があります。そのような場合、この実行可能ファイルをインストルメント化されたライブラリの静的バージョンにリンクするか、実行時に正しい.soファイルが読み込まれるようにすることが不可欠です(通常は`LD_LIBRARY_PATH`を設定します)。最も簡単な方法は静的ビルドで、通常は次のようにして可能です:```shell
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
AFL_HARDEN=1 を設定して 'make' を呼び出すと、CC ラッパーが自動的にコード強化オプションを有効にし、単純なメモリバグを検出しやすくなります。AFL に同梱されているヘルパーライブラリである Libdislocator (libdislocator/README.dislocator を参照) も、ヒープ破壊の問題を発見するのに役立ちます。
PS. ASAN ユーザーは、重要な注意事項について notes_for_asan.txt ファイルを確認することをお勧めします。
ソースコードが 利用できない 場合、ファザーはブラックボックスバイナリに対する高速かつオンザフライのインストルメンテーションを実験的にサポートしています。これは、あまり知られていない "ユーザースペースエミュレーション" モードで動作する QEMU のバージョンによって実現されます。
QEMU は AFL とは別のプロジェクトですが、次のようにしてこの機能を簡単にビルドできます:```shell $ cd qemu_mode $ ./build_qemu_support.sh
追加の手順と注意事項については、qemu_mode/README.qemu を参照してください。
このモードはコンパイル時計装よりも約2〜5倍遅く、
並列化にはあまり適しておらず、その他の癖がある場合もあります。
## 5) 初期テストケースの選択
正しく動作させるには、ファザーは1つ以上の開始ファイルを必要とします。
開始ファイルには、対象アプリケーションが通常期待する入力データの良い例が含まれている必要があります。
基本的なルールは2つあります。
- ファイルは小さく保ってください。1 kB未満が理想的ですが、必須ではありません。
サイズが重要な理由については、[perf_tips.txt](https://github.com/google/afl/blob/master/docs/perf_tips.txt) を参照してください。
- 複数のテストケースを使用するのは、各テストケースが機能的に異なる場合のみにしてください。
50種類の異なるバケーション写真を使って画像ライブラリを
ファズしても意味はありません。
このツールに同梱されている testcases/ サブディレクトリには、
開始ファイルの良い例が多数あります。
PS. スクリーニング用に大規模なデータコーパスが利用できる場合は、
afl-cmin ユーティリティを使用して、機能的に異なるファイルのサブセットを特定するとよいでしょう。
それらのファイルは、対象バイナリ内の異なるコードパスを実行します。
## 6) バイナリのファジング
ファジングプロセス自体は、afl-fuzz ユーティリティによって実行されます。このプログラムは、
初期テストケースが入った読み取り専用ディレクトリ、調査結果を保存するための別の場所、
そしてテスト対象バイナリへのパスを必要とします。
標準入力から直接入力を受け取る対象バイナリの場合、通常の構文は次のとおりです:```shell
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
ファイルから入力を受け取るプログラムの場合、'@@' を使用して、対象のコマンドラインで入力ファイル名を配置する場所をマークします。ファザーがこれを置き換えます:```shell $ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
-fオプションを使用すると、変異したデータを特定のファイルに書き込むこともできます。
これは、プログラムが特定のファイル拡張子などを期待する場合に便利です。
インストルメントされていないバイナリは、QEMUモード(コマンドラインに-Qを追加)または
従来のブラインドファザーモード(-nを指定)でファズできます。
-tおよび-mを使用すると、実行プロセスのデフォルトのタイムアウトとメモリ制限を上書きできます。
これらの設定を調整する必要があるまれなターゲットの例としては、コンパイラや
ビデオデコーダがあります。
ファジングパフォーマンスを最適化するためのヒントは、[perf_tips.txt](https://github.com/google/afl/blob/master/docs/perf_tips.txt)で説明されています。
afl-fuzzは、最初に一連の決定論的ファジングステップを実行することに注意してください。
これには数日かかる場合がありますが、きれいなテストケースを生成する傾向があります。
zzufや他の従来のファザーと同様に、すぐに手っ取り早い結果が欲しい場合は、
コマンドラインに-dオプションを追加してください。
## 7) 出力の解釈
[status_screen.txt](https://github.com/google/afl/blob/master/docs/status_screen.txt)ファイルには、表示された統計情報の解釈方法と
プロセスの健全性を監視する方法に関する情報が記載されています。
特にUI要素が赤でハイライトされている場合は、必ずこのファイルを
確認してください。
ファジングプロセスは、Ctrl-Cを押すまで継続されます。最低でも、
ファザーに1回のキューサイクルを完了させる必要があります。これには、
数時間から1週間程度かかることがあります。
出力ディレクトリ内には、次の3つのサブディレクトリが作成され、
リアルタイムで更新されます:
- queue/ - すべての特徴的な実行パスに対するテストケースと、ユーザーが指定した開始ファイル
が含まれます。これは、セクション2で説明した合成コーパス
です。
このコーパスを他の目的で使用する前に、afl-cminツールを使用してより小さなサイズに縮小できます。
このツールは、同等のエッジカバレッジを提供するより小さなファイルのサブセットを
見つけます。
- crashes/ - テスト対象プログラムが致命的なシグナル(例: SIGSEGV、SIGILL、SIGABRT)を
受信する原因となる一意のテストケース。エントリは受信したシグナルごとに
グループ化されます。
- hangs/ - テスト対象プログラムがタイムアウトする原因となる一意のテストケース。
何かがハングアップと分類されるまでのデフォルトの時間制限は、1秒と-tパラメータの値の
うち大きい方です。
この値はAFL_HANG_TMOUTを設定することで微調整できますが、
これが必要になることはほとんどありません。
クラッシュとハングは、関連する実行パスに、以前に記録された障害では見られなかった
状態遷移が含まれる場合に「一意」と見なされます。
単一のバグに複数の方法で到達できる場合、プロセスの初期にはカウントが膨らむことがありますが、
すぐに落ち着くはずです。
クラッシュとハングのファイル名は、親の、障害を発生させないキューエントリと
関連付けられています。これはデバッグに役立ちます。
afl-fuzzによって発見されたクラッシュを再現できない場合、最も可能性の高い原因は、
ツールが使用するのと同じメモリ制限を設定していないことです。試してください:```shell
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10]; /path/to/tested_binary ... )
LIMIT_MB を afl-fuzz に渡す -m パラメータに合わせて変更してください。OpenBSD では、 -Sv も -Sd に変更してください。
既存の出力ディレクトリは、中断されたジョブの再開にも使用できます。試してください:```shell $ ./afl-fuzz -i- -o existing_output_dir [...etc...]
gnuplotがインストールされていれば、afl-plotを使って実行中のファジングタスクのきれいなグラフを生成することもできます。実際の例としては、[http://lcamtuf.coredump.cx/afl/plot/](http://lcamtuf.coredump.cx/afl/plot/)を参照してください。
## 8) 並列ファジング
各afl-fuzzインスタンスはおよそ1コアを占有します。つまり、マルチコアシステムではハードウェアを完全に活用するために並列化が必要です。複数のコアや複数のネットワーク接続されたマシン上で共通のターゲットをファジングする方法のヒントについては、[parallel_fuzzing.txt](https://github.com/google/afl/blob/master/docs/parallel_fuzzing.txt)を参照してください。
並列ファジングモードは、AFLを他のファザー、シンボリック実行エンジン、コンコリック実行エンジンなどと連携させる簡単な方法も提供します。これについても、[parallel_fuzzing.txt](https://github.com/google/afl/blob/master/docs/parallel_fuzzing.txt)の最後のセクションを参照してください。
## 9) ファザーディクショナリ
デフォルトでは、afl-fuzzのミューテーションエンジンはコンパクトなデータ形式(画像、マルチメディア、圧縮データ、正規表現構文、シェルスクリプトなど)向けに最適化されています。特に冗長で反復的な表現が多い言語(代表的なものとしてHTML、SQL、JavaScriptなど)にはあまり適していません。
構文を認識するツールを構築する手間を避けるため、afl-fuzzは、対象となるデータ型に関連する言語キーワード、マジックヘッダー、その他の特殊トークンのオプションのディクショナリをファジングプロセスにシードし、それを使って基礎となる文法をその場で再構築する方法を提供します:
[http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html](http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html)
この機能を使用するには、まずdictionaries/README.dictionariesで説明されている2つの形式のいずれかでディクショナリを作成し、次にコマンドラインの-xオプションでファザーにそれを指定する必要があります。
(そのサブディレクトリには、いくつかの一般的なディクショナリもすでに用意されています。)
基礎となる構文のより構造化された記述を提供する方法はありませんが、ファザーはインストルメンテーションフィードバックだけに基づいてその一部を推測できる可能性が高いです。これは実際に機能します。例えば:
[http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html](http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html)