LibAFL
基本介绍
前置知识
- Rust
- Fuzzing
LibAFL介绍
LibAFL:为了解决大量的fuzzer工具重复工作的问题,将fuzzer拆分成多个部分,编写fuzzer只需要将其组装,如输入可以改成字节输入或者AST输入,不需要重新安装熟悉多个fuzzer。缺点就是Rust门槛比较高。
资源
LibAFL Book:https://aflplus.plus/libafl-book/libafl.html
项目:https://github.com/AFLplusplus/LibAFL
baby_fuzzer
官方给出的一个简单案例用来说明如何使用LibAFL,我们摘取一部分,以下就是一个简单的fuzzer代码,看着很复杂,不如直接AFL++一把梭。
// Create an observation channel using the signals map
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
// Feedback to rate the interestingness of an input
let mut feedback = MaxMapFeedback::new(&observer);
// A feedback to choose if an input is a solution or not
let mut objective = CrashFeedback::new();
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::newfrom("./crashes")).unwrap(,
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();
// The Monitor trait define how the fuzzer stats are displayed to the user
#[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::new(|s| println!("{s}"));
#[cfg(feature = "tui")]
let mon = TuiMonitor::newfrom("Baby Fuzzer"), false;
// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(mon);
// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// Create the executor for an in-process function with just one observer
let mut executor = InProcessExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
)
.expect("Failed to create the Executor");
// Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(32);
// Generate 8 initial inputs
state
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!new(mutator);
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
首先序中说过LibAFL是将fuzzer的多个步骤进行解耦,好让每个部分可以进行替换,实现一个fuzzer可以搞定所有fuzzer的效果。那我们如果编写fuzzer需要哪些部分呢?以下是我根据自己的认知需要的部分
- A-存储我们fuzzing过程中的状态
- B-提供fuzzing过程中的输入数据
- C-提供fuzzing输入的变异算法
- D-判定fuzzing结果是否有效(包含最少两种:有趣则加入原始语料库,产生如crash效果加入crash结果库)
- E-收集覆盖率
- F-任务调度,种子以怎样的方式或者算法加入队列
- G-执行器,如何去执行fuzzing
- H-界面输出,提供一个类似AFL界面来告知用户fuzzing的结果
我自己大概能想到这几个组件,接下来看一下LibAFL的组件
组件
- Observer
- Executor
- InProcessExecutor
- ForkserverExecutor
- TimeoutExecutor
- InProcessForkExecutor
- Feedback
- Input
- Corpus
- Mutator
- Generator
- Stage
以baby_fuzzer为例看看如何装配组件的
通过对着色部分的组装完成一个简单的fuzzer,乍看是有些繁杂,但是了解了每个部分功能以后,直接在原有代码进行更改就会比较简便。
结构体
Struct libafl::observers::map::ConstMapObserver
Use a const size to speedup when the user can know the size of the map at compile time.
Feedback::is_interesting
pub unsafe fn from_mut_ptr(name: &'static str, map_ptr: *mut T) -> Self
Creates a new MapObserver from a raw pointer
从baby_fuzzer看LibAFL
Harness
Observer
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
/// Creates a new [`MapObserver`] from a raw pointer
/// # Safety
/// Will dereference the `map_ptr` with up to len elements.
pub unsafe fn from_mut_ptr<S>(name: S, map_ptr: *mut T, len: usize) -> Self
where
S: Into<String>,
{
Self::maybe_differential_from_mut_ptr(name, map_ptr, len)
}
/// Creates a new [`MapObserver`] from a raw pointer
/// # Safety
/// Will dereference the `map_ptr` with up to len elements.
unsafe fn maybe_differential_from_mut_ptr<S>(name: S, map_ptr: *mut T, len: usize) -> Self
where
S: Into<String>,
{
Self::maybe_differential_from_mut_slice(
name,
OwnedMutSlice::from_raw_parts_mut(map_ptr, len),
)
}
/// Create a new [`OwnedMutSlice`] from a raw pointer and length
/// # Safety
/// The pointer must be valid and point to a map of the size `size_of<T>() * len`
/// The contents will be dereferenced in subsequent operations.
#[must_use]
pub unsafe fn from_raw_parts_mut(ptr: *mut T, len: usize) -> OwnedMutSlice<'a, T> {
if ptr.is_null() || len == 0 {
Self {
inner: OwnedMutSliceInner::Ownednew(),
}
} else {
Self {
inner: OwnedMutSliceInner::RefRaw(ptr, len),
}
}
}
没看懂==
state
// Generate 8 initial inputs
state
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
/// Generate `num` initial inputs, using the passed-in generator.
pub fn generate_initial_inputs<G, E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
generator: &mut G,
manager: &mut EM,
num: usize,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
G: Generator<<Self as UsesInput>::Input, Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.generate_initial_internal(fuzzer, executor, generator, manager, num, false)
}
fn generate_initial_internal<G, E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
generator: &mut G,
manager: &mut EM,
num: usize,
forced: bool,
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
G: Generator<<Self as UsesInput>::Input, Self>,
Z: Evaluator<E, EM, State = Self>,
{
let mut added = 0;
for _ in 0..num {
let input = generator.generate(self)?;
if forced {
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
added += 1;
} else {
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
if res != ExecuteInputResult::None {
added += 1;
}
}
}
manager.fire(
self,
Event::Log {
severity_level: LogSeverity::Debug,
message: format!("Loaded {added} over {num} initial testcases"),
phantom: PhantomData,
},
)?;
Ok(())
}
生成输入
let input = generator.generate(self)?;
输入评估
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input)?;
/// Runs the input and triggers observers and feedback,
/// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus
fn evaluate_input(
&mut self,
state: &mut Self::State,
executor: &mut E,
manager: &mut EM,
input: <Self::State as UsesInput>::Input,
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
self.evaluate_input_events(state, executor, manager, input, true)
}
/// Process one input, adding to the respective corpora if needed and firing the right events
#[inline]
fn evaluate_input_events(
&mut self,
state: &mut CS::State,
executor: &mut E,
manager: &mut EM,
input: <CS::State as UsesInput>::Input,
send_events: bool,
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error> {
self.evaluate_input_with_observers(state, executor, manager, input, send_events)
}
/// Process one input, adding to the respective corpora if needed and firing the right events
#[inline]
fn evaluate_input_with_observers<E, EM>(
&mut self,
state: &mut Self::State,
executor: &mut E,
manager: &mut EM,
input: <Self::State as UsesInput>::Input,
send_events: bool,
) -> Result<(ExecuteInputResult, Option<CorpusId>), Error>
where
E: Executor<EM, Self> + HasObservers<Observers = OT, State = Self::State>,
EM: EventFirer<State = Self::State>,
{
let exit_kind = self.execute_input(state, executor, manager, &input)?;
let observers = executor.observers();
self.scheduler.on_evaluation(state, &input, observers)?;
self.process_execution(state, manager, input, observers, &exit_kind, send_events)
}
/// Runs the input and triggers observers and feedback
pub fn execute_input<E, EM>(
&mut self,
state: &mut CS::State,
executor: &mut E,
event_mgr: &mut EM,
input: &<CS::State as UsesInput>::Input,
) -> Result<ExitKind, Error>
where
E: Executor<EM, Self> + HasObservers<Observers = OT, State = CS::State>,
EM: UsesState<State = CS::State>,
OT: ObserversTuple<CS::State>,
{
start_timer!(state);
executor.observers_mut().pre_exec_all(state, input)?;
mark_feature_time!(state, PerfFeature::PreExecObservers);
*state.executions_mut() += 1;
start_timer!(state);
let exit_kind = executor.run_target(self, state, event_mgr, input)?;
mark_feature_time!(state, PerfFeature::TargetExecution);
start_timer!(state);
executor
.observers_mut()
.post_exec_all(state, input, &exit_kind)?;
mark_feature_time!(state, PerfFeature::PostExecObservers);
Ok(exit_kind)
}
//impl<EM, H, HB, OT, S, Z> Executor<EM, Z> for GenericInProcessExecutor<H, HB, OT, S>
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut Self::State,
mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
self.handlers
.pre_run_target(self, fuzzer, state, mgr, input);
let ret = (self.harness_fn.borrow_mut())(input);
self.handlers.post_run_target();
Ok(ret)
}
/// The inmem executor simply calls a target function, then returns afterwards.
#[allow(dead_code)]
pub struct GenericInProcessExecutor<H, HB, OT, S>
where
H: FnMutInput -> ExitKind + ?Sized,
HB: BorrowMut<H>,
OT: ObserversTuple<S>,
S: UsesInput,
{
/// The harness function, being executed for each fuzzing loop execution
harness_fn: HB,
/// The observers, observing each run
observers: OT,
// Crash and timeout hah
handlers: InProcessHandlers,
phantom: PhantomData<(S, *const H)>,
}
/// Call after running a target.
#[allowunused_self]
pub fn post_run_target(&self) {
#[cfg(unix)]
unsafe {
write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null());
compiler_fenceSeqCst;
}
#[cfg(all(windows, feature = "std"))]
unsafe {
write_volatile(&mut GLOBAL_STATE.current_input_ptr, ptr::null());
compiler_fenceSeqCst;
}
}
Fuzzers
Name | Description |
---|---|
baby_fuzzer_gramatron | 语法fuzzing工具 |
baby_fuzzer | |
/home/v/fuzzer/LibAFL/fuzzers/baby_fuzzer/src/main.rs | |
/home/v/fuzzer/LibAFL/fuzzers/baby_fuzzer_gramatron/src/main.rs |
backtrace_baby_fuzzers
介绍了baby_fuzzer衍生fuzzer
c_code_with_fork_executor
//C
#[allowsimilar_names]
pub fn main() {
let mut shmem_provider = StdShMemProvider::new().unwrap();
unsafe { create_shmem_array() };
let map_ptr = unsafe { get_ptr() };
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
unsafe { c_harness(buf.as_ptr()) }
libafl::executors::ExitKind::Ok
};
// Create an observation channel using the signals map
let observer = unsafe { ConstMapObserver::<u8, 3>::from_mut_ptr("signals", map_ptr) };
// Create a stacktrace observer
let mut bt = shmem_provider.new_shmem_object::<Option<u64>>().unwrap();
//因使用forkserver模式,因此第三个参数为Child
let bt_observer = BacktraceObserver::new(
"BacktraceObserver",
unsafe { bt.as_object_mut::<Option<u64>>() },
libafl::observers::HarnessType::Child,
);
// Feedback to rate the interestingness of an input, obtained by ANDing the interestingness of both feedbacks
let mut feedback = MaxMapFeedback::new(&observer);
// A feedback to choose if an input is a solution or not
let mut objective = feedback_and!new(), NewHashFeedback::new(&bt_observer);
// create a State from scratch
let mut state = StdState::new(
// RNG
StdRand::with_seed(current_nanos()),
// Corpus that will be evolved, we keep it in memory for performance
InMemoryCorpus::new(),
// Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::newfrom("./crashes")).unwrap(,
// States of the feedbacks.
// The feedbacks can report the data that should persist in the State.
&mut feedback,
// Same for objective feedbacks
&mut objective,
)
.unwrap();
// The Monitor trait define how the fuzzer stats are displayed to the user
let mon = SimpleMonitor::new(|s| println!("{s}"));
// The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus
let mut mgr = SimpleEventManager::new(mon);
// A queue policy to get testcasess from the corpus
let scheduler = QueueScheduler::new();
// A fuzzer with feedbacks and a corpus scheduler
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
// Create the executor for an in-process function with just one observer
let mut executor = InProcessForkExecutor::new(
&mut harness,
tuple_list!(observer, bt_observer),
&mut fuzzer,
&mut state,
&mut mgr,
shmem_provider,
)
.expect("Failed to create the Executor");
// Generator of printable bytearrays of max size 32
let mut generator = RandPrintablesGenerator::new(32);
// Generate 8 initial inputs
state
.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!new(mutator);
fuzzer
.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)
.expect("Error in the fuzzing loop");
}
c_code_with_inprocess_executor
// Create a stacktrace observer
let mut bt = None;
let bt_observer = BacktraceObserver::new(
"BacktraceObserver",
&mut bt,
libafl::observers::HarnessType::InProcess,
);
// Feedback to rate the interestingness of an input, obtained by ANDing the interestingness of both feedbacks
let mut feedback = MaxMapFeedback::new(&observer);
// A feedback to choose if an input is a solution or not
let mut objective = feedback_and!new(), NewHashFeedback::new(&bt_observer);
The projects contained in this directory are simple fuzzers derived from the original baby_fuzzer examples, whose purpose is to show how to use a
BacktraceObserver
or anASANObserver
to dedupe crashes and other necessary components for this feature. To usecasr
deduplication forBacktraceObserver
orASANObserver
build LibAFL withcasr
feature.
The examples cover:
- An
InProcessForkExecutor
fuzzing a C harness- An
InProcessForkExecutor
fuzzing a Rust harness- An
InProcessExecutor
fuzzing a C harness- An
InProcessExecutor
fuzzing a Rust harness- A
CommandExecutor
fuzzing a simple binary- A
ForkServerExecutor
fuzzing a simple binary
baby_fuzzer_gramatron
与baby_fuzzer的区别在于,生成部分和变异部分不同
let automaton = read_automaton_from_filefrom("auto.postcard");
let mut generator = GramatronGenerator::new(&automaton);
// Generate 8 initial inputs
state
.generate_initial_inputs_forced(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8)
.expect("Failed to generate the initial corpus");
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
GramatronRandomMutator::new(&generator),
GramatronRandomMutator::new(&generator),
GramatronRandomMutator::new(&generator),
GramatronSpliceMutator::new(),
GramatronSpliceMutator::new(),
GramatronRecursionMutator::new()
),
2,
);
首先修改了这个函数,区别在于多了个强制字眼
generate_initial_inputs_forced
Generate num
initial inputs, using the passed-in generator and force the addition to corpus.
generate_initial_inputs
Generate num
initial inputs, using the passed-in generator.
其次
let mutator = StdScheduledMutator::new(havoc_mutations());
////////////////////////////////////////////////////////////////
let mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
GramatronRandomMutator::new(&generator),
GramatronRandomMutator::new(&generator),
GramatronRandomMutator::new(&generator),
GramatronSpliceMutator::new(),
GramatronSpliceMutator::new(),
GramatronRecursionMutator::new()
),
2,
);
pub fn new(mutations: MT) -> Self
Create a new StdScheduledMutator
instance specifying mutations
pub fn with_max_stack_pow(mutations: MT, max_stack_pow: [u64]) -> Self
Create a new StdScheduledMutator
instance specifying mutations and the maximun number of iterations
自定义多种变异方式,并且添加了最大迭代次数
baby_fuzzer_grimoire
// Feedback to rate the interestingness of an input
let mut feedback = MaxMapFeedback::tracking(&observer, false, true);
if state.metadata_map<Tokens>().is_none( {
state.add_metadatafrom([b"FOO".to_vec(), b"BAR".to_vec()]);
}
let generalization = GeneralizationStage::new(&observer);
// Setup a mutational stage with a basic bytes mutator
let mutator = StdScheduledMutator::with_max_stack_pow(havoc_mutations(), 2);
let grimoire_mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
GrimoireExtensionMutator::new(),
GrimoireRecursiveReplacementMutator::new(),
GrimoireStringReplacementMutator::new(),
// give more probability to avoid large inputs
GrimoireRandomDeleteMutator::new(),
GrimoireRandomDeleteMutator::new(),
),
3,
);
let mut stages = tuple_list!(
generalization,
StdMutationalStage::new(mutator),
StdMutationalStage::transforming(grimoire_mutator)
);
for input in initial_inputs {
fuzzer
.evaluate_input(&mut state, &mut executor, &mut mgr, input)
.unwrap();
}
pub fn new(map_observer: [&O]) -> Self
Create new MapFeedback
pub fn new_tracking( map_observer: &O, track_indexes: bool, track_novelties: bool ) -> Self Create new
MapFeedback` specifying if it must track indexes of used entries and/or novelties
baby_fuzzer_with_forkexecutor
// Create the executor for an in-process function with just one observer
let mut executor = InProcessForkExecutor::new(
&mut harness,
tuple_list!(observer),
&mut fuzzer,
&mut state,
&mut mgr,
shmem_provider,
)
.expect("Failed to create the Executor");
InProcessExecutor->InProcessForkExecutor
tinyinst_simple
cl.exe /I "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include"
test/test.cpp -o test.exe
cl.exe /I "C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\ucrt" test/test.cpp -o test.exe
消息传递
自定义fuzzer
todo
高级特性
todo
各种问题
tinyinst_simple
error: linking with link.exe
failed: exit code: 1120 when compile the tinyinst_simple fuzzer #1218
https://github.com/AFLplusplus/LibAFL/issues/1218#issuecomment-1514374997
pylibafl
/LibAFL/bindings/pylibafl
maturin build --release -i python
pip install .\wheels\pylibafl-0.10.0-cp311-none-win_amd64.whl
相关链接
- An
InProcessForkExecutor
fuzzing a C harness - An
InProcessForkExecutor
fuzzing a Rust harness - An
InProcessExecutor
fuzzing a C harness - An
InProcessExecutor
fuzzing a Rust harness - A
CommandExecutor
fuzzing a simple binary - A
ForkServerExecutor
fuzzing a simple binary