AFL++
AFL++は、オリジナルのAFL fuzzerのフォークで、より優れたファジングパフォーマンスと高度な機能を提供しながら、安定性を維持しています。libFuzzerに対する主な利点は、複数のコアでファジングキャンペーンを実行するための安定したサポートがあり、大規模なファジング作業に最適であることです。
使用タイミング
| Fuzzer | 最適な用途 | 複雑さ |
|---|---|---|
| AFL++ | マルチコアファジング、多様な変異、成熟したプロジェクト | 中 |
| libFuzzer | クイックセットアップ、シングルスレッド、シンプルなハーネス | 低 |
| LibAFL | カスタムファザー、研究、高度なユースケース | 高 |
AFL++を選ぶタイミング:
- スループットを最大化するためにマルチコアファジングが必要な場合
- プロジェクトがClangまたはGCCでコンパイル可能な場合
- 多様な変異戦略と成熟したツールが欲しい場合
- libFuzzerが停滞し、より多くのカバレッジが必要な場合
- 並列実行が有効なプロダクションコードベースをファジングしている場合
クイックスタート
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// ファザーが提供するデータでコードを呼び出す
check_buf((char*)data, size);
return 0;
}
コンパイルして実行:
# 最初にAFL++ラッパースクリプトをセットアップする(インストールを参照)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
インストール
AFL++には、LLVM、Python、Rustを含む多くの依存関係があります。AFL++でのファジングには、最新のDebianまたはUbuntuディストリビューションを使用することを推奨します。
| 方法 | 使用タイミング | サポートされるコンパイラ |
|---|---|---|
| Ubuntu/Debianリポジトリ | 最近のUbuntu、基本機能のみ | Ubuntu 23.10: Clang 14 & GCC 13Debian 12: Clang 14 & GCC 12 |
| Docker(Docker Hubから) | 特定のAFL++バージョン、Apple Siliconサポート | 4.35c時点: Clang 19 & GCC 11 |
| Docker(ソースから) | 未リリース機能のテスト、パッチの適用 | Dockerfileで設定可能 |
| ソースから | Dockerを避けたい、特定のパッチが必要 | LLVM_CONFIG 環境変数で調整可能 |
Ubuntu/Debian
afl++をインストールする前に、apt-cache show afl++ でパッケージのclangバージョン依存関係を確認し、一致する lld バージョン(例: lld-17)をインストールしてください。
apt install afl++ lld-17
Docker(Docker Hubから)
docker pull aflplusplus/aflplusplus:stable
Docker(ソースから)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
ソースから
Ubuntuバージョンの要件と依存関係については、Dockerfileを参照してください。LLVM_CONFIG を設定してClangバージョンを指定します(例: llvm-config-18)。
ラッパースクリプトのセットアップ
AFL++をホストまたはDockerで実行するためのラッパースクリプトを作成します:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-\"stable\"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -i \
--privileged \
-v ./:/src \
--rm \
--name "afl_fuzzing_$$" \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Usage: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
以下の例では、システム構成コマンド(ホストカーネルにアクセスする必要がある)を除き、docker モードを使用します。host を指定して、マシン自体にインストールされたAFL++に対して実行できます。ラッパーはモード引数の後のすべてを1つのシェル文字列に結合するため、クォーテーションは維持されません。スペースを含む引数(-x "my dict.dict")は単語分割されて届きます。そのようなファイルはスペースなしで名前を変更するか、その実行のためにラッパーを編集してください。
-t がないのは意図的です。docker run -ti は、stdinが端末でない場合(CIジョブやエージェント/スクリプトが駆動するものすべてをカバー)に the input device is not a TTY で中断します。afl-fuzz は端末がないことを検出し、全画面UIの代わりにプレーンなステータス行を出力します。watch のように端末を必要とするプログラムは、ラッパーの代わりにホスト側で実行する必要があります。$$ はラッパーのPIDに展開されるため、並列インスタンスは単一の afl_fuzzing で衝突するのではなく、個別のコンテナ名を取得します。docker ps はCOMMAND列を切り詰めるため、すべての行が同じように見えます。インスタンスを停止する前に区別するには、docker ps --no-trunc を使用します。
セキュリティ警告: afl-system-config および afl-persistent-config スクリプトはroot権限を必要とし、OSのセキュリティ機能を無効にします。本番システムや開発環境ではファジングしないでください。代わりに専用のVMを使用してください。
システム構成
再起動ごとに実行すると、1秒あたりの実行数が最大15%向上します:
./afl++ host afl-system-config
afl-system-config は、実行されるカーネルをチューニングするため、キャンペーンをホストするマシン上で実行します。./afl++ docker afl-system-config は、ラッパーの --privileged コンテナを介して同じ設定に到達します。これは、AFL++がDockerのみでインストールされている場合の唯一の方法です。
最大のパフォーマンスを得るには、カーネルセキュリティ緩和策を無効にします(grubブートローダーが必要。Dockerではサポートされていません):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ host afl-system-config
cat /proc/cmdline で確認 - 出力に mitigations=off が含まれている必要があります。
ハーネスの作成
ハーネス構造
AFL++はlibFuzzerスタイルのハーネスをサポートします:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. 必要に応じて入力サイズを検証
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. ファズデータでターゲット関数を呼び出す
target_function(data, size);
// 3. 0を返す(非ゼロは将来の使用のために予約済み)
return 0;
}
ハーネスのルール
| すべきこと | すべきでないこと |
|---|---|
| 実行間でグローバル状態をリセットする | 以前の実行の状態に依存する |
| エッジケースを適切に処理する | 無効な入力で終了する |
| ハーネスを決定論的に保つ | 乱数生成器を使用する |
| 割り当てられたメモリを解放する | メモリリークを作成する |
| 入力サイズを検証する | 無制限の入力を処理する |
関連項目: 詳細なハーネス作成テクニック、複雑な入力の処理パターン、および高度な戦略については、fuzz-harness-writing テクニックスキルを参照してください。
コンパイル
AFL++は、異なるトレードオフを持つ複数のコンパイルモードを提供します。
コンパイルモードの決定木
コンパイルモードを選択してください:
- LTOモード (
afl-clang-lto): 最高のパフォーマンスとインストルメンテーション。最初にこれを試してください。 - LLVMモード (
afl-clang-fast): LTOがコンパイルに失敗した場合のフォールバック。 - GCCプラグイン (
afl-gcc-fast): GCCを必要とするプロジェクト向け。
基本コンパイル(LLVMモード)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCCコンパイル
./afl++ docker afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
重要: GCCバージョンは、AFL++ GCCプラグインのコンパイルに使用されたバージョンと一致している必要があります。
サニタイザーと一緒に
./afl++ docker AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
関連項目: 詳細なサニタイザー設定、一般的な問題、および高度なフラグについては、address-sanitizer および undefined-behavior-sanitizer テクニックスキルを参照してください。
ビルドフラグ
-g は必須ではないことに注意してください。AFL++コンパイラによってデフォルトで追加されます。
| フラグ | 目的 |
|---|---|
-DNO_MAIN=1 | libFuzzerハーネスを使用する際にmain関数をスキップする |
-O2 | プロダクション最適化レベル(ファジングに推奨) |
-fsanitize=fuzzer | libFuzzer互換モードを有効にし、実行可能ファイルのリンク時にファザーランタイムを追加する |
-fsanitize=fuzzer-no-link | ファザーランタイムをリンクせずにインストルメンテーションする(静的ライブラリおよびオブジェクトファイル用) |
コーパス管理
初期コーパスの作成
AFL++には少なくとも1つの空でないシードファイルが必要です:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
実際のプロジェクトでは、代表的な入力を収集します:
- ファジングしている形式のサンプルファイルをダウンロードする
- プロジェクトのテストスイートからテストケースを抽出する
- ファイル形式の最小限の有効な入力を使う
コーパス最小化
キャンペーン後、一意のカバレッジのみを保持するためにコーパスを最小化します:
./afl++ docker afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
関連項目: コーパス作成戦略、辞書、シード選択については、fuzzing-corpus テクニックスキルを参照してください。
キャンペーンの実行
基本実行
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
環境変数の設定
./afl++ docker AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
出力の解釈
AFL++はこれらの統計をどちらにしても報告しますが、読み方はモードによって異なります。ラッパーの docker run -i はコンテナにTTYを与えないため、afl-fuzz は全画面UIをドロップし、代わりにログにプレーンなステータス行を書き込みます。以下のフィールドはそこに、および state/<instance>/fuzzer_stats に表示されます。インタラクティブUIを取得するには、ターミナルで host モードを実行するか、手動で監視する実行のためにラッパーに -t を追加します。
| 出力 | 意味 |
|---|---|
| execs/sec | 実行速度 - 高いほど良い |
| cycles done | 完了したキューパスの数 |
| corpus count | キュー内の一意のテストケースの数 |
| saved crashes | 見つかった一意のクラッシュの数 |
| stability | 安定したエッジの割合(100%に近いべき) |
出力ディレクトリ構造
out/default/
├── cmdline # SUTはどのように呼び出されたか?
├── crashes/ # SUTをクラッシュさせる入力
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # SUTをハングさせる入力
├── queue/ # 最終ファザー状態を再現するテストケース
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # キャンペーン統計
└── plot_data # プロット用データ
結果の分析
ライブキャンペーン統計を表示:
./afl++ docker afl-whatsup out
カバレッジプロットを作成します。aflplusplus イメージには既に gnuplot-nox が含まれています。ホストモードでは、最初に apt install gnuplot でgnuplotをインストールします。
./afl++ docker afl-plot out/default out_graph/
テストケースの再実行
out/default/crashes/ のファイル名の1つを渡します:
./afl++ docker ./fuzz out/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
ファザーオプション
| オプション | 目的 |
|---|---|
-G 4000 | 最大テスト入力長(デフォルト: 1048576バイト) |
-t 1000 | 各テストケースのタイムアウト(ミリ秒)(デフォルト: 1000ms) |
-m 1000 | メモリ制限(メガバイト)(デフォルト: 0 = 無制限) |
-x ./dict.dict | 変異をガイドするために辞書ファイルを使用する |
重要な環境変数
AFL++には多くの環境変数がありますが、ほとんどはニッチなものです。実際に重要となるのは以下のものです。
常に設定するもの
# すべてのキャンペーンはtmpfsを使用するべき - SSDが喜び、かつ高速
AFL_TMPDIR=/dev/shm
AFL_TMPDIR はデメリットのない無料のパフォーマンス向上です。設定しないとSSDを消耗し、ファジングが遅くなります。
遅いターゲット
# キャリブレーションを約2.5倍高速化 - ターゲットが遅い場合に使用(例: >10 ms/exec)
AFL_FAST_CAL=1
AFL_FAST_CAL は、わずかな精度低下でキャリブレーション時間を短縮します。キャリブレーションに時間がかかる遅いターゲットに特にお勧めします。
マルチコアキャンペーン
# プライマリ(-M)インスタンスのみ - afl-cminに必要。ファジング自体には不要
AFL_FINAL_SYNC=1
# すべてのインスタンス - テストケースをメモリにキャッシュ(デフォルト: 50 MB、適切な範囲: 50-250 MB)
AFL_TESTCACHE_SIZE=100
AFL_FINAL_SYNC は、停止時にプライマリインスタンスがすべてのセカンダリから最終インポートを行うように指示します。これはファジングプロセス自体には影響しません。後でコーパス最小化のために afl-cmin を実行する場合にのみ重要で、プライマリのキューが完全な結合コーパスを持つことを保証します。AFL_TESTCACHE_SIZE はテストケースをメモリにキャッシュしてディスクI/Oを削減します。デフォルトは50 MBで、ほとんどのキャンペーンでは50~250 MBの値が適切に機能します。
CI/自動ファジング
# ファジングが何も見つからない場合に迅速に失敗
AFL_EXIT_ON_TIME=3600 # 新しいパスが1時間ない = 停止
# または「完了」するまで実行(すべてのキューエントリが処理されるまで)
AFL_EXIT_WHEN_DONE=1
# ヘッドレス環境
AFL_NO_UI=1
CIでの無制限のファジングはリソースを浪費します。時間制限を設定するか、終了条件を使用します。
避けるべき変数
| 変数 | スキップする理由 |
|---|---|
AFL_NO_ARITH | バイナリ形式のカバレッジを損なう可能性があるが、テキストベースのターゲットには有用かもしれない |
AFL_SHUFFLE_QUEUE | エキゾチックなセットアップのみ、通常は有害 |
AFL_DISABLE_TRIM | トリミングは価値がある。理由なく無効にしない |
マルチコアファジング
AFL++はマルチコアファジングに優れており、2つの大きな利点があります:
- 1秒あたりの実行数が多い(物理コアに比例してスケール)
- 非対称ファジング(例: 1つのASanジョブ、残りはサニタイザーなし)
キャンペーンの開始
プライマリファザーを開始(バックグラウンド):
./afl++ docker afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
セカンダリファザーを開始(コアの数だけ):
./afl++ docker afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error </dev/null &
./afl++ docker afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error </dev/null &
</dev/null は必須であり、飾りではありません。docker run -i はクライアントが自身のstdinを読み取り続けるようにし、バックグラウンドプロセスがターミナルを読み取ると SIGTTIN が送信されます。
マルチコアのベストプラクティス
- 物理コアを超えない: ハイパースレッディングをカウントしない
- 永続モードを使用: 可能な場合は、ハーネスで
LLVMFuzzerTestOneInputのループを実装する - 異なる辞書: セカンダリに異なる辞書を割り当てる
- 非対称サニタイザー: 1つのインスタンスをASanで実行し、残りはサニタイザーなしで実行する
キャンペーンの監視
すべてのインスタンスのステータスを要約:
./afl++ docker afl-whatsup state
キャンペーンの停止
インスタンスを実行中に強制終了すると、キューデータが失われるか破損する可能性があります。代わりに、各ファザーで Ctrl+C を使用するか、SIGINT を送信します:
kill -INT <PID>
AFL_EXIT_WHEN_DONE=1 で開始されたキャンペーンは、すべてのキューエントリが処理されると自動的に終了します。
永続モード
永続モードは、プロセスをフォークして再実行するオーバーヘッドなしに、同じプロセスで複数のテストケースを実行できるようにします。これにより、特に高速なターゲットではスループットが大幅に向上します。
永続モードを有効にするには、新しいプロセスをフォークせずにテストケースを実行するループでハーネスをラップします(LLVMFuzzerTestOneInput を繰り返し呼び出す)。AFL++は自動的に永続モードを検出します。
クラッシュの処理
クラッシュの分析
バックトレースを取得:
./afl++ docker ./fuzz < crash_file
afl-gdb または afl-kill ユーティリティを使用して、クラッシュをより詳細に分析することもできます。
クラッシュの削減
afl-tmin を使用してクラッシュ入力を最小化し、根本原因を特定しやすくします:
./afl++ docker afl-tmin -i crash_file -o minimized_crash -- ./fuzz
CIでのAFL++
CI設定
# GitHub Actionsの例
jobs:
fuzz:
steps:
- uses: actions/checkout@v4
- name: Build fuzzer
run: |
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir -p seeds
echo "initial" > seeds/seed
- name: Run fuzzing
run: |
AFL_EXIT_ON_TIME=3600 AFL_NO_UI=1 ./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
- name: Upload crashes
uses: actions/upload-artifact@v4
with:
path: out/default/crashes/
パフォーマンスの最適化
| テクニック | 利点 | トレードオフ |
|---|---|---|
AFL_TMPDIR=/dev/shm | ディスクI/Oを削減、速度を向上 | RAMを消費する |
afl-system-config | 15%のパフォーマンス向上 | root権限が必要、セキュリティを弱める |
| 永続モード | 最大10倍のスループット | 適切なハーネス設計が必要 |
| マルチコア | コアに比例してスケール | 調整と監視が必要 |
| 辞書ファイル | より良い変異、高いカバレッジ | 辞書のメンテナンスが必要 |
トラブルシューティング
| 問題 | 解決策 |
|---|---|
| コンパイルエラー | LTO、LLVM、GCCモードを試す。Clangバージョンを確認する |
| 低い実行速度 | 永続モードを有効にする。システム設定を確認する |
| ハング | -t タイムアウトを増やす。再帰的アルゴリズムを確認する |
| メモリ不足 | -m 制限を増やす。AFL_USE_ASAN=1 を確認する |
| 奇妙な動作 | ホストではなくDockerを使用する。OSの違いを確認する |
関連項目: ファジングハーネスの作成、サニタイザーの設定、コーパス戦略の詳細については、それぞれのテクニックスキル(fuzz-harness-writing、address-sanitizer、undefined-behavior-sanitizer、fuzzing-corpus)を参照してください。