libafl について
LibAFL は、Rust で書かれたモジュール型のファジングライブラリです。独自のファザーを構築する開発者や研究者向けで、libFuzzer の代替としても、フィードバック、変異、スケジューリングを組み合わせる用途にも使えます。
主な機能
- 01Rust ライブラリとしてファザーを構築
- 02libFuzzer 互換モード
- 03フィードバックと変異のモジュール
- 04マルチコアファジング
- 05コーパス管理と実行統計
活用シーン
- 01既存の libFuzzer 対象をファジング
- 02入力変異とフィードバックをカスタマイズ
- 03対象コードに対する並列キャンペーンを実行
- 04新しいファジング手法を研究
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill libaflインストール後、Claude Code で/libaflを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlassets/trail-of-bits-mark.svg
詳細
LibAFL
LibAFL は、AFL++ などの AFL ベースのファザーの機能を実装するモジュール式のファジングライブラリです。従来のファザーとは異なり、LibAFL はすべての機能をモジュール式かつカスタマイズ可能な Rust ライブラリとして提供します。libFuzzer のドロップイン代替として使用することも、ゼロからカスタムファザーを構築するためのライブラリとして使用することもできます。
使用する場面
| ファザー | 最適な用途 | 複雑さ |
|---|---|---|
| libFuzzer | 手早く設定して、シングルスレッドで実行する場合 | 低 |
| AFL++ | マルチコア、汎用用途 | 中 |
| LibAFL | カスタムファザー、高度な機能、研究 | 高 |
LibAFL を選ぶ場面:
- カスタムのミューテーション戦略やフィードバック機構が必要な場合
- 標準的なファザーが対象アーキテクチャをサポートしていない場合
- 新しいファジング手法を実装したい場合
- ファジングコンポーネントを細かく制御する必要がある場合
- ファジング研究を行う場合
クイックスタート
LibAFL は、最小限のセットアップで libFuzzer のドロップイン代替として使用できます。
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Call your code with fuzzer-provided data
my_function(data, size);
return 0;
}
LibAFL の libFuzzer 互換レイヤーをビルドします:
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
コンパイルして実行します:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/
インストール
前提条件
- Clang/LLVM 15-18
- Rust(rustup 経由)
- 追加のシステム依存関係
Linux/macOS
Clang をインストールします:
apt install clang
または、apt.llvm.org 経由で特定のバージョンをインストールします:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Rust 用の環境を設定します:
export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"
Rust をインストールします:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
追加の依存関係をインストールします:
apt install libssl-dev pkg-config
libFuzzer 互換モードを使用する場合は、nightly Rust をインストールします:
rustup toolchain install nightly --component llvm-tools
検証
インストールを検証するために LibAFL をビルドします:
cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Should produce libFuzzer.a
ハーネスの作成
ドロップイン代替モードで使用する場合、LibAFL のハーネスは libFuzzer と同じパターンに従います:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing target code here
return 0;
}
Rust ライブラリとして LibAFL を使用してカスタムファザーを構築する場合、ハーネスのロジックはファザーに直接組み込まれます。全体的なパターンについては、以下の「カスタムファザーの作成」セクションを参照してください。
関連項目: ハーネス作成の詳しい手法については、harness-writing 技術スキルを参照してください。
使用モード
LibAFL は、主に 2 つの使用モードをサポートしています:
1. libFuzzer のドロップイン代替
既存のハーネスで、libFuzzer の代替として LibAFL を使用します。
コンパイル:
clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
実行:
./fuzz corpus/
長時間のキャンペーンで推奨:
./fuzz -fork=1 -ignore_crashes=1 corpus/
2. Rust ライブラリとしてのカスタムファザー
LibAFL のコンポーネントを使用して、完全にカスタマイズしたファザーを構築します。
プロジェクトを作成:
cargo init --lib my_fuzzer
cd my_fuzzer
cargo add [email protected] [email protected] [email protected] [email protected] \
--features "[email protected]/libfuzzer,[email protected]/sancov_pcguard_hitcounts"
Cargo.toml を設定:
[lib]
crate-type = ["staticlib"]
カスタムファザーの作成
関連項目: ハーネス作成の詳しい手法、複雑な入力を処理するパターン、 高度な戦略については、fuzz-harness-writing 技術スキルを参照してください。
ファザーのコンポーネント
LibAFL のファザーは、モジュール式のコンポーネントで構成されています:
- オブザーバー - 実行フィードバック(カバレッジ、実行時間)を収集します
- フィードバック - 入力に興味深い点があるかどうかを判定します
- 目標 - ファジングの目標(クラッシュ、タイムアウト)を定義します
- 状態 - コーパスとメタデータを保持します
- ミューテーター - 新しい入力を生成します
- スケジューラー - ミューテーションする入力を選択します
- エグゼキューター - 入力を使ってターゲットを実行します
基本的なファザーの構造
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};
#[no_mangle]
pub extern "C" fn libafl_main() {
let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
// 1. Setup observers
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. Define feedback
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. Define objective
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. Create or restore state
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. Setup mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. Setup scheduler
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. Create fuzzer
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. Define harness
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. Setup executor
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. Load initial inputs
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. Start fuzzing
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// Launch fuzzer
Launcher::builder()
.run_client(&mut run_client)
.cores(&cores)
.build()
.launch()
.unwrap();
}
コンパイル
詳細モード
すべての計装フラグを手動で指定します:
clang++-15 -DNO_MAIN -g -O2 \
-fsanitize-coverage=trace-pc-guard \
-fsanitize=address \
-Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
main.cc harness.cc -o fuzz
コンパイララッパー(推奨)
計装を自動的に処理する LibAFL コンパイララッパーを作成します。
src/bin/libafl_cc.rs を作成:
use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};
pub fn main() {
let args: Vec<String> = env::args().collect();
let mut cc = ClangWrapper::new();
cc.cpp(is_cpp)
.parse_args(&args)
.link_staticlib(&dir, "my_fuzzer")
.add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
.add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
.run()
.unwrap();
}
コンパイルして使用:
cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz
関連項目: サニタイザーの詳しい設定、よくある問題、高度なフラグについては、 address-sanitizer および undefined-behavior-sanitizer 技術スキルを参照してください。
キャンペーンの実行
基本的な実行
./fuzz --cores 0 --input corpus/
マルチコアファジング
./fuzz --cores 0,8-15 --input corpus/
これにより、クライアントが 9 個実行されます。1 個はコア 0 で、8 個はコア 8~15 で実行されます。
オプションを指定して実行
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
テキストユーザーインターフェイス(TUI)
グラフィカルな統計表示を有効にします:
./fuzz -tui=1 corpus/
出力の解釈
| 出力 | 意味 |
|---|---|
corpus: N | 見つかった興味深いテストケースの数 |
objectives: N | 見つかったクラッシュ/タイムアウトの数 |
executions: N | ターゲットの呼び出し総数 |
exec/sec: N | 現在の実行スループット |
edges: X% | コードカバレッジ率 |
clients: N | 並列ファジングプロセスの数 |
ファザーは、主に次の2種類のイベントを出力します。
- UserStats - 現在の統計情報を定期的に通知するハートビート
- Testcase - 新たに発見された興味深い入力
高度な使い方
ヒントとコツ
| ヒント | 効果 |
|---|---|
-fork=1 -ignore_crashes=1 を使用する | 最初のクラッシュ後もファジングを続行します |
InMemoryOnDiskCorpus を使用する | 再起動後もコーパスを保持します |
-tui=1 で TUI を有効にする | 進行状況をより見やすく表示します |
| 特定の LLVM バージョンを使用する | 互換性の問題を回避します |
RUSTFLAGS を正しく設定する | リンクエラーを防ぎます |
クラッシュの重複排除
同じバグによる重複したクラッシュが保存されないようにします。
バックトレースオブザーバーを追加する:
let backtrace_observer = BacktraceObserver::owned(
"BacktraceObserver",
libafl::observers::HarnessType::InProcess
);
エグゼキューターを更新する:
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer, backtrace_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
ハッシュフィードバックを使ってオブジェクティブを更新する:
let mut objective = feedback_and!(
feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
NewHashFeedback::new(&backtrace_observer)
);
これにより、一意のバックトレースを持つクラッシュのみが保存されます。
ディクショナリファジング
ディクショナリを使用して、ファジングを特定のトークンへ誘導します。
ファイルからトークンを追加する:
let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);
ミューテーターを更新する:
let mutator = StdScheduledMutator::new(
havoc_mutations().merge(tokens_mutations())
);
ハードコードされたトークンの例(PNG):
state.add_metadata(Tokens::from([
vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
"IHDR".as_bytes().to_vec(),
"IDAT".as_bytes().to_vec(),
"PLTE".as_bytes().to_vec(),
"IEND".as_bytes().to_vec(),
]));
関連項目: ディクショナリの作成方法やフォーマット固有のディクショナリについて詳しくは、 fuzzing-dictionaries テクニックスキルを参照してください。
自動トークン
プログラムからマジック値とチェックサムを自動的に抽出します。
コンパイララッパーで有効にする:
cc.add_pass(LLVMPasses::AutoTokens)
ファザーで自動トークンを読み込む:
tokens += libafl_targets::autotokens()?;
トークンセクションを確認する:
echo "p (uint8_t *)__token_start" | gdb fuzz
パフォーマンスの調整
| 設定 | 影響 |
|---|---|
| マルチコアファジング | コア数に応じて速度が線形に向上します |
InMemoryCorpus | 高速ですが、永続化されません |
InMemoryOnDiskCorpus | 速度と永続性のバランスが取れています |
| サニタイザー | 2~5倍遅くなりますが、バグの発見に不可欠です |
最適化レベル -O2 | 速度とカバレッジのバランスが取れます |
ファザーのデバッグ
デバッグを容易にするため、ファザーをシングルプロセスモードで実行します。
// Replace launcher with direct call
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// Comment out:
// Launcher::builder()
// .run_client(&mut run_client)
// ...
// .launch()
その後、GDB でデバッグします。
gdb --args ./fuzz --cores 0 --input corpus/
実際の使用例
例:libpng
LibAFL を使った libpng のファジング:
1. ソースコードを取得する:
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev
2. コンパイララッパーを設定する:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
3. 静的ライブラリをビルドする:
./configure --enable-shared=no
make
4. ハーネスを取得する:
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
5. ファザーをリンクする:
$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
6. シードを準備する:
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
7. ディクショナリを取得する(任意):
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
8. ファジングを開始する:
./fuzz --input seeds/ --cores 0 -x png.dict
例:CMake プロジェクト
LibAFL を CMake ビルドシステムに統合します。
CMakeLists.txt:
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)
計測なしのバイナリをビルドする:
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program
ファザーをビルドする:
export FUZZER_CARGO_DIR="/path/to/libafl/project"
cmake -DCMAKE_C_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cc \
-DCMAKE_CXX_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cxx .
cmake --build . --target fuzz
ファジングを実行する:
./fuzz --input seeds/ --cores 0
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| カバレッジが増加しない | 計測に失敗しています | コンパイララッパーが使用されていることを確認し、-fsanitize-coverage を確認します |
| ファザーが起動しない | 興味深い入力を含むコーパスが空です | コードパスを実行するシード入力を指定します |
libafl_main のリンクエラー | ランタイムがリンクされていません | -Wl,--whole-archive または -u libafl_main を使用します |
| LLVM のバージョン不一致 | LibAFL には LLVM 15~18 が必要です | 対応する LLVM バージョンをインストールし、環境変数を設定します |
| Rust のコンパイルに失敗する | Rust または Cargo が古くなっています | rustup update で Rust を更新します |
| ファジングが遅い | サニタイザーが有効になっています | 2~5倍の速度低下は想定内であり、バグの発見に必要です |
| 環境変数の干渉 | CC、CXX、RUSTFLAGS が設定されています | LibAFL プロジェクトのビルド後に設定を解除します |
| デバッガーをアタッチできない | マルチプロセスファジングが実行されています | シングルプロセスモードで実行します(「デバッグ」セクションを参照) |
関連スキル
技術スキル
| スキル | 用途 |
|---|---|
| fuzz-harness-writing | 効果的なハーネスの作成に関する詳細なガイダンス |
| address-sanitizer | ファジング中のメモリエラーの検出 |
| undefined-behavior-sanitizer | 未定義動作の検出 |
| coverage-analysis | コードカバレッジの測定と改善 |
| fuzzing-corpus | シードコーパスの構築と管理 |
| fuzzing-dictionaries | フォーマットを考慮したファジング用の辞書の作成 |
関連するファザー
| スキル | 検討するタイミング |
|---|---|
| libfuzzer | セットアップを簡素化したい場合、LibAFL の高度な機能が不要な場合 |
| aflpp | カスタムファザーを開発せずにマルチコアファジングを行いたい場合 |
| cargo-fuzz | セットアップを簡素化して Rust プロジェクトをファジングしたい場合 |
リソース
公式ドキュメント
- LibAFL Book - 包括的なドキュメントを掲載した公式ハンドブック
- LibAFL GitHub - ソースコードとサンプル
- LibAFL API Documentation - Rust API リファレンス
サンプルとチュートリアル
- LibAFL Examples - ファザーのサンプル集
- cargo-fuzz with LibAFL - cargo-fuzz のバックエンドとして LibAFL を使用
- Testing Handbook LibAFL Examples - このハンドブックの実行可能なサンプル集