Executor 是一个 trait,我们只需要实现一个方法:run_target
。以 GenericInProcessExecutor 为例:
impl<EM, H, HB, HT, I, OT, S, Z> Executor<EM, I, S, Z>
for GenericInProcessExecutor<EM, H, HB, HT, I, OT, S, Z>
where
S: HasExecutions,
OT: ObserversTuple<I, S>,
HT: ExecutorHooksTuple<I, S>,
HB: BorrowMut<H>,
H: FnMut(&I) -> ExitKind + Sized,
{
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut S,
mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
看起来有点复杂,但其实我们可以不用管那些泛型,先只关心 run_target
函数。它的参数为:
fuzzer
,在目前的 LibAFL 中基本上是StdFuzzer
类,我们可以从中提取或修改 fuzzer 中的一些信息,我这里没有用到;state
,常见的 State 是 StdState,保存了运行时的一些信息,最常见的是在执行完一次后给执行次数加一,也就是代码中的*state.executions_mut() += 1;
;mgr
,这个应该是 EventManager,我没用过;input
,这个是输入,在 Executor 中,我们会接受变异好的输入并发送给目标程序。
这样的话,我们可以实现一个最小的 Executor:
use libafl::executors::{Executor, ExitKind};
pub struct BabyExecutor;
impl<EM, I, S, Z> Executor<EM, I, S, Z> for BabyExecutor {
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut S,
mgr: &mut EM,
input: &I,
) -> Result<libafl::executors::ExitKind, libafl::Error> {
Ok(ExitKind::Ok)
}
}
看,我们没有添加任何其他的泛型就实现了一个 Executor。现在它什么都没有做,只是一味的返回 Ok(ExitKind::Ok)
。将它作为底层框架逐步添加功能是一个很好的开始。
动手练习
在这里有 baby_executor 的源码,你可以尝试使用这个 Executor 在 Fuzzing 中实际运行吗?在这个过程中会报什么错误呢?
答案
由于这个 Executor 实在太小,我们可以不必初始化 Feedback 等功能,参考 nop_executor 的测试进行实现:
mod tests {
use libafl::{
NopFuzzer,
events::NopEventManager,
executors::{Executor, ExitKind},
inputs::BytesInput,
state::NopState,
};
use crate::executor::baby_executor::BabyExecutor;
#[test]
fn test_baby_executor() {
let empty_input = BytesInput::new(vec![]);
let mut executor = BabyExecutor {};
let mut fuzzer = NopFuzzer::new();
let mut mgr: NopEventManager = NopEventManager::new();
let mut state: NopState<BytesInput> = NopState::new();
assert_eq!(
executor
.run_target(&mut fuzzer, &mut state, &mut mgr, &empty_input)
.unwrap(),
ExitKind::Ok
);
}
}
之后运行 cargo test -- test_baby_executor --no-capture
进行测试。由于我们的 Executor 总是会返回 ExitKind::Ok
,因此这个测试会成功 pass,我们已经成功地实现了第一个 Executor!
Harness
现在,我们有了基础的 Executor,但它显然无法正常工作。该咋做到官方的示例 baby_fuzzer 的效果呢?
官方的示例用的是 InProcessExecutor,在 fuzzing 中,它接受的参数为:
// 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");
先不管其他参数,我们实现一个只带 harness 的 Executor。
harness 是什么?我们看一下上面的初始化:
// The closure that we want to fuzz
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();
let buf = target.as_slice();
signals_set(0);
if !buf.is_empty() && buf[0] == b'a' {
signals_set(1);
if buf.len() > 1 && buf[1] == b'b' {
signals_set(2);
if buf.len() > 2 && buf[2] == b'c' {
#[cfg(unix)]
panic!("Artificial bug triggered =)");
// panic!() raises a STATUS_STACK_BUFFER_OVERRUN exception which cannot be caught by the exception handler.
// Here we make it raise STATUS_ACCESS_VIOLATION instead.
// Extending the windows exception handler is a TODO. Maybe we can refer to what winafl code does.
// https://github.com/googleprojectzero/winafl/blob/ea5f6b85572980bb2cf636910f622f36906940aa/winafl.c#L728
#[cfg(windows)]
unsafe {
write_volatile(0 as *mut u32, 0);
}
}
}
}
ExitKind::Ok
};
简单来说,harness 是一个闭包函数(和Python/C++中的lambda函数类似),它接收 input 作为参数,发送给真正的目标——这正对应上文我们为 BabyExecutor 实现的 Executor trait:我们可以在 run_target 函数中调用自定义 harness 来将输入发送到本地文件/远程。那么,在 BabyExecutor 的基础上添加 harness 参数吧。
我们直接照抄参考 InProcessMonitor 的初始化函数:
impl<'a, EM, H, I, OT, S, Z> InProcessExecutor<'a, EM, H, I, OT, S, Z>
where
H: FnMut(&I) -> ExitKind + Sized,
OT: ObserversTuple<I, S>,
S: HasCurrentTestcase<I> + HasExecutions + HasSolutions<I>,
I: Input,
{
/// Create a new in mem executor with the default timeout (5 sec)
pub fn new<OF>(
harness_fn: &'a mut H,
observers: OT,
fuzzer: &mut Z,
state: &mut S,
event_mgr: &mut EM,
) -> Result<Self, Error>
where
EM: EventFirer<I, S> + EventRestarter<S>,
OF: Feedback<EM, I, OT, S>,
Z: HasObjective<Objective = OF>,
{
这里可能哪个变量的生命周期有问题,因此对 harness_fn 进行了生命周期的声明,除此之外,它被要求实现为泛型 H,注意到 H: FnMut(&I) -> ExitKind + Sized,
,harness 函数需要实现 FnMut(&I) -> ExitKind
和 Sized
两个 trait。由于我暂时不了解 Sized
是哪里做的约束,我们先只实现第一个 trait。
注意啦,在这里我们需要实现两个泛型:H 和 I,H 自不必多说,I 是因为 H 需要接受一个泛型的输入 I,目前我们只要求它实现 Input 即可。
噔噔!接下来就是见证奇迹的时刻。我们首先为 BabyExecutor 添加 harness 参数:
pub struct BabyExecutor<H, I> {
harness_fn: H,
phantom: PhantomData<I>,
}
这里使用 PhantomData 包裹 I,是因为目前 BabyExecutor 中没有任何结构体依赖 I,但 H 又需要 I 的声明,所以这里使用了一种零成本抽象(即 PhantomData)的方式进行声明,以后如果有其它需求我们也可以进行类似的声明(例如 phantom: PhantomData<(I, S, ...)>,
, 用元组进行包裹)。
之后,我们可以实现 new 函数接收 harness:
impl<H, I> BabyExecutor<H, I>
where
H: FnMut(&I) -> ExitKind,
{
pub fn new(harness_fn: H) -> Self {
Self {
harness_fn,
phantom: PhantomData,
}
}
}
最后,我们对上面实现的 run_target
函数函数稍作修改:
impl<EM, I, S, Z, H> Executor<EM, I, S, Z> for BabyExecutor<H, I>
where
H: FnMut(&I) -> ExitKind,
{
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut S,
_mgr: &mut EM,
input: &I,
) -> Result<libafl::executors::ExitKind, libafl::Error> {
(self.harness_fn)(input);
Ok(ExitKind::Ok)
}
}
需要注意的是,这里我们需要指定 H:
where
H: FnMut(&I) -> ExitKind,
要不然下面的 (self.harness_fn)(input);
不知道 harness_fn 的类型。
这样,我们就实现了一个带 harness 的 BabyFuzzer!不过,为了让它正常运行,我们最好还是将 state 中的运行次数加一:
impl<EM, I, S, Z, H> Executor<EM, I, S, Z> for BabyExecutor<H, I>
where
H: FnMut(&I) -> ExitKind,
S: HasExecutions,
{
fn run_target(
&mut self,
_fuzzer: &mut Z,
state: &mut S,
_mgr: &mut EM,
input: &I,
) -> Result<libafl::executors::ExitKind, libafl::Error> {
*state.executions_mut() += 1;
(self.harness_fn)(input);
Ok(ExitKind::Ok)
}
}
因为我们在这里使用了 state 的特性,因此就需要让 State 支持对应的 trait(在这里是 HasExecutions)。
Ok,现在我们可以尝试替代官方示例的 baby_fuzzer 了吗?让我们试一试吧!
打一个简单的 patch:
25,26d24
< use libafl_study::executor::{self, baby_harness_executor::BabyExecutor};
<
115,125c113,121
< // // 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");
<
< let mut executor = BabyExecutor::new(&mut harness);
---
> // 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");
这个 patch 的作用很简单,就是将原本的 InProcessExecutor 替换为我们的 BabyExecutor。很显然它不能工作,那它会报什么错误呢?你可以使用这个项目进行测试。
检查报错,它的主要描述为:
the trait bound BabyExecutor...: HasObservers` is not satisfied
the following other types implement trait `HasObservers`:
...
简单来说,就是我们的 BabyExecutor 没有实现其他组件需要的 HasObservers 这个 trait,参考 InProcessExecutor 为它添加即可。我们看一下源码:
/// Holds a tuple of Observers
pub trait HasObservers {
/// The observer
type Observers;
/// Get the linked observers
fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers>;
/// Get the linked observers (mutable)
fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers>;
}
/// An executor takes the given inputs, and runs the harness/target.
pub trait Executor<EM, I, S, Z> {
/// Instruct the target about the input and run
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut S,
mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error>;
}
Executor 这个 trait 实现了 <EM, I, S, Z>
这四个泛型,因此我们实现的时候不需要在 BabyExecutor 中实现。但是 HasObservers 没有这些泛型,需要我们进行较大幅度的修改。我们一步一步来看:
首先,InProcessExecutor 的 HasObserver 实现为:
impl<EM, H, HB, HT, I, OT, S, Z> HasObservers
for GenericInProcessExecutor<EM, H, HB, HT, I, OT, S, Z>
{
type Observers = OT;
#[inline]
fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> {
self.inner.observers()
}
#[inline]
fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers> {
self.inner.observers_mut()
}
}
这个 self.inner.observers()
本质上就是要求我们的 Executor 实现自己的 OT 类型的 observers,它的泛型是 OT,向上追一下 OT,发现:
impl<EM, H, HB, HT, I, OT, S, Z> Executor<EM, I, S, Z>
for GenericInProcessExecutor<EM, H, HB, HT, I, OT, S, Z>
where
S: HasExecutions,
OT: ObserversTuple<I, S>,
HT: ExecutorHooksTuple<I, S>,
HB: BorrowMut<H>,
H: FnMut(&I) -> ExitKind + Sized,
{
也就是说,OT 依赖 I 和 S。我们之前已经添加了 PhantomData<I>
,那么,为了添加 OT,我们就得将上面的 PhantomData 修改为 PhantomData<(I, S)>
。对于 BabyExecutor,HasObservers trait 的实现为:
impl<H, I, OT, S> HasObservers for BabyExecutor<H, I, OT, S>
where
OT: ObserversTuple<I, S>,
S: HasExecutions,
{
type Observers = OT;
fn observers(&self) -> RefIndexable<&Self::Observers, Self::Observers> {
RefIndexable::from(&self.observers)
}
fn observers_mut(&mut self) -> RefIndexable<&mut Self::Observers, Self::Observers> {
RefIndexable::from(&mut self.observers)
}
}
在泛型实现中,泛型的顺序很重要,例如我这里写的是:
impl<H, I, OT, S> HasObservers for BabyExecutor<H, I, OT, S>
那么当我们修改上面的代码时,就需要保持这个顺序进行修改。这里不再复制粘贴大段代码,可以到这里查看修改后的代码。
在修改后,我们再次传入 BabyExecutor,这次没有报错并可以正常运行了(参考代码在这里),我们成功地实现了一个可以工作的最小 Executor!