LibAFL
LibAFLは、AFL++などのAFLベースファザーの機能を実装したモジュラーファジングライブラリです。従来のファザーとは異なり、すべての機能をモジュール化してカスタマイズ可能なRustライブラリとして提供します。libFuzzerのドロップイン置換として、またはゼロからカスタムファザーを構築するためのライブラリとして使用できます。
使用するタイミング
| ファザー | 最適な用途 | 複雑さ |
|---|---|---|
| libFuzzer | クイックセットアップ、シングルスレッド | 低 |
| AFL++ | マルチコア、汎用 | 中 |
| LibAFL | カスタムファザー、高度な機能、研究 | 高 |
LibAFLを選択するタイミング:
- カスタムの変異戦略やフィードバック機構が必要な場合
- 標準的なファザーがターゲットアーキテクチャをサポートしていない場合
- 新しいファジング手法を実装したい場合
- ファジングコンポーネントを細かく制御する必要がある場合
- ファジング研究を行っている場合
クイックスタート
LibAFLは、最小限の設定でlibFuzzerのドロップイン置換として使用できます:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// ファザーが提供するデータでコードを呼び出す
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
# libFuzzer.a が生成されるはず
ハーネスの作成
LibAFLのハーネスは、ドロップイン置換モードを使用する場合、libFuzzerと同じパターンに従います:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// ここにファジング対象のコードを記述
return 0;
}
LibAFLをRustライブラリとしてカスタムファザーを構築する場合、ハーネスロジックはファザーに直接統合されます。完全なパターンについては、以下の「カスタムファザーの作成」セクションを参照してください。
関連情報: 詳細なハーネス作成テクニックについては、harness-writing テクニックスキルを参照してください。
使用モード
LibAFLは2つの主要な使用モードをサポートしています:
1. libFuzzerドロップイン置換
既存のハーネスでLibAFLをlibFuzzerの代わりに使用します。
コンパイル:
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. オブザーバーを設定
let edges_observer = HitcountsMapObserver::new(
unsafe { std_edges_map_observer("edges") }
).track_indices();
let time_observer = TimeObserver::new("time");
// 2. フィードバックを定義
let mut feedback = feedback_or!(
MaxMapFeedback::new(&edges_observer),
TimeFeedback::new(&time_observer)
);
// 3. 目的を定義
let mut objective = feedback_or_fast!(
CrashFeedback::new(),
TimeoutFeedback::new()
);
// 4. 状態を作成または復元
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryCorpus::new(),
OnDiskCorpus::new(&output_dir).unwrap(),
&mut feedback,
&mut objective,
).unwrap()
});
// 5. ミューテーターを設定
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
// 6. スケジューラーを設定
let scheduler = IndexesLenTimeMinimizerScheduler::new(
&edges_observer,
QueueScheduler::new()
);
// 7. ファザーを作成
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// 8. ハーネスを定義
let mut harness = |input: &BytesInput| {
let buf = input.target_bytes().as_slice();
libfuzzer_test_one_input(buf);
ExitKind::Ok
};
// 9. エグゼキュータを設定
let mut executor = InProcessExecutor::with_timeout(
&mut harness,
tuple_list!(edges_observer, time_observer),
&mut fuzzer,
&mut state,
&mut restarting_mgr,
timeout,
)?;
// 10. 初期入力を読み込み
if state.must_load_initial_inputs() {
state.load_initial_inputs(
&mut fuzzer,
&mut executor,
&mut restarting_mgr,
&input_dir
)?;
}
// 11. ファジングを開始
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
Ok(())
};
// ファザーを起動
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/
これにより、コア0で1つ、コア8-15で8つの合計9つのクライアントが実行されます。
オプション付き
./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ヘッダ
"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 | 速度とカバレッジのバランス |
ファザーのデバッグ
デバッグを容易にするために、シングルプロセスモードでファザーを実行:
// ランチャーを直接呼び出しに置き換え
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();
// コメントアウト:
// 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"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx
cmake -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX .
cmake --build . --target fuzz
実行:
./fuzz --input seeds/ --cores 0,1,2,3 --output crashes/ --timeout 1000
トラブルシューティング
一般的な問題
| 問題 | 解決策 |
|---|---|
RUSTFLAGS が設定されていない | export RUSTFLAGS="-C linker=/usr/bin/clang-15" |
| LLVMのバージョンの不一致 | 存在するLLVMバージョンに合わせて調整 |
| リンクエラー | 静的ライブラリが正しくリンクされているか確認 |
| セグメンテーションフォールト | アドレスサニタイザーで再コンパイル |
| シンボルが見つからない | 正しい crate-type が staticlib であることを確認 |