中文译名:HALucinator: 通过抽象层仿真重新托管固件 作者:Abraham A. Clements 单位:桑迪亚国家实验室 国家: #美国 年份: #2020年 来源: #USENIX会议 关键字: #固件 #仿真托管 代码地址: 笔记建立时间: 2023-03-04 10:29

摘要

背景:硬件和固件之间的紧密耦合以及嵌入式系统中的多样性使得对固件进行动态分析变得困难。 现状:固件开发人员经常使用抽象,如硬件抽象层(HAL),来简化他们的工作。 方法:利用这些抽象作为重新托管和分析固件的基础。通过为 HAL 函数提供高级替代(一个称为高级模拟 - HLE 的过程),将硬件和固件分离。首先通过二进制分析定位固件样本中的库函数,然后在全系统模拟器中提供这些函数的通用实现。 工作:对现有库匹配技术进行扩展,可以识别二进制固件中的库函数;使用简化处理程序喝外设模型来进行 re-hosting;集成到 AFL 来演示安全分析方面的应用

1引言

背景: 固件动态分析难

  • 固件有硬件依赖性,传统的手段不好实施
  • 嵌入式硬件提供有限的内省能力,包括极其有限数量的断点和监视点,大大限制了对固件进行动态分析的能力。
  • 没有源代码,并且固件难以获得,即使得到固件也难以处理复杂的环境依赖

模拟具有局限性:

  • 嵌入式硬件中的异构性对固件模拟构成了重大障碍,模拟需要对片上外设和内存布局以专门方式支持
    • 流行的开源 QEMU 模拟器支持不到 30 种 ARM 设备
    • 英特尔的 SIMICS [38, 57]支持许多 CPU 和外设,但要求分析师手动构建硬件级别的完整系统模型。
    • 大多数嵌入式系统还需要其他硬件组件才可能使固件运行(如传感器、存储设备或网络组件),然而这些外设几乎没有模拟支持

现状:

  • 模拟器将固件与不受支持的外设的交互转发的真实设备上
    • 受硬件可用的限制
    • 分析和插桩受限
  • 记录并重放或建模来自硬件的数据
  • 上述问题同样困扰固件开发人员
    • 芯片供应商和各种第三方提供了硬件抽象层 HAL。HAL 是软件库,为程序员提供高级硬件操作,同时隐藏固件执行所在特定芯片或系统的细节。

方法: 通过使用高级抽象层和可重用的替换功能来实现嵌入式系统的可扩展模拟,称为高级模拟 (HLE)

  • 首先识别固件映像中负责硬件交互的 HAL 函数
  • 然后 HLE 提供简单的、分析师创建的高级替换(称之为处理程序)替换 HAL 函数,
    • 这种替换可以扩展到来自同一供应商的芯片,甚至扩展到使用相同中间件库的固件

这些处理程序可以使用外设模型,它们作为硬件外设通用类别的抽象,并在模拟环境和主机环境之间作为交互点,而无需复杂逻辑。 贡献:

  • 在 QEMU 的基础上利用 HAL 实现了一个高级模拟系统 HALucinator
  • 支持多个芯片供应商的 ARM Cortex-M 架构的 blob 固件
  • 改进了现有的库匹配技术,以更好地定位固件中用于拦截的函数
  • 利用 HALucinator 进行固件的模糊测试,效果良好 image.png

2动机

鉴于目前市场上的趋势,作者预计未来固件中将普遍采用 HAL。

2.1 模拟硬件和外设

  • 片上外设可以通过模拟内存映射 I/O(MMIO)来实现,因为在芯片文档中对每个外设的 MMIO 区域的确切布局和语义有描述
  • 进一步复杂化重新托管的是固件与片外设备(例如传感器、执行器、外部存储设备或通信硬件)之间的交互。
    • 这种外设涉及到定制化的电路板,所以每个固件样本的完整执行环境基本上都是独一无二的

想要使用现有的仿真工具进行模拟,就必须实现符合固件使用 MMIO 寄存器接口规范的片上和片外设备的模拟。

2.2 固件栈

image.png 图 2a 展示了一个通过 http 服务器获取温度的软硬件堆栈。首先 http server 的某个应用程序调用温度传感器制造商提供的库中的 API 来获取温度,该库向下又调用微控制器制造商提供的 I2C HAL 通过 I2C 总线与温度传感器通信获得温度信息。Http server 得到温度后,调用 OS 库的 API 发送和接收 TCP 信息,OS 使用 LWIP 库进行 tcp 报文的封装,LWIP 库使用以太网 HAL 通过物理以太网端口发送以太网帧。 这个例子表示,现代设备的复杂性和减少开发时间的压力越来越使得固件中的功能建立在一系列中间件库和 HAL 之上。这样实现了应用程序和物理硬件的分离。为了让 HALucinator 打破固件与硬件之间的耦合,它必须拦截其中一个层次——中间件/库或 HAL——并插入其替代功能。 所以选择在哪个层次进行替换需要权衡高级替换的通用性和可重用性、代码量以及在目标设备固件中找到给定库的可能性。 作者可以确定固件开发者更有可能使用芯片供应商的 HAL,而对于更高层次(如网络堆栈或中间件),无法预测开发者用了什么库。所以作者主要关注在 HAL 层次上重新托管。 但是作者也指出 HAL 层具有最多的函数,并且与硬件产生复杂的交互(中断和 DMA),而围绕高层次的库构建的处理程序可以更简单,更容易在设备间移植。

2.3 高级仿真

  • 作者的方法减少了模拟的工作量,模拟工作量随着 HAL 或中间件库数量增加而增长得更慢。
  • 高级模拟消除了理解硬件低级细节的要求。因此,处理程序不需要实现低级 MMIO 操作,而只需截获相应的 HAL 函数,将所需参数传递给适当的外设模型并返回固件期望的值即可。
  • 对于分析时不关心或者不必要的外设,作者的方法也允许使用简单低保真度处理程序绕过该函数并返回指示成功执行的值

3 设计

为了让我们的设计充分利用高层模拟的优势,我们需要(1)在固件中找到HAL库函数(例如,通过库匹配),(2)为HAL函数提供高层替代品,并且(3)启用与模拟固件的外部交互。 HALucinator采用模块化设计,以便于其与各种固件和分析情况一起使用,如图1所示。 为了介绍HALucinator的各个阶段和组成部分,让我们考虑一个简单的示例固件,它使用串行端口将来自连接计算机的字符回显。 除了硬件初始化代码外,此固件仅需要发送和接收串行数据的能力。 分析师注意到设备的CPU是STM32F4微控制器,并使用第3.2节中介绍的LibMatch分析,并为STMicroelectrics’ HAL库构建数据库以用于此芯片系列。 这确定了二进制文件中的HAL_UART_Receive和HAL_UART_Transmit。 然后,分析师为HALucinator创建配置,指示应使用一组处理程序(即高级功能替换)来处理包含的HAL。 如果处理程序尚不存在,则由分析师创建它们。 这两个HAL函数将串行端口引用、缓冲区指针和长度作为参数传递。 为了节省精力,这些处理程序只需将这些参数转换成可由串行端口外设模型使用的形式(例如要发送或接收的原始数据)。 最后,I/O服务器在串行端口外设模型和主机计算机终端之间传输数据。 现在,在HALucinator中执行固件时,可以像任何其他控制台程序一样通过终端使用固件。 这只代表了HALucinator能力的一小部分,在以下各节中我们将详细探讨。

3.1 先决条件

  • 需要设备的完整固件
  • 想要模拟的库和编译工具链
  • 一个能够忠实执行固件代码并能够支持 HALucinator 的仪器的底层模拟器
    • 可配置的内存布局
    • 能挂钩代码中的特定地址以触发高级处理程序
    • 访问模拟器的寄存器和内存

虽然这看起来是一长串要求,但实际上获得它们很简单。对于我们在本工作中关注的 ARM Cortex-M 设备,通用内存映射已标准化并可从供应商提供的手册中轻松获得,固件在内存中的位置可以从固件 blob 本身读取,并且常见模拟器(例如 QEMU [18])忠实地模拟指令。每个 Cortex-M 供应商都为其芯片提供开源 HAL(s),带有编译器和配置[16、34、42、53]。为了将 HALucinator 应用于特定设备,所需的只是获取固件,知道 CPU 的供应商并获取其 SDK。

3.2 libmatch

  • 目的:High level emulation 的一个关键部分就是需要在程序中定位可以用作模拟基础的抽象
  • 现状:
    • 现有解决在剥离二进制文件中查找函数问题的方法[24、33、35]缺乏对嵌入式 CPU 架构(尤其是 ARM Cortex-M 架构)的支持,该架构常用于许多消费级别的设备并且在本工作中使用。
    • 固件库函数通常会对大小进行优化,导致两个几乎相同的代码却用于截然不同的目的
    • 固件库函数的一个不寻常特征是它们经常调用非库部分代码中的函数
      • 意思是有一部分固件库函数的参数是函数指针,也就是回调函数,所以为了提供完全工作的处理程序,我们不仅必须恢复库函数名称和地址,还必须恢复它们调用应用程序代码的名称和地址。
  • 方法:libmatch,利用程序中函数的上下文来帮助二进制到库匹配
    • 通过提取库的未链接二进制对象文件的控制流图以及它们代码的中间表示(IR)来创建 HAL 函数匹配数据库
    • 然后基于以下几个原则进行匹配:
      1. 统计比较:比较目标程序和数据库中库函数的基本块数量、CFG 边和函数调用
      2. 基本块比较:如果两个函数的每个基本块的 IR 内容完全匹配,我们认为它们匹配。
      3. 上下文匹配:前一步可以输出一组匹配,但是也会输出那些没有匹配成功的函数,那么利用目标程序中函数的上下文中已经匹配成功的函数来推断函数可能是什么
      4. 最后,如果给目标二进制中的一个函数分配了唯一名称,则确定为有效匹配。

3.3 高级模拟

Handlers

  • 我们将固件中 HAL 代码的高级替换称为处理程序(Handlers)。
  • 创建处理程序是手动完成的,但只需要为每个 HAL 或库执行一次,并且与正在分析的固件无关
  • 几乎所有的处理程序都很简单,属于几个基本类别之一(比如在外围模型上执行琐碎的操作,返回常量值,或者什么都不做),对于复杂的 HAL 可以利用迭代过程来构建处理程序
    • 首先分析人员在 HALucinator 运行二进制文件,它将报告当前未被处理程序替换的所有 I/O 访问以及位置
    • 如果固件卡住了,或者缺少所需的行为,分析人员可以评估哪些函数包含 I/O 操作,并考虑实现一个处理程序。过程重复,连续的处理程序产生更大的覆盖范围和更准确的功能。

外设模型

  • 用于模拟外设行为,负责处理固件和外设交互的模型

I/O server

  • 用于外设模型和主机系统的交互。

HAL 外的外设访问

  • 固件中也存在直接发起(绕过 HAL)的 MMIO 访问
  • HALucinator 将向用户报告所有此类 I/O,对这些区域的所有读取操作都将返回零,并且所有写入都将被忽略,从而允许直接与硬件交互的代码执行而不崩溃。(作者发现大多数 MMIO 访问可以安全的忽略)

3.4 使用 HALucinator 进行模糊测试

Fuzzed input

  • HALucinator 提供了将 fuzzer 的输入流转入到处理程序的方法

Termination

  • 目前的模糊器通常针对桌面程序,并期望它们在输入耗尽时终止; 但是,固件永远不会终止。
  • HALucinator,设计 fuzz 模型以优雅地退出程序,向 fuzzer 发送一个信号,表明程序在执行期间没有崩溃。

Non-determinism

  • 固件有明显的不确定性行为,必须删除,以允许 fuzzer 正确地收集覆盖指标
  • HALucinator 在确定产生随机性的函数时,为它们提供静态处理程序

Timers

  • 不确定性的一个特殊情况是定时器,它经常作为特殊的外围设备出现在微控制器中,以指定的间隔触发中断和其他事件
  • 我们提供了一个定时器外围模型,它将计时器的速率与执行块的数量联系起来,创建确定的计时器行为,并公平地执行计时器的中断处理程序和主程序,而不考虑仿真速度。

Crash Detection

  • 基于高级仿真的系统从仿真器提供的可见性中获得了大量的崩溃检测能力
  • 高级模拟处理程序可以执行它们自己的检查
  • 高级仿真还可用于轻松添加通常在编译时处理的插装

Input Generation

  • 模糊化需要有代表性的输入来为其突变算法提供种子。HALucinator 的全交互模式可用于与设备交互,并记录感兴趣的库调用的返回值,这可用于种子模糊

实施

LibMatch

  • 基于 angr 二进制分析平台,对 angr 进行扩展,增加了对 Cortex-M 调用约定、缺失指令、函数开始检测和间接跳转解析的支持。
  • LibMatch 使用带有符号的未链接的目标文件,来创建已知函数的数据库,使用这个数据库来定位固件中没有符号的函数。
  • 当 LibMatch 针对固件样例运行时,它输出一个已识别的函数及其地址列表,并记录冲突,以防人工分析人员希望手动解决冲突。

HALucinator Implementation

  • HALucinator 是用 Python 实现的,并使用 Avatar2 来设置一个全系统 QEMU 仿真目标并检测其执行
    • 虽然 Avatar2 通常是作为硬件在环编制方案部署的,但我们在这里仅使用它来实现对 QEMU 的灵活控制,而不是用于任何与硬件相关的目的
  • 输入:内存布局、要拦截的函数列表和相关处理程序,来自 libMatch 的函数和地址列表
  • 操作:在要拦截的每个函数的第一条指令上放置断点,并注册处理程序,以便在命中断点时执行
  • Handlers
    • 处理程序被实现为 Python 类,每个函数覆盖固件的 HAL 或库中的一个或多个函数
    • 可以读取或写入模拟器的寄存器和内存
    • 调用固件本身函数
    • 与外围模型交互
  • 外设模型
    • 外设模型作为 Python 类实现,可以充分利用系统库或 I/O 服务器来实现所需的功能
    • 例如,从硬件实时时钟获取时间的调用可以简单地调用主机系统的 time () 函数。
  • I/O 服务器
    • 使用 ZeroMQ消息传递库实现为发布-订阅系统。除了向主机系统的外围模型提供事件外,I/O 服务器还可以将模拟器的外围模型连接在一起,从而允许模拟多个互连的系统。

Fuzzing with HALucinator

  • 将 HALucinator 中心的全系统 QEMU 引擎替换为 AFLUnicorn
    • AFL- unicorn 将 QEMU 的 ISA 模拟特性与灵活的 API 结合在一起,并提供 AFL 使用的覆盖工具和分叉服务器功能
    • HALucinator 的高级仿真可以提供所需的外围硬件支持

评估

Library Identification in Binaries

我们首先探讨 LibMatch 在二进制固件程序中恢复函数地址的有效性。由于固件中有多个位置可能被连接,在模拟的复杂性中有各种各样的权衡,在这里我们尝试匹配 HAL 及其相关中间件提供的整个功能集。我们在每个目标固件样例中使用符号信息来提供每个函数的真实地址。然后,LibMatch 尝试使用该二进制文件的剥离版本确定其 HAL 数据库中每个函数的地址。表 1 显示了使用 LibMatch 进行上下文匹配和不进行上下文匹配的 16 个固件示例的比较。没有上下文匹配的 LibMatch 与当前匹配算法 (例如,BinDiff[28]或 Diaphora[21]) 所实现的效果相当。然而,直接比较是不可能的,因为这些工具只执行链接二进制到链接二进制的比较,而 LibMatch 必须将链接二进制与从 HALs 和中间件获得的未链接库对象集合相匹配。在表 1 中,HAL 符号的数量是固件中存在的库函数的数量,而“正确”列显示了那些正确识别的函数的数量。“碰撞”、“不正确”和“缺失”列描述了 LibMatch 无法正确识别未匹配函数的原因。最后一列,’ External ‘是 HAL 库外部的函数的数量,LibMatch 与上下文匹配标签正确。总的来说,在 16 个应用程序中,没有上下文匹配的 LibMatch 平均匹配了 74.5%的库函数,而带有上下文匹配的 LibMatch 平均匹配了 87.4%。因此,几乎所有的 HAL 和中间件库都精确地位于二进制文件中。 image.png 上图可以看到在 16 个应用程序中,没有上下文匹配的 LibMatch 平均匹配了 74.5%的库函数,而带有上下文匹配的 LibMatch 平均匹配了 87.4%。因此,几乎所有的 HAL 和中间件库都精确地位于二进制文件中。

5.2 Scaling of High-Level Emulation

Handlers and Human Effort 实现处理程序是一项手动任务,因此需要量化实现处理程序的工作量。将实验中使用的处理程序分为三类:

  • 普通处理程序:只返回一个常量,通常用于硬件初始化函数
  • 转化处理程序:将截取的函数参数转换为外设模型上的操作,它们不实现任何逻辑,而只是在获得模型的适当数据后调用模型。
  • 内部逻辑处理程序:需要理解替换函数的内部逻辑 image.png

跨设备扩展 为了演示 HLE 如何允许一个 HAL 的仿真跨设备扩展,我们使用 NXP MCUXpresso HAL 的样本构建了一个实验,每个样本来自不同的板和 CPU。这些代表 NXPs 的每个主要 ARM 微控制器产品系列的芯片,包括 Kinetis、LPC 和i.MX,它们的设计和外设布局完全不同,因为它们是在以前独立的公司开发的。不管家族和血统如何,所有这些部分都共享相同的 HAL。结果,我们从 20 个不同的开发板获得了 uart_polling 示例的 20 个实例。之所以选择 uart_polling 示例,是因为几乎每个单板上都有 uart,而其他外设的存在因单板而异。然后,我们使用相同的 NXP UART 处理程序和外设模型来模拟这 20 个固件示例。具体来说,我们使用了三个处理程序,一个发送处理程序、一个接收处理程序和一个返回 0 的默认处理程序。不同固件在 HALucinator 配置上的唯一差异是 RAM/Flash 布局、时钟拦截和电源初始化函数,所有这些都是由普通的默认处理程序处理的。总共截获了 29 个独特的函数。每块板至少拦截 6 个功能,最多 9 个,平均 6.9 个。这表明相同的处理程序和模型可以用于支持多个产品系列。唯一的挑战是识别被拦截的时钟和电源初始化函数的名称。

5.3Interactive Emulation Comparison

实验设置:

  • 使用 QEMU、Avatar2 [43] 和 HALucinator 交互式地重新托管表 1 中显示的 16 个固件样本
    • 在这个实验中,使用 Avatar2 提供的 QEMU 的默认配置,并在没有硬件存在的情况下将固件加载并执行到 QEMU 中。在这种配置下,对 QEMU 中不支持的 MMIO 的任何访问都会出错。
    • HALucinator 对 LibMatch 匹配的函数进行高级替换,对于执行的任何 MMIO,实现了一个默认的 MMIO 处理程序,该程序对读取返回零,对写入静默忽略。

作者的一些解释和说明:

  • 作者指出如果模拟系统上能够执行与真实硬件相同的功能,那么外部行为就是“正确”的
    • 所以 HALucinator 可以做到正确回应固件运行中对外设的请求
      • TCP/UDP 可以成功传输和物理硬件相同的数据
      • 能够访问 HTTP 服务器固件样本上的相同页面
      • FatFs 示例能够在其文件系统内的相应文件中读取和写入所需数据
      • 6LoWPAN 示例成功地相互通信
      • UART 示例能够通过它们的 UART 发送和接收数据并给出预期的响应
    • 虽然作者指出无法验证和真实的物理硬件相比是否遵循了相同的代码路径,但是 HALucinatior 实现的模拟已经足够应用于其他的安全测试,比如模糊测试。
  • HALucinator 在所有情况下都能实现正确的黑盒行为——所有厂商、所有板子、所有固件样本
    • Avatar2 只成功模拟了 NXP UART 固件,因为这个固件轮询 MMIO 并且不使用任何中断。
    • 对于 QEMU 来说,搞不定 MMIO,当发生任何 MMIO 时,QEMU 都挂了(总线故障)
    • Avatar2 采用 MMIO 转发的方式,但是有的固件有启用了 SysTick 计时器,你 MMIO 一直不回应,回应的慢也会挂
      • HALucinator 拦截初始化 SysTick 计时器的 HAL 函数并替换为一个计数器来保持时间;使其能够避免这个问题

表 3 显示了每个固件使用的软件库以及 HALucinator 模拟的接口。对于每种技术,它显示了执行的唯一基本块的数量(“BB”),这表明固件执行了多少。它还显示外部输入和输出行为是否与在物理硬件上执行固件时观察到的匹配(外部行为正确 – “EBC”)。对于 Avatar2,我们报告转发到板子的读写次数(“Fwd R/W”),这表明 Avatar2 正确地转发了内存请求。对于 HALucinator,我们报告拦截的函数数量(“Funcs”)和默认 MMIO 处理的唯一地址数量。拦截的函数数量给出了使用 HALucinator 模拟固件所需工作量的度量,而使用默认处理程序的 MMIO 是对硬件的访问,这些访问可能会被进一步拦截 HAL 函数替换。 image.png 这个实验展示了 HALucinator 如何实现复杂固件的模拟,该固件在真实硬件上执行时具有与现有方法无法做到的相同的外部功能。HALucinator 在我们的样本固件上平均执行了超过 1, 000 个基本块,是 Avatar2 的 10 倍。来自三个不同制造商的四个不同板子的模拟展示了 HLE 支持各种硬件的能力,以及所有板子都使用相同外设模型的可扩展性,显示了它们在供应商和硬件平台之间的可扩展性。

5.4 Fuzzing with HALucinator

image.png

巴拉巴拉,大概就是说找了好多漏洞。

相关工作

函数识别和标记

  • BinDiff [28, 55] 及其开源对应物 Diaphora [21] 使用图匹配技术有效且高效地比较两个程序。但并未考虑冲突。
  • 特征提取技术包括基于函数前奏的签名 [31],从系统调用的后向切片 [35],以及来自符号执行的跟踪 [46, 47]。
  • 匹配提取的特征已通过贝叶斯网络 [15],神经网络 [33] 和局部敏感哈希 [24] 进行。
  • 这些系统都不适用于标记固件中的函数:无法分析或执行 ARM Cortex-M 代码,由于 HAL 中函数的小尺寸和相似性,机器学习方法可用的信息不足,以及某些方法无法有效地处理冲突。

固件模拟

  • 最常见的方法采用硬件在环执行,如 AVATAR [58],AVATAR2 [43] 和 SURROGATES [36]

    • 在这些系统中,物理目标设备与分析环境相连,通常使用调试端口,并在执行期间由标准模拟器使用其硬件外设。
    • 受其对硬件的可见性限制——外设内部的状态与模拟器不同步
    • 不支持中断或直接存储器访问 (DMA)
  • 另一种方法 [19, 20] 模拟涉及使用高级操作系统(如 Linux)的存在作为抽象点,并用能够在模拟器中运行的版本替换固件的版本。这可以被认为是一种高级模拟,因为它使用用户内核屏障作为建模边界。然而,它只适用于具有文件系统映像的固件,该映像可以在不运行任何设备特定代码的情况下启动。在这项工作中,我们专门针对没有这样的操作系统的设备中发现的“blob”固件

不足和讨论

  • HALucinator 完全基于 HAL,固件如果没有使用 HAL,那就干不了了
  • 当编译器或库版本未知时,LibMatch 的有效性是有限的。函数匹配技术无法应对编译器引起的生成代码中的变化。LibMatch 的主要贡献是使用函数的上下文(被调用者/调用者)来消除二进制等效函数的歧义。

编写处理程序的时候对于普通程序的处理是不是有点太草率了 如何实现 DMA 的