AFL++ は何をしますか?
AFL++は、オリジナルのAFLファザーをフォークしたもので、安定性を維持しながら優れたファジング性能と高度な機能を提供します。libFuzzerに対する大きな利点は、マルチコアでのファジングキャンペーンを安定してサポートしていることであり、大規模なファジングに最適です。
使用すべき状況
| ファザー | 最適な用途 | 複雑さ |
|---|---|---|
| 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)を指定します。
ラッパースクリプトのセットアップ
ホストまたはDocker上でAFL++を実行するためのラッパースクリプトを作成します:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -ti \
--privileged \
-v ./:/src \
--rm \
--name afl_fuzzing \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "使用方法: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
セキュリティ警告: afl-system-configおよびafl-persistent-configスクリプトはroot権限を必要とし、OSのセキュリティ機能を無効にします。本番システムや開発環境ではファジングしないでください。代わりに専用のVMを使用してください。
システム設定
毎回の再起動後に実行すると、1秒あたりの実行数が最大15%向上します:
./afl++ <host/docker> afl-system-config
最大のパフォーマンスを得るには、カーネルセキュリティ緩和策を無効にします(grubブートローダが必要、Dockerではサポートされていません):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ <host/docker> 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を返す(0以外は将来の使用のために予約)
return 0;
}
ハーネスのルール
| すべきこと | してはいけないこと |
|---|---|
| 実行間でグローバル状態をリセットする | 以前の実行からの状態に依存する |
| エッジケースを適切に処理する | 無効な入力で終了する |
| ハーネスを決定論的に保つ | 乱数生成器を使用する |
| 割り当てられたメモリを解放する | メモリリークを作成する |
| 入力サイズを検証する | 無制限の入力を処理する |
関連項目: 詳細なハーネス作成テクニック、複雑な入力を処理するためのパターン、および高度な戦略については、fuzz-harness-writing テクニックスキルを参照してください。
コンパイル
AFL++は、トレードオフの異なる複数のコンパイルモードを提供します。
コンパイルモードの決定木
コンパイルモードを選択してください:
- LTOモード (
afl-clang-lto): 最高のパフォーマンスと計装。これを最初に試してください。 - LLVMモード (
afl-clang-fast): LTOがコンパイルに失敗した場合のフォールバック。 - GCCプラグイン (
afl-gcc-fast): GCCを必要とするプロジェクト向け。
基本的なコンパイル(LLVMモード)
./afl++ <host/docker> afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCCコンパイル
./afl++ <host/docker> afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
重要: GCCバージョンは、AFL++ GCCプラグインのコンパイルに使用されたバージョンと一致している必要があります。
サニタイザーを使用する場合
./afl++ <host/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++ <host/docker> afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
関連項目: コーパス作成戦略、辞書、およびシード選択については、fuzzing-corpus テクニックスキルを参照してください。
キャンペーンの実行
基本的な実行
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
環境変数の設定
./afl++ <host/docker> AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
出力の解釈
AFL++ UIはリアルタイムのファジング統計情報を表示します:
| 出力 | 意味 |
|---|---|
| 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++ <host/docker> afl-whatsup out
カバレッジプロットを作成:
apt install gnuplot
./afl++ <host/docker> afl-plot out/default out_graph/
テストケースの再実行
./afl++ <host/docker> ./fuzz out/default/crashes/<test_case>
ファザーオプション
| オプション | 目的 |
|---|---|
-G 4000 | 最大テスト入力長(デフォルト:1048576バイト) |
-t 1000 | 各テストケースのタイムアウト(ミリ秒)(デフォルト:1000ms) |
-m 1000 | メモリ制限(メガバイト)(デフォルト:0 = 無制限) |
-x ./dict.dict | ミューテーションをガイドする辞書ファイルを使用 |
マルチコアファジング
AFL++はマルチコアファジングに優れており、2つの大きな利点があります:
- 1秒あたりの実行数が多い(物理コアに比例して拡大)
- 非対称ファジング(例:1つのASanジョブ、残りはサニタイザーなし)
キャンペーンの開始
プライマリファザーを開始(バックグラウンド):
./afl++ <host/docker> afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error &
セカンダリファザーを開始(コアと同じ数だけ):
./afl++ <host/docker> afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error &
./afl++ <host/docker> afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
マルチコアキャンペーンの監視
実行中のすべてのジョブを一覧表示:
jobs
ライブ統計を表示(毎秒更新):
./afl++ <host/docker> watch -n1 --color afl-whatsup state/
すべてのファザーの停止
kill $(jobs -p)
カバレッジ分析
AFL++はエッジ計装を通じて自動的にカバレッジを追跡します。カバレッジ情報はfuzzer_statsとplot_dataに保存されます。
カバレッジの測定
afl-plotを使用して経時的なカバレッジを可視化:
./afl++ <host/docker> afl-plot out/default out_graph/
カバレッジの改善
- フォーマットを認識したファジングのために辞書を使用
- より長いキャンペーンを実行(
cycles_wo_findsはプラトーを示す) - マルチコアファジングで異なるミューテーション戦略を試す
- カバレッジのギャップを分析し、ターゲットを絞ったシード入力を追加
関連項目: 詳細なカバレッジ分析テクニック、カバレッジギャップの特定、および体系的なカバレッジ改善については、coverage-analysis テクニックスキルを参照してください。
CMPLOG
CMPLOG/RedQueenは、あらゆるファザーで利用可能な最良のパス制約解決メカニズムです。 これを有効にするには、ファズターゲットをそれ用に計装する必要があります。 ファジングターゲットをビルドする前に、環境変数を設定します:
./afl++ <host/docker> AFL_LLVM_CMPLOG=1 make
ハーネスのコンパイルとリンクには特別なアクションは必要ありません。
CMPLOGで計装されたファジングターゲットでファザーインスタンスを実行するには、コマンドライン引数に-c0を追加します:
./afl++ <host/docker> afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
サニタイザー統合
サニタイザーは、即座にクラッシュを引き起こさないメモリ破壊バグを見つけるために不可欠です。
AddressSanitizer(ASan)
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
注意: 20TBの仮想メモリ予約のため、ASanではメモリ制限(-m)はサポートされていません。
UndefinedBehaviorSanitizer(UBSan)
./afl++ <host/docker> AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz
一般的なサニタイザーの問題
| 問題 | 解決策 |
|---|---|
| ASanがファジングを遅くする | マルチコア設定で1つのASanジョブのみを使用 |
| スタックの枯渇 | ASAN_OPTIONS=stack_size=...でスタックを増やす |
| GCCバージョンの不一致 | システムGCCがAFL++プラグインバージョンと一致することを確認 |
関連項目: 包括的なサニタイザー設定とトラブルシューティングについては、address-sanitizer テクニックスキルを参照してください。
高度な使用法
ヒントとコツ
| ヒント | なぜ役立つか |
|---|---|
| 可能な場合はLLVMFuzzerTestOneInputハーネスを使用 | ファジングキャンペーンの安定性が少なくとも85%あれば、これが最も効率的なファジングスタイルです。そうでない場合は、標準入力またはファイル入力ファジングを試してください |
| 辞書を使用 | ファザーがフォーマット固有のキーワードとマジックバイトを発見するのに役立ちます |
| 現実的なタイムアウトを設定 | システム負荷による誤検出を防ぎます |
| 入力サイズを制限 | 大きな入力が必ずしもより多くのスペースを探索するとは限りません |
| 安定性を監視 | 低い安定性は非決定論的な動作を示します |
標準入力ファジング
AFL++は、libFuzzerハーネスなしでstdinから読み取るプログラムをファジングできます:
./afl++ <host/docker> afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_stdin
これは永続モードより低速ですが、ハーネスコードは必要ありません。
ファイル入力ファジング
ファイルを読み取るプログラムの場合は、@@プレースホルダーを使用します:
./afl++ <host/docker> afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_file @@
パフォーマンスを向上させるには、fmemopenを使用してメモリからファイル記述子を作成します。
引数ファジング
argv-fuzz-inl.hを使用してコマンドライン引数をファジングします:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_COMPILER
#include "argv-fuzz-inl.h"
#endif
void check_buf(char *buf, size_t buf_len) {
if(buf_len > 0 && buf[0] == 'a') {
if(buf_len > 1 && buf[1] == 'b') {
if(buf_len > 2 && buf[2] == 'c') {
abort();
}
…
[翻訳のために切り詰められました]