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) -> ExitKindSized 两个 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!