Tldr

这篇文章定义了现代 fuzzer 应该具备的功能,并提供了 Rust 实现,可以作为设计 fuzzer 的参考。

update

  • 根据 Fuzzer 结构补充一些工作

看看能不能以 Fuzzer 结构为核心,整理总结一下模糊测试近年来的工作。

现代 Fuzzer 结构

作者提出了现代 fuzzer 的组成部分:

Input

Corpus

  • Corpus:输入和其附属元数据的存储。
    • 这个部分相关的工作不多,主要是需要考虑工程上的效率问题。
    • 若位于内存中会导致较大的资源消耗(但是速度会快一些),若位于磁盘则方便用户观察 fuzzer 的状态,代价是速度受到磁盘读写的瓶颈制约,主流 fuzzer 大多选择后者。
    • 此外,存储时还要区分有助于进化的 interesting testcase 和最终触发 crash 的 solution

Scheduler

  • Scheduler:从 corpus 中选取 testcase 的调度策略。
    • 个人理解,因为 corpus 中的种子是越来越多的,因此怎样挑选最合适的种子是很重要的。
    • 最朴素的即先进先出或随机选择,近年来也有工作利用调度来给 testcase 排优先级或是防止 testcase 爆炸。
  • 相关工作

Stage

  • Stage:定义对单个 testcase 进行的操作(action)。
  • 在 scheduler 选择了一个 testcase 后,fuzzer 会在其上进行分阶段的操作,比如 AFL 中的 random havoc stage 会对输入进行多种变异操作,许多 fuzzer 都有 minimization phase 以在保持覆盖率的同时减小 testcase,这也是一种 stage。
  • 相关工作

Observer

  • Observer:提供一次执行目标程序的信息。从对 fuzzer state 的影响看,observer 的快照应当和执行本身应当是等效的,这样在分布式 fuzzer 或是执行很慢的目标程序上会比较有帮助。覆盖导向型 fuzzer 中常用的 coverage map 就是一种 observer。
    • 简而言之是在执行一次模糊测试之后,用户能够得到的信息。
    • 例如对于 AFL/LibFuzzer 就是 bitmap 中填充的数据(指代的是边覆盖率的信息)
  • 相关工作:

Executor

  • Executor:用 fuzzer 的输入来执行目标程序。不同 fuzzer 在这方面区别可能很大,libFuzzer 这种 in-memory fuzzer 只需调用 harness 函数,Nyx 这种 hypervisor-based fuzzer 则每次执行都要从快照重启整个系统。
  • 相关工作:

Feedback

  • Feedback:将程序执行的结果分类以决定是否将其加入 corpus。大多数情况下 Feedback 和 observer 紧密相连但又有所不同,feedback 通常处理一个或多个 observer 报告的信息来判断 execution 是否 “interesting”,是否是满足条件的 solution,比如可观测的 crash。
  • 相关工作:

Mutator

  • Mutator:从一个或多个输入生成新的 testcase。
    • 这部分通常是定制 fuzzer 时最常改动的,不同 mutator 可以组合,往往还和特定的输入类型绑定,比如传统 fuzzer 中常见的是比特级别的变异,比如 bit flip 和 blocks swapping,而 grammar fuzzer 中的 mutator 则可交换 AST 上的节点来进行变异。

Generator

  • Generator:凭空产生新的输入。有随机生成的,也有 Nautilus 这种基于语法的。

LibAFL 的实现

基于上面这许多抽象的定义,作者用 Rust 实现了 LibAFL,有三个主要组成部分:核心组件 LibAFL Core, 在目标程序中运行的 LibAFL Targets,提供编译 wrapper 的 LibAFL CC,除此之外还包含了几个插桩后端(Instrumentation Backends),下图描绘了 LibAFL Core 的架构:

从图中可以看到 LibAFL 的组成部分,除了 State, Fuzzer 和 Events Manager 这三个大模块之外,大部分都一一对应前文所定义的 9 大实体。总之,模块化的设计让 LibAFL 天生具有极强的 Extensibility,基于 Rust 实现,独立于平台和不依赖标准库带来了 Portability,Event Manager 使得多节点并行 fuzz 的 Scalability 成为可能,这便是 LibAFL 设计之初所遵循的三个原则。

一些想法

  • 可以看出 Observer 模块相当于一次程序执行之后能反馈的信息。对于可以获取覆盖率的程序显然就是覆盖率了,在此基础上还可以添加状态,就算是纯黑盒的协议 Fuzz 理论上也可以通过返回状态作为反馈信息。
    • 如果有一些需要纯黑盒 Fuzz 还不一定有返回信息的该怎么办呢?
      • Fuzz 确实不是万能的
      • 但是这个需求也很奇怪吧!
  • Executor 也是现实实现中需要注意的点,对于某些无法模拟的固件或者很难模拟的固件,需要精心设计对应的 Executor。
  • 如果想要 fuzz 的目标是一个带有状态机的协议(比如 SSH),那么该如何修改 libafl 进行适配呢?

找时间结合源码看一下吧。

相关工作

在 libafl 的基础上有人实现了 feroxfuzz,一个黑盒 http 协议 fuzz。根据它的设计哲学:

它将 response 作为 observer 传递给 feedback。

参考资料