mcpField
L
libafl

libafl

github.com/trailofbitsテスト・品質7.9k5.2k 回インストール今日更新
リポジトリ

libafl について

LibAFL は、Rust で書かれたモジュール型のファジングライブラリです。独自のファザーを構築する開発者や研究者向けで、libFuzzer の代替としても、フィードバック、変異、スケジューリングを組み合わせる用途にも使えます。

主な機能

  1. 01Rust ライブラリとしてファザーを構築
  2. 02libFuzzer 互換モード
  3. 03フィードバックと変異のモジュール
  4. 04マルチコアファジング
  5. 05コーパス管理と実行統計

活用シーン

  1. 01既存の libFuzzer 対象をファジング
  2. 02入力変異とフィードバックをカスタマイズ
  3. 03対象コードに対する並列キャンペーンを実行
  4. 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 のファザーは、モジュール式のコンポーネントで構成されています:

  1. オブザーバー - 実行フィードバック(カバレッジ、実行時間)を収集します
  2. フィードバック - 入力に興味深い点があるかどうかを判定します
  3. 目標 - ファジングの目標(クラッシュ、タイムアウト)を定義します
  4. 状態 - コーパスとメタデータを保持します
  5. ミューテーター - 新しい入力を生成します
  6. スケジューラー - ミューテーションする入力を選択します
  7. エグゼキューター - 入力を使ってターゲットを実行します
基本的なファザーの構造
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 プロジェクトをファジングしたい場合

リソース

公式ドキュメント
サンプルとチュートリアル

コメント