元々は Michal Zalewski lcamt[email protected] によって開発されました。
このファイルを読む時間がない場合は、QuickStartGuide.txt を参照してください。
ファジングは、現実世界のソフトウェアのセキュリティ問題を特定するための最も強力で実証済みの戦略の 1 つです。これは、セキュリティ クリティカルなソフトウェアでこれまでに見つかったリモート コード実行および権限昇格のバグの大部分の原因です。
残念ながら、ファジングも比較的浅いものです。盲目的でランダムな突然変異により、テストされたコード内の特定のコード パスに到達する可能性が非常に低くなり、一部の脆弱性はこの手法の範囲外に完全に残されます。
この問題を解決するために数多くの試みがなされてきました。 Tavis Ormandy によって開拓された初期のアプローチの 1 つは、コーパス蒸留です。この方法は、カバレッジ信号に依存して、候補ファイルの大量の高品質コーパスから興味深いシードのサブセットを選択し、従来の手段でそれらをファジングします。このアプローチは非常にうまく機能しますが、そのようなコーパスがすぐに利用できる必要があります。さらに、ブロック カバレッジの測定は、プログラムの状態を非常に単純化して理解することしかできず、長期にわたるファジング作業の指針としてはあまり役に立ちません。
その他、より洗練された研究では、プログラム フロー分析 (「コンコリック実行」)、シンボリック実行、静的分析などの手法に焦点を当てています。これらの手法はすべて、実験環境では非常に有望ですが、実際の使用では信頼性とパフォーマンスの問題に悩まされる傾向があり、現時点では「愚かな」ファジング手法に代わる実行可能な代替手段は提供されていません。
American Fuzzy Lop は、非常にシンプルだが堅牢なインストルメンテーション ガイド付き遺伝的アルゴリズムと組み合わせたブルートフォース ファザーです。修正された形式のエッジ カバレッジを使用して、プログラム制御フローに対する微妙なローカル スケールの変更を簡単に検出します。
少し単純化すると、アルゴリズム全体は次のように要約できます。
ユーザーが指定した初期テスト ケースをキューにロードします。
キューから次の入力ファイルを取得し、
測定されたプログラムの動作を変更しない最小サイズにテスト ケースをトリミングしようとします。
バランスのとれた、よく研究されたさまざまな従来のファジング戦略を使用して、ファイルを繰り返し変更します。
生成された突然変異のいずれかによって、インストルメンテーションによって記録された新しい状態遷移が生じた場合は、突然変異した出力を新しいエントリとしてキューに追加します。
2に進みます。
発見されたテスト ケースは、よりカバレッジの高い新しい発見によって廃止されたテスト ケースを排除するために定期的に選別されます。そして、その他のいくつかのインスツルメンテーション主導の労力最小化ステップを経ます。
ファジング プロセスの副次的な結果として、このツールは興味深いテスト ケースの小さな自己完結型コーパスを作成します。これらは、ブラウザー、オフィス アプリケーション、グラフィックス スイート、クローズド ソース ツールのストレス テストなど、労働力やリソースを大量に消費する他のテスト体制をシードするのに非常に役立ちます。
ファザーは徹底的にテストされており、ブラインド ファジングやカバレッジのみのツールよりもはるかに優れた、すぐに使用できるパフォーマンスを提供します。
ソース コードが利用可能な場合、サードパーティ コードの標準ビルド プロセスで gcc または Clang のドロップイン代替として機能するコンパニオン ツールによってインストルメンテーションを挿入できます。
インストルメンテーションによるパフォーマンスへの影響はかなり控えめです。 afl-fuzz によって実装される他の最適化と組み合わせて、ほとんどのプログラムを従来のツールと同じかそれよりも高速にファジングできます。
ターゲット プログラムを再コンパイルする正しい方法は、ビルド プロセスの詳細によって異なりますが、ほぼ普遍的なアプローチは次のとおりです。
$ 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
を設定することによって)。最も単純なオプションは静的ビルドで、通常は次の方法で可能です。
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
「make」を呼び出すときにAFL_HARDEN=1
設定すると、CC ラッパーはコード強化オプションを自動的に有効にし、単純なメモリ バグを検出しやすくします。 AFL に含まれるヘルパー ライブラリである Libdislocator (libdislocator/README.dislocator を参照) も、ヒープ破損の問題の発見に役立ちます。
PS. ASAN ユーザーは、notes_for_asan.txt ファイルで重要な注意事項を確認することをお勧めします。
ソース コードが利用できない場合、ファザーは、ブラック ボックス バイナリの高速なオンザフライ インストルメンテーションの実験的なサポートを提供します。これは、あまり知られていない「ユーザー空間エミュレーション」モードで実行される QEMU のバージョンによって実現されます。
QEMU は AFL とは別のプロジェクトですが、次のようにして機能を簡単に構築できます。
$ cd qemu_mode
$ ./build_qemu_support.sh
追加の手順と注意事項については、qemu_mode/README.qemu を参照してください。
このモードはコンパイル時インストルメンテーションよりも約 2 ~ 5 倍遅く、並列化にはあまり役に立たず、その他にもいくつかの癖がある可能性があります。
ファザーが正しく動作するには、ターゲットのアプリケーションが通常予期する入力データの良い例を含む 1 つ以上の開始ファイルが必要です。基本的なルールは 2 つあります。
ファイルは小さくしておいてください。厳密に必要というわけではありませんが、1 KB 未満が理想的です。サイズが重要な理由については、perf_tips.txt を参照してください。
複数のテスト ケースは、機能的に異なる場合にのみ使用してください。画像ライブラリを曖昧にするために 50 枚の異なる休暇の写真を使用するのは意味がありません。
このツールに付属の testcase/ サブディレクトリには、起動ファイルの良い例が数多くあります。
PS.大規模なデータ コーパスをスクリーニングに利用できる場合は、afl-cmin ユーティリティを使用して、ターゲット バイナリ内で異なるコード パスを実行する、機能的に異なるファイルのサブセットを特定できます。
ファジング プロセス自体は、afl-fuzz ユーティリティによって実行されます。このプログラムには、初期テスト ケースを含む読み取り専用ディレクトリ、結果を保存する別の場所、およびテストするバイナリへのパスが必要です。
標準入力から直接入力を受け入れるターゲット バイナリの場合、通常の構文は次のとおりです。
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
ファイルから入力を受け取るプログラムの場合は、「@@」を使用して、ターゲットのコマンド ライン内で入力ファイル名を配置する場所をマークします。ファザーがこれを代わりに使用します。
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
-f オプションを使用して、変更されたデータを特定のファイルに書き込むこともできます。これは、プログラムが特定のファイル拡張子などを予期している場合に便利です。
インスツルメントされていないバイナリは、QEMU モード (コマンド ラインに -Q を追加) または従来のブラインドファザー モード (-n を指定) でファジングできます。
-t と -m を使用すると、実行されたプロセスのデフォルトのタイムアウトとメモリ制限をオーバーライドできます。これらの設定を変更する必要があるターゲットのまれな例には、コンパイラやビデオ デコーダが含まれます。
ファジングのパフォーマンスを最適化するためのヒントについては、perf_tips.txt で説明されています。
afl-fuzz は一連の決定的ファジング ステップを実行することから始まり、これには数日かかる場合がありますが、きちんとしたテスト ケースが生成される傾向があることに注意してください。 zzuf やその他の従来のファザーと同様に、すぐに簡単で汚い結果が必要な場合は、コマンド ラインに -d オプションを追加します。
表示される統計を解釈し、プロセスの状態を監視する方法については、status_screen.txt ファイルを参照してください。特に UI 要素が赤色で強調表示されている場合は、必ずこのファイルを参照してください。
ファジングプロセスは、Ctrl+C を押すまで継続します。少なくとも、ファザーが 1 つのキュー サイクルを完了できるようにする必要があります。これには、数時間から 1 週間ほどかかる場合があります。
出力ディレクトリ内には 3 つのサブディレクトリが作成され、リアルタイムで更新されます。
queue/ - すべての固有の実行パスのテスト ケースと、ユーザーが指定したすべての開始ファイル。これは、セクション 2 で説明した合成コーパスです。このコーパスを他の目的に使用する前に、afl-cmin ツールを使用して小さいサイズに縮小できます。このツールは、同等のエッジ カバレッジを提供するファイルのより小さなサブセットを検索します。
crashes/ - テストされたプログラムに致命的なシグナル (SIGSEGV、SIGILL、SIGABRT など) を受信させる固有のテスト ケース。エントリは受信信号によってグループ化されます。
Hangs/ - テストされたプログラムのタイムアウトを引き起こす固有のテスト ケース。何かがハングとして分類されるまでのデフォルトの制限時間は、1 秒と -t パラメーターの値の大きい方です。この値は AFL_HANG_TMOUT を設定することで微調整できますが、これが必要になることはほとんどありません。
関連する実行パスに、以前に記録された障害では見られない状態遷移が含まれる場合、クラッシュとハングは「固有」であるとみなされます。 1 つのバグに複数の方法で到達できる場合、プロセスの初期段階である程度のカウントの増加が発生しますが、これはすぐに減少するはずです。
クラッシュとハングのファイル名は、親の障害のないキュー エントリと関連付けられます。これはデバッグに役立つはずです。
afl-fuzz によって検出されたクラッシュを再現できない場合、最も考えられる原因は、ツールで使用されているのと同じメモリ制限を設定していないことです。試す:
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10] ; /path/to/tested_binary ... )
afl-fuzz に渡される -m パラメータと一致するように LIMIT_MB を変更します。 OpenBSD では、-Sv も -Sd に変更します。
既存の出力ディレクトリを使用して、中止されたジョブを再開することもできます。試す:
$ ./afl-fuzz -i- -o existing_output_dir [...etc...]
gnuplot がインストールされている場合は、afl-plot を使用してアクティブなファジング タスクに対していくつかのきれいなグラフを生成することもできます。これがどのようになるかの例については、http://lcamtuf.coredump.cx/afl/plot/ を参照してください。
afl-fuzz の各インスタンスは、およそ 1 つのコアを占有します。これは、マルチコア システムでは、ハードウェアを最大限に活用するには並列化が必要であることを意味します。複数のコアまたは複数のネットワーク接続されたマシン上で共通のターゲットをファジングする方法のヒントについては、Parallel_fuzzing.txt を参照してください。
並列ファジング モードは、AFL を他のファザー、シンボリックまたはコリック実行エンジンなどに接続する簡単な方法も提供します。ヒントについては、Parallel_fuzzing.txt の最後のセクションを参照してください。
デフォルトでは、afl-fuzz ミューテーション エンジンは、画像、マルチメディア、圧縮データ、正規表現構文、シェル スクリプトなどのコンパクトなデータ形式用に最適化されています。特に HTML、SQL、JavaScript など、特に冗長で冗長な表現を含む言語にはあまり適していません。
構文認識ツールを構築する手間を避けるために、afl-fuzz は、言語キーワードのオプションの辞書、マジック ヘッダー、またはターゲットのデータ型に関連付けられたその他の特別なトークンをファジング プロセスにシードし、それを使用して再構築する方法を提供します。外出先でも基礎となる文法を学習:
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
PS.明示的な辞書が指定されていない場合でも、afl-fuzz は、確定的なバイト反転中にインストルメンテーションを注意深く監視することにより、入力コーパス内の既存の構文トークンを抽出しようとします。これは、一部のタイプのパーサーや文法では機能しますが、-x モードほど優れたものではありません。
辞書を入手するのが非常に難しい場合は、AFL をしばらく実行してから、AFL に付属のユーティリティとして提供されるトークン キャプチャ ライブラリを使用するという別のオプションもあります。詳細については、libtokencap/README.tokencap を参照してください。
通常、クラッシュをカバレッジベースでグループ化すると、手動で、または非常に単純な GDB または Valgrind スクリプトを使用して迅速にトリアージできる小さなデータ セットが生成されます。すべてのクラッシュはキュー内の親の非クラッシュ テスト ケースまで追跡できるため、障害の診断が容易になります。
そうは言っても、一部のファジング クラッシュは、多くのデバッグやコード分析作業を行わなければ、悪用可能性を迅速に評価することが難しい場合があることを認識することが重要です。このタスクを支援するために、afl-fuzz は、-C フラグで有効になる非常にユニークな「クラッシュ探索」モードをサポートしています。
このモードでは、ファザーは 1 つ以上のクラッシュ テスト ケースを入力として受け取り、フィードバック駆動のファジング戦略を使用して、プログラムをクラッシュ状態に保ちながら、プログラム内で到達できるすべてのコード パスを非常に迅速に列挙します。
クラッシュを引き起こさない突然変異は拒否されます。実行パスに影響を与えない変更も同様です。
出力はファイルの小さなコーパスであり、攻撃者が障害のあるアドレスに対してどの程度の制御を持っているか、または最初の境界外読み取りを回避できるかどうかを非常に迅速に調べることができ、その下に何があるのかを確認できます。 。
ああ、もう 1 つ: テスト ケースを最小限に抑えるために、afl-tmin を試してみてください。このツールは非常に簡単な方法で操作できます。
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
このツールは、クラッシュするテスト ケースとクラッシュしないテスト ケースの両方で同様に機能します。クラッシュ モードでは、インストルメント化されたバイナリとインスツルメント化されていないバイナリを問題なく受け入れます。非クラッシュ モードでは、ミニマイザーは標準の AFL インストルメンテーションに依存して、実行パスを変更せずにファイルを簡素化します。
ミニマイザーは、afl-fuzz と互換性のある方法で -m、-t、-f、および @@ 構文を受け入れます。
AFL に最近追加されたもう 1 つのツールは、afl-analyze ツールです。入力ファイルを取得し、バイトを順次反転して、テストされたプログラムの動作を観察します。次に、どのセクションが重要であるか、どのセクションがそうでないかに基づいて入力を色分けします。完璧ではありませんが、多くの場合、複雑なファイル形式について迅速に洞察を得ることができます。その操作の詳細については、technical_details.txt の終わり近くに記載されています。
ファジングは、クラッシュしない設計エラーや実装エラーを発見するための素晴らしい手法ですが、あまり活用されていません。次のような場合に abort() を呼び出すようにターゲット プログラムを変更することによって、かなりの数の興味深いバグが発見されました。
2 つの bignum ライブラリは、ファザーで生成された同じ入力が与えられた場合に異なる出力を生成します。
画像ライブラリは、同じ入力画像を連続して数回デコードするように要求されると、異なる出力を生成します。
シリアル化/逆シリアル化ライブラリは、ファザーが提供するデータを反復的にシリアル化および逆シリアル化するときに、安定した出力を生成できません。
圧縮ライブラリは、特定の BLOB を圧縮してから解凍するように要求されると、入力ファイルと矛盾する出力を生成します。
これらまたは同様の健全性チェックの実装には通常、ほとんど時間がかかりません。特定のパッケージのメンテナである場合は、 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
(libfuzzer とも共有されるフラグ) または#ifdef __AFL_COMPILER
(これは AFL 専用) を使用して、このコードを条件付きにすることができます。
他の多くの計算集約型タスクと同様に、ファジングはハードウェアや OS に負担をかける可能性があることに注意してください。特に:
CPU は高温になるため、適切な冷却が必要になります。ほとんどの場合、冷却が不十分な場合、または適切に動作しなくなった場合、CPU 速度は自動的に調整されます。そうは言っても、特に適切ではないハードウェア (ラップトップ、スマートフォンなど) でファジングを行う場合、何かが爆発する可能性が完全に不可能というわけではありません。
標的となったプログラムは、最終的にギガバイト単位のメモリを不規則に取得したり、ジャンク ファイルでディスク領域を埋め尽くしたりする可能性があります。 AFL は基本的なメモリ制限を強制しようとしますが、起こり得るすべての事故を防ぐことはできません。肝心なのは、データ損失の可能性が許容できるリスクではないシステムではファジングをすべきではないということです。
ファジングには、ファイルシステムに対する何十億もの読み取りと書き込みが含まれます。最新のシステムでは、これは通常大量にキャッシュされるため、かなり控えめな「物理」I/O になりますが、この方程式を変える可能性のある要因は数多くあります。潜在的な問題を監視するのはあなたの責任です。 I/O が非常に多いと、多くの HDD や SSD の寿命が短くなる可能性があります。
Linux でディスク I/O を監視する良い方法は、「iostat」コマンドです。
$ iostat -d 3 -x -k [...optional disk ID...]
AFL に関する最も重要な注意事項をいくつか示します。
AFL は、最初に生成されたプロセスがシグナル (SIGSEGV、SIGABRT など) によって停止しているかどうかをチェックすることで障害を検出します。これらの信号のカスタム ハンドラーをインストールするプログラムでは、関連するコードをコメントアウトする必要がある場合があります。同様に、ファズされたターゲットによって生成された子処理のフォールトも、それを検出するコードを手動で追加しない限り、検出を回避できる可能性があります。
他のブルート フォース ツールと同様、テスト対象の実際のデータ形式を完全にラップするために暗号化、チェックサム、暗号署名、または圧縮が使用されている場合、ファザーが提供する範囲は限られています。
これを回避するには、関連するチェックをコメント アウトします (インスピレーションについては、experimental/libpng_no_checksum/ を参照してください)。これが不可能な場合は、experimental/post_library/ で説明されているように、ポストプロセッサを作成することもできます。
ASAN と 64 ビット バイナリには、残念なトレードオフがいくつかあります。これは afl-fuzz の特定の障害によるものではありません。ヒントについては、notes_for_asan.txt を参照してください。
ファジング ネットワーク サービス、バックグラウンド デーモン、または UI インタラクションが機能する必要がある対話型アプリは直接サポートされていません。より伝統的な方法で動作させるには、簡単なコード変更が必要になる場合があります。 Preeny も比較的シンプルなオプションを提供する可能性があります - https://github.com/zardus/preeny を参照してください
ネットワークベースのサービスを変更するための役立つヒントは、https://www.fastly.com/blog/how-to-fuzz-server-american-fuzzy-lop でも参照できます。
AFL は人間が判読できるカバレッジ データを出力しません。カバレッジを監視したい場合は、Michael Rash の afl-cov を使用してください: https://github.com/mrash/afl-cov
時折、知覚を備えた機械が創造者に対して反抗することがあります。この問題が発生した場合は、http://lcamtuf.coredump.cx/prep/ を参照してください。
これ以外のプラットフォーム固有のヒントについては、「インストール」を参照してください。
afl-fuzz の改善の多くは、以下からのフィードバック、バグ レポート、またはパッチがなければ実現できません。
Jann Horn Hanno Boeck
Felix Groebert Jakub Wilk
Richard W. M. Jones Alexander Cherepanov
Tom Ritter Hovik Manucharyan
Sebastian Roschke Eberhard Mattes
Padraig Brady Ben Laurie
@dronesec Luca Barbato
Tobias Ospelt Thomas Jarosch
Martin Carpenter Mudge Zatko
Joe Zbiciak Ryan Govostes
Michael Rash William Robinet
Jonathan Gray Filipe Cabecinhas
Nico Weber Jodie Cunningham
Andrew Griffiths Parker Thompson
Jonathan Neuschfer Tyler Nighswander
Ben Nagy Samir Aguiar
Aidan Thornton Aleksandar Nikolich
Sam Hakim Laszlo Szekeres
David A. Wheeler Turo Lamminen
Andreas Stieger Richard Godbee
Louis Dassy teor2345
Alex Moneger Dmitry Vyukov
Keegan McAllister Kostya Serebryany
Richo Healey Martijn Bogaard
rc0r Jonathan Foote
Christian Holler Dominique Pelle
Jacek Wielemborek Leo Barnes
Jeremy Barnes Jeff Trull
Guillaume Endignoux ilovezfs
Daniel Godas-Lopez Franjo Ivancic
Austin Seipp Daniel Komaromy
Daniel Binderman Jonathan Metzman
Vegard Nossum Jan Kneschke
Kurt Roeckx Marcel Bohme
Van-Thuan Pham Abhik Roychoudhury
Joshua J. Drake Toby Hutton
Rene Freingruber Sergey Davidoff
Sami Liedes Craig Young
Andrzej Jackowski Daniel Hodson
ありがとう!
質問がありますか?懸念事項?バグ報告? GitHubをご利用ください。
このプロジェクトにはメーリング リストもあります。参加するには、[email protected] にメールを送信してください。または、最初にアーカイブを参照したい場合は、https://groups.google.com/group/afl-users を試してください。