中文译名:FIRM-AFL:通过增强过程仿真实现的物联网固件的高通量灰盒模糊测试 作者:郑 yaowen 单位:中国科学院信息工程研究所北京物联网重点实验室 国家: #中国 年份: #2019年 来源: #USENIX会议 关键字: #嵌入式 #fuzzing 笔记建立时间: 2023-02-07 09:13

作者题目中提到的高通量在文章体现为仿真器的高吞吐量。什么意思呢,就是说固件需要仿真运行在仿真器中,模糊测试喂给固件大量测试用例,但是目前的仿真手段对于喂给的测试用例执行的很慢,1 秒可能只执行 1 个或几个测试用例,即吞吐量小,作者实现的 FIRM-AFL 吞吐量很高。

摘要

  • FIRM-AFL 是第一个针对物联网固件的高通量的灰盒模糊测试器
    • 解决了兼容性问题——对可以在系统仿真器仿真的 POSIX 兼容固件进行模糊测试
    • 解决了性能瓶颈——增强进程模拟的新技术
  • 增强型进程仿真以一种新颖的方式将系统模式仿真和用户模式仿真结合起来,提供了系统模式仿真的高兼容性和用户模式仿真的高吞吐量
  • 看来本篇文章的主要工作在于这个增强进程仿真技术

引言

  • 吞吐量是影响模糊效果的关键因素。根据文章的实验,全系统仿真比用户模式仿真 (AFL 使用的用户模式仿真) 慢大约 10 倍。10 倍的减速意味着在物联网程序中查找漏洞所需的计算资源大约是桌面程序的 10 倍。
    • 全系统仿真的巨大运行时开销的一部分来自内存管理单元 (即 SoftMMU) 的软件实现,用于将虚拟机中发生的每一次内存访问的客户虚拟地址转换为主机虚拟地址。开销的另一部分来自系统调用模拟开销
  • 我们的解决方案:通过增强过程仿真进行灰盒模糊测试。
    1. 优势在于透明度和效率
      1. 透明度,即不需要对固件中的程序进行修改,
      2. 效率,即整个系统的模糊吞吐量应该接近用户模式仿真。
    2. 关键在于将全系统仿真和用户模式仿真相结合,得到了全系统仿真的通用性和用户模式仿真的效率。
    3. 主要是通过系统仿真来加强进程(或者说用户模式)的仿真。具体来说是被测程序主要运行在用户态的仿真以达到高效的目的,只有在必要时切换到全系统仿真,以保证程序的正确执行,从而实现通用性。
    4. FIRM-AFL 基于 AFL 和 Firmadyne 实现,AFL 负责覆盖引导模糊测试,Firmadyne 负责仿真和全系统仿真与用户态仿真之间的切换

背景和动机

Fuzzing 和 qemu 跳过,没啥意思

测试物联网固件

  • 挑战
    1. 兼容性
    2. 代码覆盖率:黑盒模糊器的代码覆盖率很低,而白盒模糊器不能扩展到略大的代码库
  • 同类型工具对比
    1. Avatar:协调模拟器和物理硬件,Avatar 充当模拟器和实际硬件之间的软件代理。
      • 由于涉及到白盒模糊和缓慢的硬件,Avatar 的吞吐量预计会很低。
    2. IoTFuzzer:直接对设备进行黑盒测试,它的主要优点是通过目标设备的配套移动应用程序执行模糊。
      • IoTFuzzer 从未超过每秒 1 个测试用例的吞吐量,这是缓慢的
    3. Firmadyne:在系统模式 QEMU 中增加了对 IoT 固件的硬件支持,通过修改内核和驱动程序来完全模拟系统,以处理由于缺乏实际硬件而导致的物联网异常。更容易适应新的 IOT 固件。
      • 全系统模拟的吞吐量通常比本机执行(本机执行是指直接向硬件发送输入)好
    4. AFL:AFL 可以通过 qemu 进行二进制模糊,但是无法成功模拟大多数物联网程序。
  • Muench 等人[28]的研究表示相比较于部分仿真和本机执行,完全仿真拥有最大的吞吐量。
    • 因为物联网设备处理器要比桌面级处理器慢的多,所以你一旦涉及到真实硬件,速度就会被物联网设备处理器拖慢,所以基于桌面级处理器的全仿真要快得多
  • 作者在这里的评价标准聚焦于吞吐量,不太明白这个吞吐量是啥,应该就是指设备接收测试用例的速度吧

动机

  1. 固件大多是二进制文件,基于仿真是最佳的
  2. 完全仿真吞吐量要高于其他仿真方法

吞吐量的瓶颈所在:

  1. 内存地址转换:全系统仿真中的内存地址转换要比用户模式仿真中的复杂,耗时久
  2. 动态代码转换。块链接仅限于同一物理页面中的基本块,这意味着全系统仿真比在用户模式模拟中更频繁地调用转换器
  3. 系统调用仿真:用户模式仿真中,系统调用交给主机操作系统和硬件;但是在全系统仿真中,系统调用交给模拟的操作系统和硬件,速度较慢,并且其实不是所有的系统调用都需要仿真的操作系统和硬件来支持。

增强进程仿真

概述

挑战:兼容性和性能。第一个挑战可以通过全系统仿真来解决,但这会导致较差的性能。第二个挑战可以通过用户模式模拟解决,但会导致兼容性较差。 前提:

  1. 固件可以在系统模拟器中正确地仿真 (例如,系统模式 QEMU):在 Firmadyne[13]的帮助下,大部分 IoT 固件映像都能够满足这一要求。
  2. 固件支持 posix 兼容的操作系统:许多 IoT 固件映像使用 Linux 作为操作系统,因此满足了这一需求。

目标(对应了挑战的两点):

  1. 透明度:在增强进程模拟中运行的用户级程序的行为应该与在系统模式模拟中运行一样。
  2. 效率高:由于吞吐量是模糊的主要因素,增强的过程模拟需要尽可能高效。理想情况下,它应该接近纯用户模式模拟的性能。

方法概述

image.png

  • 首先固件在系统模式模拟器中启动,用户级程序(包括要模糊测试的程序)也启动。
  • 当要模糊化的程序到达预定的点 (例如,主函数的入口点,或在接收到第一个网络数据包后) 后,进程执行将迁移到用户模式模拟,以获得较高的执行速度。只有在极少数情况下,才会将执行迁移回系统模式执行,以确保执行的正确性。
  • 为了最小化迁移成本,内存状态在这两种模拟模式之间共享。系统模式模拟和用户模式模拟通过 RAM 文件来实现内存共享。
    • 注意,系统模式模拟将 RAM 文件视为物理内存,因此通过物理地址访问它,而用户模式模拟通过虚拟地址访问共享内存。
  • 程序先在用户模式模拟下运行,当出现系统调用时,借助 RAM 文件,模拟器迁移到系统模式仿真处理系统调用。当系统调用返回时,模拟器迁移回用户模式模拟。

内存映射(Memory Mapping)

引导(bootstrapping)

在系统模式模拟中启动 IoT 固件,并进一步启动指定的 IoT 程序。通过 DECAF[23](基于系统仿真的动态分析平台)提供的虚拟机内省进行监控执行情况,在执行达到预定 fork 点时遍历指定进程的页表,收集虚拟到物理的页映射信息,并将其发送到用户模式模拟端。然后,对于虚拟地址 (va) 到物理地址 (pa) 的每个映射,用户模式仿真端通过调用 mmap 建立映射,如下所示: image.png 实际上,我们将 RAM 文件的一页以物理地址作为偏移量映射到指定的虚拟地址。参数 prot 由对应页表项的保护位决定。从这一点开始,系统模式模拟中的执行暂停,CPU 状态被发送到用户模式模拟,然后在那里继续执行。 理解:这里是指在程序刚启动的时候如何将系统级仿真和用户级仿真的内存进行共享——就是先在系统级仿真运行,然后将页表发送到用户级仿真,构建虚拟内存和物理内存的映射,实现两个模式下的内存映射的统一。这个部分就是初始化(所以称之为引导)。

页面错误处理(Page fault handing)

  • 当发生页面错误的时候如何协调两个模式呢。作者为用户模式模拟中的页面错误注册了一个信号处理程序,这样主机操作系统就会将页面错误事件传递给用户模式模拟。当接收到信号后,用户模式仿真将 CPU 状态发送到系统模式仿真端,期望在系统模式仿真中处理页面错误,并为故障虚拟地址建立新的映射。
  • 系统模式模拟接收到 CPU 状态并恢复执行后,IoT 固件操作系统中的页面错误处理程序将响应此页面错误并尝试建立映射。
  • 难点在于如何确定发生错误的时刻、页面映射建立的时刻
    • 及时捕捉两种时刻,可以及时切换模式,最大限度提高速度
    • 因为操作系统同时处理多个任务,同时可能会发生大量的上下文切换,所以有难度。
    • 作者每个基本块的末尾,如果当前执行在指定的进程 (或线程) 内,则意味着执行已经从内核返回到用户空间,然后在 TLB 表中找到新建立的映射,连同 CPU 状态一块发送给用户模式模拟,它将通过调用 mmap 创建这个新的映射并恢复执行。
    • 如果由于某种原因,发生了错误,进程被终止,我们可以依靠 DECAF[23]提供的 VMI (虚拟机自检) 功能来获得通知,然后双方的整个执行都被终止。

理解:程序会一直在用户模式模拟下运行,当发生页面错误需要系统调用的时候,就会把错误抛给系统模式仿真,由它处理问题建立新映射,处理好了然后切换回到用户模式模拟。

预加载页面映射

  • 因为现代操作系统以惰性方式加载内存页。每个页面映射建立的时刻是第一次访问的页错误,但是对于作者的模拟来说,频繁的页错误会拖慢模糊速度。作者的解决方案是对于要模糊的程序的页面进行预加载,在引导阶段模拟对程序每个代码页的访问,在系统模式模拟下就迫使操作系统将页面建立完整。

系统调用重定向

  • 因为主机操作系统没办法完全处理物联网固件的系统调用,所以需要把用户模式模拟中的系统调用重定向到系统模式模拟。
  • 更具体地说,当用户模式模拟遇到系统调用时,它暂停执行,保存当前 CPU 状态,并将其发送给系统模式模拟。系统模式模拟接收 CPU 状态并恢复执行。这将导致客户系统中的模式切换到内核模式以处理相应的系统调用。同样,由于客户操作系统内核是多任务的,在系统调用返回之前可能会发生许多上下文切换。因此,与处理页面错误的方式类似,我们将检测每个基本块的末尾。如果当前基本块在内核空间中,但是下一个程序计数器在用户级别,并且当前执行上下文是用于进行系统调用的线程的,那么我们检测系统调用返回的时刻。此时,我们暂停系统模式模拟中的执行,保存 CPU 状态,并将其传递回用户模式模拟,然后用户模式模拟将继续执行。
  • 理解:简单来说,这个问题本质上和页错误一样的,处理方法也一样。

优化与文件系统相关的系统调用

  • 作者发现物联网程序发出的许多系统调用都和文件系统有关。
  • 所以作者采用直接讲固件镜像中的文件系统挂载在主机操作系统上,这样当处于模拟中的物联网程序需要访问文件系统时无需切换到系统模式模拟中,可以直接在用户模式模拟中通过主机操作系统访问文件系统。

Firm-AFL 的设计和实现

AFL 的工作流

AFL 很熟了,不再赘述。

带有增强进程仿真的 AFL

image.png FIRM-AFL 的差别在于用增强进程仿真代替了 QEMU 。

引导

没有新东西,和前面说的一样。

Forking

  • 区别于 AFL 默认 main 函数为 fork 点,本文因为想要找通过网络接口触发的漏洞,所以与网络相关的系统调用的第一次调用都将作为 fork 点
  • 不仅需要为用户模式模拟派生一个子进程,还需要为系统模式模拟“派生”一个新的虚拟机实例,因为两种模式必须彼此同步。但是创建一个新的虚拟机代价太高,所以作者选择在 fork 点创建快照的方式。
  • QEMU 提供了快照功能,将所有 CPU 寄存器和内存空间保存到特定的文件中。,但是文件的读写操作还是非常慢。作者实现了一种基于写时拷贝原则的轻量级快照机制。
    • 原理是不全部记录初始的内存状态,只记录那些在程序运行时会被修改的页的初始状态。当新一轮的 fuzz 要开始时,将保存的页的初始状态写回。
    • 更具体地说,我们首先将映射到系统模式 QEMU 的 RAM 文件标记为只读。然后内存写入将导致页面错误。我们复制该页,然后将该页标记为可写。因此,我们记录在一次模糊执行期间修改过的所有内存页。在恢复快照时,我们只需要将这些记录的页面写回。
  • 理解:作者在此部分的工作创新点主要是这个快照机制,只记录会变的,不变的不去动它。

Feeding input

输入通过检测系统调用提供。对于从网络接口接收输入的物联网程序,我们直接在用户模式模拟中检测与网络相关的系统调用,因此不需要将这些系统调用重定向到系统模式模拟。 理解:因为输入是我们提供的,所以我们直接在用户模式模拟下截获对网络输入的系统调用,直接把输入 feed,不用切换到系统模拟模式下。

收集覆盖信息

  • 由于大多数执行都发生在用户模式模拟中,而系统模式模拟只需要处理页面错误和一些系统调用,所以在用户模式模拟中采取 AFL 的收集覆盖信息的措施——计算覆盖位图。

评价

问题

  1. 透明度。能否从物联网固件中提取 FIRM-AFL fuzz 程序,就像它们在全系统模拟器中运行一样?
  2. 效率高。FIRM-AFL 的吞吐量 (执行/秒) 与基于纯用户模式仿真的模糊器的吞吐量有多接近?
  3. 优化的有效性。我们的优化技术是否成功地解决了我们确定的性能瓶颈?
  4. 漏洞发现的有效性。FIRM-AFL 在物联网固件中发现真正漏洞的有效性如何?

疑问

  • 全系统仿真和用户模式仿真的区别是啥
  • 关于 qume 的仿真原理
  • Mmap 函数