LibAFL
LibAFL 是一個模組化模糊測試庫,實作了 AFL 系列模糊器(如 AFL++)的功能。與傳統模糊器不同,LibAFL 以 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
撰寫 Harness
當使用直接替代模式時,LibAFL harness 遵循與 libFuzzer 相同的模式:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 您的模糊測試目標程式碼在此
return 0;
}
當使用 LibAFL 作為 Rust 庫建置自訂模糊器時,harness 邏輯會直接整合到模糊器中。有關完整模式,請參閱下方的「撰寫自訂模糊器」一節。
另請參閱: 有關詳細的 harness 撰寫技術,請參閱 harness-writing 技術技能。
使用模式
LibAFL 支援兩種主要使用模式:
1. libFuzzer 直接替代
將 LibAFL 用作現有 harness 的 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"]
撰寫自訂模糊器
另請參閱: 有關詳細的 harness 撰寫技術、處理複雜輸入的模式以及進階策略,請參閱 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. 定義 harness
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
另請參閱: 有關詳細的 sanitizer 設定、常見問題以及進階標誌,請參閱 address-sanitizer 和 undefined-behavior-sanitizer 技術技能。
執行活動
基本執行
./fuzz --cores 0 --input corpus/
多核心模糊測試
./fuzz --cores 0,8-15 --input corpus/
這會執行 9 個客戶端:一個在核心 0 上,8 個在核心 8-15 上。
帶選項
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
文字使用者介面
啟用圖形統計視圖:
./fuzz -tui=1 corpus/
解讀輸出
| 輸出 | 意義 |
|---|---|
corpus: N | 找到的有趣測試案例數量 |
objectives: N | 找到的崩潰/超時數量 |
executions: N | 目標呼叫總次數 |
exec/sec: N | 當前執行吞吐量 |
edges: X% | 程式碼覆蓋率百分比 |
clients: N | 並行模糊測試程序數量 |
模糊器發出兩種主要事件類型:
- 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 | 平衡速度和持久性 |
| Sanitizer | 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. 取得 Harness:
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="/
…
[已截斷以進行翻譯]