当前位置:   article > 正文

【SEmu】What Your Firmware Tells You Is Not How You Should Emulate It: A Specification-Guided Approach

what your firmware tells you is not how you should emulate it: a specificati

【题目】What Your Firmware Tells You Is Not How You Should Emulate It: A Specification-Guided Approach for Firmware Emulation
【来源】2022 CCS
【笔记建立时间】2023-4-4

摘要

  • 由于缺少外设模型,仿真微控制器固件具有挑战性。
  • 现有工作通过分析目标固件找出如何响应外围设备的读操作。这是存在问题的,因为固件有时不包含足够的线索去支持仿真,甚至包含一些误导信息。
  • 在本文中,我们提出一种新方法,从外设规范构建外设模型。
    • 使用NLP技术,我们将外设行为(记录在芯片手册)翻译成一套结构化的条件-动作规则。通过在运行时检查,执行和链接它们(即规则),我们可以为每一个固件动态生成一个外设模型。
    • 但是,提取的条件-动作规则可能是不完整的,甚至是错误的。因此,我们建议结合符号执行去快速定位规则错误的根本原因,手动纠正有问题的规则。

引言

  • 涉及到真实外设使得固件的安全分析相当困难。因此,一些基于仿真的技术被提出。通过在主机重新托管固件,许多现有的动态安全分析方法可以在不涉及实际硬件情况下实现。
  • 固件仿真的一个主要挑战是对未知外设的行为进行建模。特别是,当固件读取外设寄存器时,我们怎么生成适当的回应?现有工作的普遍共识是,仿真器应该生成满足固件期望的响应,以便它不会crash或hang。
  • 固件引导的解决方案(即从固件本身学习关于未知外设的知识)
    • P2IM通过观察外设访问模式自动构建外设模型,当在仿真器中运行目标固件时。
    • Laelaps,uEmu,JetsetFuzzware使用符号执行去探索固件以找到满意的响应值。
    • 现有方法仅从固件中学习粗粒度的静态外设模型。不完整或者误导性的信息会导致严重的低保真度(即相似度很低)问题,从而影响构建在仿真器上的固件分析工具的有效性,效率和适用性。
      • 有效性:复杂外设的驱动程序代码不能被正确仿真。动态分析会排除大量真实世界的固件(例如,现有的工作都不能模糊测试有以太网功能的固件)。
      • 效率:即使目标固件可以被仿真,执行轨迹也可能与真实硬件上的轨迹有很大偏差。这导致假阳性(即在不可行的路径找到不存在的错误)和假阴性(即由于遗漏代码覆盖而无法找到真正的错误)。此外,未能正确传递中断使得仿真过程缓慢。
      • 适用性:低保真度仿真限制了可以采用的动态分析的类型。注意到,与固件仿真的相关工作局限在模糊测试,其可以容忍不正确的仿真,因为人类必须手动确认错误。而高保真度仿真器可以推动Fuzzing之外的新的安全分析应用。
  • 外设规范引导的解决方案(即外设行为翻译为一套规则)
    • MCU芯片供应商通常会为每个芯片发布基于文本的参考手册,采用自然语言(English)来描述外设行为。
    • 以下观察结果有助于我们利用规范更加准确的仿真外设
      • 芯片手册中的单个句子可以容易地被自然语言处理引擎理解,并被形式化为一些简单地条件-动作规则(C-A规则)。
      • 无需显式维护状态机,策略性地执行这些C-A规则可以仿真外设。
    • 在获得足够的C-A规则后,在运行时,我们为固件执行期间访问的每个外设动态合成一个模型。具体来说,每个外设模型代表相应的硬件外设提供适当的响应。同时,通过有选择地以正确的顺序执行某些相关的C-A规则来维护外围设备的状态(执行这些规则隐式地维护了相应的状态机)。
    • 由于糟糕的手册和NLP引擎本身的限制,偶尔会发现C-A规则缺失或者错误,导致外围设备建模不准确。为了解决此问题,引入了uEmu提出的无效引导仿真来快速定位缺失/错误的C-A规则。 直白地说,如果一个C-A规则丢失或者错误,仿真可能进入无效状态。如果发生这种情况,我们使用符号执行来快速识别哪个外设读操作对错误负责,并相应地修复C-A规则。(基于NLP技术从芯片手册自动提取C-A规则和无效性引导的仿真对提取的规则进行诊断和增强是互补的操作。)
    • 为了客观比较我们的解决方案与现有工作,我们提出了一种基于修改编辑距离的新方法来量化仿真的保真度(即轨迹相似性)
    • 最后,将我们提出的SEmu应用到一个新的动态分析任务,即基于规范的一致性检查,利用手册中提取的语义信息,检查外设驱动程序的实现是否符合芯片手册中指定的逻辑。

背景

MCU(单片机)概述

  • 当我们使用单片机的时候,我们在使用它的什么?计算能力?逻辑分析能力?不!我们使用最多的是它所提供的各个功能模块(外设模块)。
    • 单片机的主要功能是通过控制外设与外部环境交互。
  • 为了正确地操作外设,固件应该在任何一次访问之前确保外设处于正确的状态;否则,不被期待的行为可能发生。
  • 为了确定外设是否准备好响应某个访问请求,固件通常首先读取相应的状态寄存器SR来查询当前外设的状态,状态寄存器是外设映射到内存空间(MMIO)。
  • 外设还可以通过中断机制通知处理器(固件)外部事件,一般,外部事件会导致外设状态改变。一些高吞吐量外设可以直接使用DMA在内存和外设之间传输数据,而不涉及CPU。

单片机参考手册

  • 单片机参考手册应该提供关于如何使用每个外设的基本信息,包括其寄存器-内存映射,硬件接口和其他行为。
  • 由于每个外设包含多个寄存器,手册至少需要一个部分写寄存器内存映射去总结每个外设寄存器对应的MMIO地址和其访问权限。
    • MMIO(内存映射I/O)
      • 内存和I/O设备共享同一个地址空间。
      • I/O设备的内存和寄存器被映射到与之相关联的地址。
      • 当CPU访问某个地址空间时,它可能是物理内存,也可能是某个I/O设备的内存。
  • 手册还包括一个中断向量表,记录了为每个外设分配的中断号(即IRQ号)。一些外设可能有多个IRQ号用于不同功能。
  • 举个栗子(手册中的寄存器字段描述)

    RDRF is set when the number of datawords in the receive buffer is equal to or more than the numberindicated by RWFIFO[RXW ATER].

    • 以NAP K64F的UART外设为例,手册中使用上面的句子描述SR1寄存器的RDRF字段。即当…,设置RDRF为1。
    • 寄存器有不同字段,比如状态字段,控制字段和数据字段,用于不同功能(例如,状态字段用于指示外设的当前状态)。

自然语言处理

  • 以上面寄存器字段描述中引用的句子为例
    • 词性标注技术POS可以识别命名实体。句子中有三个命名实体:RDRF,receive buffer, RWFIFO。
    • 成分分析技术首先对每个词做词性标注POS,然后再将其组成短语,再将短语不断合并构成更大的短语。在本例中,动作是"RDRF is set",其余子句是条件。
    • 依赖分析技术主要关心的是句子中的每一个词都依赖于哪个其他的词,从而得到句子中的所有依赖关系。在本例中,set是依赖于名词短语RDRF的谓语。
    • 参考资料1|参考资料2

动机和关键idea

  • 首先解释了现有的低保真度仿真解决方案如何影响动态分析方法,尤其是模糊测试。
  • 然后使用直观的例子展示我们的方法如何实现更高的保真度。

低保真度问题

  • 举个栗子(真实固件的代码片段)
    在这里插入图片描述

    在大部分单片机构建的应用产品中,基本都是以前后台方式(大循环加中断)的方式来实现功能,在主循环中处理应用,并在中断处理程序中处理外部的触发信号。

  • 在这个例子(真实固件的代码片段)中,在现有的固件引导的工作中实现的近似仿真在进行模糊测试时,至少存在两个缺陷,这从根本上是由固件中包含的不完整信息引起的。
    • 首先,仿真器不知道中断传递的类型和时间。因,此它以循环的方式触发每一个活动中断。但是在固件执行期间有很多活动中断,仿真器选择UART中断通常需要很多时间。
    • 其次,即使UART中断被触发,状态寄存器和控制寄存器也应该保存正确的值,以便在中断处理程序中调用正确的子函数(在本例中,正确子函数为接收数据子函数)。然而,现有工作不能保证这一点,因为其它子函数(在本例中,如传输数据子函数)也不会导致执行崩溃,因此也可以选择。显然,固件不能告诉仿真器应该执行哪个子函数,即固件中不包含相关信息。
  • 总之,固件缺乏一些引导仿真器的信息,使得仿真器必须在输入空间中盲目地尝试所有可能的组合(例如,中断时间和状态寄存器值)。虽然这些解决方案由于一些巧妙的设计可以避免崩溃和挂起,但是它们不太注重仿真器的保真度。

使用C-A规则模仿状态转换

  • 举个栗子(以UART外设为例)
    在这里插入图片描述
  • 注意
    • C-A规则对应下面的状态图的边和节点。
    • 此例中,存在跨外设操作。(C-A规则2)

概述

  • 利用外设规范构建外设模型,其实可以说是模拟了一个真实的仿真器开发过程。
    • 在仿真器开发过程中,开发人员阅读芯片手册,并相应的编写外设模型作为仿真器的后端。
    • 为了使其可扩展,我们采用了半自动方法。
  • 给定一个芯片手册,我们使用NLP引擎提取一组描述外设行为的C-A规则。
    • 关键的观察是,虽然芯片手册的编写语言是多样的,但是大多数句子结构相似,使得C-A规则的足够准确。
      在这里插入图片描述
  • 有了这些规则,在运行时,仿真器将拦截固件和外设的交互,驱动C-A规则的检查,执行和链接(这模拟了外设的状态转换,无需显式维护状态机)
    • 当固件向外设寄存器发出读请求时,仿真器会检查当前外设状态并计算一个响应。
    • 我们的方法根据规范动态地构建外设模型,实现状态感知的固件仿真。
  • 由于文档的不完善和NLP引擎本身的限制,我们偶尔会观察到失败的仿真。因此,我们开发了一种符号执行辅助诊断技术来快速定位错误或缺失的C-A规则。诊断结果进行手动分析,然后修改自动提取的C-A规则来修复问题。
  • 在提出的状态感知仿真器基础上。开发了两个安全分析插件。
    • 基于AFL的模糊器,用于查找固件中与内存相关的错误。
    • 一致性检查工具,用于跟踪每个外设的MMIO访问次序并将其与规范中的静态规则进行比较。

从芯片规范中提取C-A规则

条件和动作

  • 条件是由若干谓词组成(每个谓词指定命名实体的值是否等于、大于、小于一个参考值或另一个命名实体的值);动作是一个赋值函数(将一个值赋值给一个命名实体)。
  • 条件是事件驱动的。当某些事件发生时,某个条件可能得到满足。
  • 事件通过信号传递。
  • 根据底层信号的产生方式,我们将受影响的条件分为三种类型:
    • C1:通过外部硬件生成的信号,这些信号由外部硬件事件驱动,例如从物理UART接口接收新数据,通常可以抽象为用新数据填充内部缓冲区。
      • 对于这种类型条件的描述通常包含一个关键词"harewre"或者"buffer"
    • C2:通过固件生成的信号,这种类型的条件由固件的操作驱动,例如在"LBKDIF is cleared by writing a 1 to it"这句话中,写操作是一个更新LBKDIF值的信号。
    • C3:通过内部信号,当寄存器值由于之前任何动作的执行而被更新时,一些相关的条件可能变为真。
  • 取决于固件与外设的交互方式(例如,MMIO访问、中断请求、DMA请求),将动作分为三种类型:
    • A1:与MMIO寄存器相关,这些动作更新寄存器字段的值。例如上面提到的"RDRF is set"。
    • A2:与中断相关,这些动作向中断控制器发送中断请求。
    • A3:与DMA相关,这些动作在DMA传输完成时生成DMA传输请求或中断请求。
      (这两种类型的动作通常由与缓冲区相关的信号触发)

C-A规则提取

自动提取相关的C-A规则面临几个挑战:

  • 如何识别与C-A规则相关的句子?
  • 如何识别和处理常见的co-references?(注意:co-reference是指语言中多个词或短语指代同一实体)
  • 如何识别句子之间的因果关系?

通过命名实体收集相关句子

  • 首先确定一组重要的命名实体,基于此,匹配收集相关句子。
    • 固件通过MMIO寄存器与外设进行交互,因此,外设寄存器及其字段是命名实体的主要来源。
    • 与数据寄存器关联的接收缓冲区和发送缓冲区看作命名实体。
  • 命名实体的初始集是从手册的寄存器内存映射部分提取的,然后找到至少包含一个命名实体的句子,若遇到新的命名实体则扩充命名实体集,不断迭代此过程,直到找不到新的命名实体或句子。

识别共指co-references

  • 自然语言中多种表达可能指代同一实体。例如,对于UART状态寄存器,我们已经遇到了至少以下不同表达:status register of UART, UARTx_SR, and SR.
    • 我们使用近似字符串匹配来测量未知名词短语(在POS标注中,标记为NP)与初始集中每个命名实体之间的相似性,以识别可能的共指。例如,在本例中,名词短语"RWFIFO[RXWATER]"接近"UART FIFO Receive Watermark(UARTx_RWFIFO)",我们知道"RWFIFO"是命名实体"UARTx_RWFIFO"的同义词。

识别条件和动作

  • 给定一个句子,我们使用Stanford Constituency Parser(一个流行的语言模型)来分析句子的语法结构。
  • 由于自然语言的复杂性,此工具有时无法识别复杂的条件子句,为此,可以扩展解析器使用的语法模式来指定语法成分。

    语法模式是将一个句子分成几个子句的一系列正则表达式。

  • 成分分析技术如图所示。在确定when开头的条件子句后,句子分为两个子句,动作和条件。
    在这里插入图片描述

C-A规则表示

  • 首先将条件和动作转换为谓词和赋值函数。为此,需要使用Stanford Constituency Parser将命名实体和相关动词连接起来。例如,“set"连接到命名实体"RDRF”。
  • 然后,需要提取动词短语中涉及的值和操作符语义。为此,构建一个映射表,其中一个或多个关键字会被映射:
    • 到一个特定值。例如"set"被映射到1,"cleared"被映射到0。
    • 到布尔运算符。例如"equal to or more than"被映射到">=“,赋值函数被映射到”:="。
  • 其次,制定规则触发器(即什么时候检查C-A规则)。定义了五种类型的触发器:
    • B触发器表示接收/发送缓冲区的数据发生改变时检查。
    • W触发器表示固件写操作时检查。
    • R触发器表示固件读操作时检查。
    • V触发器表示一个字段的值被内部信号更新时检查。
    • O触发器表示在任何其他信号(如the timer or manual invocations)上检查。
  • 最后,制定C-A规则。
    • 命名实体通常表示为Reg[Field],例如RWFIFO[RXWATER]
    • 对于数据寄存器,我们使用D[R]和D[T]表示相应的接收缓冲区和发送缓冲区。#D[R]表示该缓冲区当前占用的大小。
    • 举个栗子
      • 手册:RDRF is set when the number of datawords in the receive buffer is equal to or more than the number indicated by RWFIFO[RXWATER].
      • C-A规则:B #D[R]>=RWFIFO[RXWATER]->S1[RDRF]:=1
      • 解释:B表示该C-A规则由B触发器触发。“the number of datawords in the receive buff"表示为”#D[R]“。解析器会自动找到"RDRF"字段对应寄存器"S1”。

用C-A规则合成外设模型

  • 我们使用QEMU来仿真基本的ARM ISA指令集和内核外设(例如,中断控制器NVIC)。在固件仿真过程中,我们动态地为每个外设建立模型。
    • 截获固件-外设的交互,由于截获的固件-外设交互只包含地址(例如,将一个值读/写到一个地址),我们必须首先将目标地址转换为命名实体,这是很简单的,因为寄存器内存映射包含了所需信息。
    • 通过捕获规则触发器,我们检查任何一个C-A规则的条件是否被满足。然后,如果命名实体匹配到一个谓词并且条件是满足的,相应的动作将被执行。
  • 捕获规则触发器
    • 通过拦截MMIO访问直接捕获固件交互。(从固件交互捕获触发器)
    • 内部硬件触发器来自之前C-A规则执行的结果。支持C-A规则的链式执行,这是仿真外设行为的一个非常重要的方面。(从内部硬件捕获触发器)
    • 为了从外部硬件捕获触发器,观察到大多数硬件信号都与数据传输相关,这允许我们通过监视I/O接口(特别是发送和接收缓冲区)来模拟硬件信号。(从外部硬件捕获触发器)
      • 因此我们使用两个字节数组来模拟这个缓冲区。
        • 当固件写入一个字节到数据寄存器,我们将其移动到发送缓冲区。
        • 当固件从数据寄存器读取数据时,我们像实际硬件一样从接收缓冲区返回一个字节。
      • 也可以用软件模拟基于定时器的硬件信号。
      • 对于其余几个无法模拟的硬件信号,我们总是满足相应条件(比如字段始终设置为1,虽然可能影响仿真的保真度,但是有助于推动仿真的发展。),使得相关联的操作有公平的机会被调用。

诊断错误或缺失的C-A规则

  • 提取的C-A规则可能不完整或不正确。
    • 由于一些硬件信号无法模拟。我们设置相应条件始终为true,导致某些C-A规则的错误执行。
    • 最先进的NLP技术在处理非常复杂的句子时面临困难。
  • 我们提出一种基于无效状态检测的C-A规则检查器来自动诊断错误或缺失的C-A规则。
    • 受uEmu启发,我们发现符号执行非常善于推理仿真失败的根本原因。仿真失败通常导致无效的执行状态(例如,暂停或崩溃),符号执行可以帮助我们快速定位外设读取的不正确响应。
    • 具体来说,方案包括以下步骤:
      • 首先,对于目标外设,准备一个固件样本和一个有效的测试用例,该测试用例和固件一起正确执行。
      • 然后,在SEmu上运行这个样本,并收集由合成的外设模型生成的具体响应。
      • 接着,将这些响应提供给符号执行引擎,用于指导选择分支时的符号执行。如果检测到无效状态,那一定是由于错误的C-A规则。因为我们专门选择了不会触发任何无效状态的固件样本和测试用例。由于遇到了无效状态,因此来自外设的先前响应之一肯定是错误的。我们将取最后一个条件语句中的另一个分支,并求解相应的符号变量。这个符号变量告诉我们模型错误的寄存器的地址。我们进一步将我们的模型生成的错误值与符号执行引擎计算的值进行比较,以识别不正确的位。
      • 最后,我们的工具以相反的顺序列出了与该字段相关的所有已执行的C-A规则。人类现在必须参与进来,以确认错误的规则。诊断是一个反复的过程。在每一轮中,它都会修复一个C-A规则,直到可以正确地模拟使用的固件。
    • 以PMC外设的SR[MOSCXTS]的描述为例。NLP引擎起初未能为该字段生成任何C-A规则,因此我们的外设模型默认置为0。然而,在PMC初始化期间,诊断工具检测到由于读取SR寄存器的错误响应而导致的无效状态。诊断工具表明此时响应应该是0x1。检查手册发现,它没有明确说明该字段取决于硬件信号(即,描述中没有"hardware"关键字),也没有发现条件子句。我们通过添加一个静态规则来解决这个问题,即应该始终设置SR[MOSCXTS]:=1。

评估

实验准备

选择了5本芯片手册,包括 STM32F103, STM32F429,STM32L152, NXP K64F series and Atmel SAM3X series,它们分别属于三家MCU供应商。
值得注意的是,一本芯片手册可以涵盖共享相似外设的一系列MCU芯片。
所有实验运行在16-core Intel Xeon Silver 4110 CPU@2.10GHz服务器的Ubuntu18.04系统,该服务器配有48G DRAM。

C-A规则提取

  • 对于每本手册,我们提取了26种常见外设的C-A规则。
    在这里插入图片描述
    由表5可得,超过一半的conditions是由固件生成的信号C2触发。84.2%的C-A规则通过A1动作更新寄存器字段的值。
  • 使用原始的C-A规则进行固件评估
    • 我们使用原始的C-A规则为P2IM论文中发布的66个单元测试样本构建外设模型。这些单元测试在三个芯片上运行,涵盖了各种外设和操作系统库。
    • 对于这66个测试用例,P2IM和uEmu分别达到了83%和95%的通过率。不幸的是,在固件引导期间,我们的仿真器无法正确运行这些单元测试的时钟配置。具体来说,F103的RCC(下一节介绍如何纠正不正确的规则)、K64的MCG、SAM3X的PMC的原始C-A规则不能为这些外设构建可用的模型。在修改时钟相关规则后,我们的方法达到了96.97%的通过率。

C-A规则增强(修订)

  • 如前所示,当使用原始C-A规则来合成外设模型时,我们遇到了仿真失败的情况。这是由于NLP技术的局限性以及不可避免的错误硬件描述。
  • 虽然不能从根本上解决这个问题,但已经开发了一种符号执行辅助的诊断工具,可以快速找出哪个规则是不正确的,然后手动修订规则。
    • 我们准备61个固件样本,然后使用SEmu运行样本,并使用收集到的具体响应来指导符号执行,如果符号执行引擎进入无效状态,它将终止并输出包括相关寄存器和C-A规则的诊断信息。注意,当检测到无效状态时,这一定是由于错误的C-A规则,因为我们已经确保测试的样本和测试用例不会在真实设备上崩溃。如前表最后一列所示,总共添加了26条规则并修订了21个规则。
  • 举个栗子(我们使用F103的时钟外设RCC来解释如何在诊断工具的帮助下纠正不正确的规则)
    • 芯片手册中描述RCC的CFGR[SWS]字段的原句是"set and cleared by hardware to indicate which clock source is used as system clock.“,翻译为"由硬件设置和清除,以指示哪个时钟源用作系统时钟。”
    • 由于NLP引擎不知道硬件将如何设置/清除该字段,我们的工具生成了一个C-A规则"O * ->CFGR[SWS]:=0/1/2/3",这表明1这是O触发器,2条件总是被满足,3动作是用0-3的随机值设置CFGR[SWS]。当我们对固件样本使用这个规则时,我们发现RCC驱动程序总是引导失败。使用诊断工具,我们能够捕获无效状态并精确定位该字段。特别是,由SEmu生成的CFGR[SWS]的具体响应导致符号执行进入无限循环。追溯起来,检测到CFGR[SWS]对应的符号值。然后,我们阅读了手册中描述CFGR[SWS]的相关句子,发现CFGR[SWS]应该遵循CFGR[SW]中的值(即相等),此外,3是一个不可能的值。因此,我们将此规则更新如下:
      在这里插入图片描述
      条件是CFGR[SW]的所有更新操作,动作是将相同的值分配给CFGR[SWS]。
  • 使用增强的C-A规则进行固件评估
    • 使用增强的规则,特别是与时钟相关的规则,我们重新测试了来自P2IM的66个单元测试样本。SEmu的通过率达到100%。
    • 我们还收集了17个新的样本,它们代表了现实世界的应用程序,具有多个外设和不同的工作模式(如DMA)。例如,运行在智能锁上的PinLock固件,它通过UART接口读取PIN,并求取hash且将其与已知hash进行比较,并在PIN正确时发送信号来解锁智能锁。SEmu成功的仿真了这些固件样本,P2IM和uEmu都不能运行这些样本。

C-A规则对芯片手册的忠实程度

  • 我们使用QEMU开发人员已经实现的工作外设模型,这些后端模型使用C语言编写并且仅实现了正确仿真固件所需的基本外设。使用外设模型的C语言源代码,我们手动检查程序逻辑,并将它们转换为C-A规则。然后,我们将SEmu生成的C-A规则与QEMU生成的C-A规则进行比较。
    在这里插入图片描述
  • 按照上述方法,使用外设模型的C语言源代码收集了所有QEMU支持的外设的C-A规则,并与SEmu对比如下。
    在这里插入图片描述
    与我们的模型相比,QEMU模型中有很多缺失的规则。主要原因是QEMU无法实现许多非标准的外围功能。为了“支持”外设,QEMU只需要为这个外设的最常见逻辑实现模拟。因此,我们经常在QEMU的源代码中看到类似于"[**] is not implemented, the registers are included for compability"这样的句子。
    我们承认,SEmu提取的许多规则对应于很少使用的外设功能,并且在评估中从未执行过。然而,我们确实发现QEMU忽略了一些关键的外设逻辑。

仿真保真度测试

  • 我们使用来自P2IM的单元测试样本来评估仿真保真度,并将SEmu与uEmu和P2IM上的仿真保真度进行了比较,因为这些样本大部分都得到了所有相关工作的支持。
  • 为了获得参考数据,我们利用外部调试探针(例如ST-Link和OpenSDA)来收集真实硬件上的轨迹。

在真实设备和仿真器上收集轨迹

  • 为了在真实设备上收集轨迹,我们使用外部调试探针OpenOCD和一个外部调试加密狗将目标板连接到芯片供应商提供的远程gdbserver。使用调试器,我们记录每条指令执行的程序计数器。
  • 收集仿真器的执行轨迹要容易得多。我们直接记录了每个要执行的翻译块的起始地址。然后,我们将其与分解的固件代码对齐,以按照与实际设备上相同的格式构建执行轨迹。

量化两个执行轨迹之间的相似性

  • 通过将执行轨迹表示为地址序列,可以采用传统字符串距离算法(比如编辑距离),计算相似度。然而,由于固件执行的不确定性,直接使用编辑距离不能可靠测量真实相似度。例如,对于相同的输入和固件,在在真实设备上的两次执行可能会产生不同的轨迹,但它们都能达到100%的保真度。(因为都是真实设备产生的)
  • 为了解决这个问题,我们将每个轨迹基于功能划分为三个部分。初始化轨迹,包括初始化功能,如外设配置。主循环轨迹,包括主要的固件业务功能。中断轨迹,记录在中断上下文中执行的指令。对于轨迹的每个部分,我们使用编辑距离来衡量相似度。编辑距离被用来量化两个字符串的不同程度,具体来说,是将一个字符串转换到另一个字符串所需的最小操作(即删除、插入和替换)。
  • 整个轨迹以地址序列的形式呈现,这些地址序列对应于每个基本块的开始。与真实设备轨迹相比,删除操作意味着仿真器错误地多取了一个基本块,插入操作意味着仿真器错过了一个基本的块执行,替换操作意味着仿真器执行不同的基本块。删除或插入重复序列被认为不那么重要。例如,固件通常以轮询模式运行,等待状态寄存器的某个值,它运行100个循环还是1000个循环不会有效地影响固件分析。因此,我们将重复删除或插入操作的权重分配为1,而将其他操作的权重分配为2。
  • 仿真器和真实设备之间的轨迹距离由在这里插入图片描述决定。保真度分数可以由此计算在这里插入图片描述DQEMU代表最坏仿真结果,因为没有提供外设仿真。
  • 在提出的仿真度度量机制下,SEmu是测试样本中唯一具有完美分数的。最显著的差异在中断轨迹,P2IM和μEmu的性能都非常差。经过手动分析,我们发现P2IM将许多控制或状态寄存器误归类为数据寄存器,从而触发了意外的仿真结果。μEmu由于其不健全的无效状态试探法而遭受低保真度问题。例如,我们发现它经常调用不可行路径,而这些路径实际上并不会导致挂起或崩溃。最后,P2IM和μEmu都不知道中断时序。因此,中断处理程序通常采用不相关的路径。
    在这里插入图片描述

模糊测试

测试环境

  • 测试的固件样本
    • P2IM中使用的10个样本
    • Pretender中使用的2个复杂样本
    • 本文新添加的4个新样本,它们使用以太网上的LwIP作为网络层协议,通过TCP和UDP执行网络通信。基于STM32F429芯片开发。
  • 集成改进版的AFL作为模糊测试引擎。
    • 由于模糊测试的随机性,我们对每一个测试的固件样本进行了5次重复,每次进行24小时的模糊测试。
    • 使用相同的随机值作为每个测试目标(P2IM,uEmu,SEmu)的初始种子。

测试结果

在这里插入图片描述

  • 代码覆盖率比较
    • 对于可以测试的固件,SEmu的代码覆盖率分别平均比P2IM和uEmu高5.1%和6.7%。
    • 覆盖率的提高似乎微不足道。然而,我们注意到SEmu实现了更好的路径保真度,因此没有探索许多错误处理函数。例如,像I2C这样的外设使用两个独立的中断来处理正常和错误信号,错误中断仅在硬件故障时发生。然而,P2IM和uEmu仍然会周期性地触发这些错误中断。
  • 崩溃/挂起比较
    • 为了验证结果,对于每个报告的崩溃/挂起,我们向相应的仿真器重放触发测试用例,并收集执行轨迹。如果收集的执行轨迹有两个相同,我们认为它是重复的。如果重放阶段没有观察到崩溃/挂起报告,则认为是误报。
    • SEmu成功重现了之前工作中提到的所有bugs,而且没有报告任何错误的崩溃/挂起。事实上,现有工作中提出的许多崩溃报告是重复的或误报的。原因有二,一是现有工作随机传递中断,这导致遭遇相同崩溃的两个执行的边缘覆盖显著变化,AFL错误以为这是两个不同的崩溃。二是由于P2IM和μEmu中不准确的仿真,有许多不可行的路径被探索,导致相当多的假阳性发生。

合规性检查测试

  • 在合规性检查中,我们检查外设驱动程序的实现是否符合芯片手册中的描述。具体来说,我们收集外设访问历史并且将其与NLP学习到的预期访问约束相匹配。
  • 在我们的原型系统中,我们实现了动态合规性检查模块,用于检查两条规则。
    • R1:外设状态验证
      • 固件应该在检查另一寄存器状态后才访问某些寄存器。这个简单的规则适用于许多I/O操作。例如,固件首先通过轮询状态寄存器中的状态来检查硬件是否就绪。只有当返回一个特定值时,它才会继续访问数据寄存器。如果固件直接访问数据寄存器,则违反R1。
    • R2:中断激活一致性
      • 为了启用中断,固件不仅需要激活到外设的本地配置,而且还需要通过在全局中断管理器(即NVIC)中设置中断集启用寄存器(ISERx)来启用相应的中断源。
      • 违规发生在以下情况
        • R2(A):固件在NVIC中启用中断,但不在本地外设控制器中启用中断。
        • R2(B):固件启用本地外设控制器,NVIC不启用中断。
  • 实验结果
    在这里插入图片描述
    R1违背:我们在SAM3X HAL驱动程序代码中观察到几处违反R1的情况。我们后来确认了根本原因是外设访问的竞争。例如SAM3X MCU的UART驱动代码在数据传输前检查TXRDY字段。但是,它还允许在TXRDY验证和数据传输之间发生UART中断,在此期间中断处理程序进行独立的数据传输。当中断返回时,之前的TXRDY验证无效,接下来的数据传输将失败。
    R2违背:芯片供应商通常为开发人员提供一个HAL库,它抽象了硬件细节,并提供了一个统一的API来访问外设。但是,并不是所有的HAL都支持NVIC寄存器配置。这是因为IRQ号分配是特定于芯片的,配置NVIC成了开发者的责任。这个假设导致了R2的违背。特别是,开发人员可能会忘记配置NVIC。例如,我们发现Robot固件调用HAL函数HAL_TIM_Base_Start_IT()来设置TIM2_DIER寄存器以启用Timer2中断。但是,它不启用NVIC寄存器中相应的中断号(28)。因此,中断永远不能被传递。

局限性和讨论

  • NLP局限性
    • 最先进的NLP工具在处理不同句子之间的引用和具有嵌套的复杂句子时存在局限性。这些局限会导致错误的规则生成。然而,SEmu较少受到这些限制。因为,
      • actions通常显式表示为执行几个简单的寄存器赋值。
      • 命名实体是芯片手册中的正式表达,缓解了共引用问题。(可以使用近似字符串匹配来统一命名实体)
      • 复杂conditions通常依赖于具有隐式语义的某些硬件信号,可以通过默认行为来建模。(例如,F103 ADC示例中默认赋值1)
  • 手工工作
    • 对于每个固件,开发人员需要识别底层的MCU芯片,并为仿真器配置内存映射信息(例如,闪存、RAM、MMIO的范围)。
    • 开发人员需要手动复制PDF格式芯片手册中的所需章节,并将其输入NLP引擎。比如寄存器内存映射章节。
    • 诊断错误的C-A规则工作量巨大。首先,需要准备测试固件和测试用例。然后,当检测到无效状态时,开发人员需要阅读和理解规范,以对错误的C-A规则进行微调。后者是不可避免的。

相关工作

固件仿真

  • 依赖真实硬件进行动态分析会产生许多问题,例如性能差,可伸缩性差。
  • 仿真是解决这些问题的有效方法。仿真固件的关键挑战是如何正确地为外围设备地行为建模,以便仿真的执行可以与在真是硬件上的执行相似。
  • 高级仿真解决方案通过连接到高级库(例如,硬件抽象层HAL)并在本机上实现等效逻辑来避免仿真与外围设备相关的代码。高级仿真像SEmu一样实现了良好的保真度。然而,这些方法完全跳过固件中的外设逻辑,因此无法发现外设驱动程序的问题。此外,开发人员为了性能考虑并不总是使用高级抽象库。
  • 固件引导的解决方案在仿真器中运行整个目标固件。基于策略,它们可以进一步分为三种类型:基于访问模式、基于符号执行和基于学习。通过观察外设访问模式,P2IM推断寄存器类型(即CR、SR、C&SR和DR),然后使用启发式方法根据寄存器类型信息生成响应。然而,如前所述,它存在寄存器错误分类问题。此外,当启发式不可用时,P2IM在有限的搜索空间内盲目地搜索合适的响应。基于符号执行的解决方案通过推理来自外围设备的响应如何影响固件执行来解决上述问题。这些解决方案的一个关键限制是它们依赖于启发式来决定要采取的路径。PRETENDER和Conware通过学习硬件和固件之间的真实交互来创建外围模型。因此,它们需要依赖于硬件的记录阶段,降低了可伸缩性。
  • 与固件引导的解决方案相比,我们的方法是由外围规范引导的。它实现了更高的仿真保真度,而不需要真正的硬件。

基于NLP技术的规则提取

  • 从规范中提取规则以解决安全问题并不新鲜。
    • SmartAuth从物联网应用描述中学习策略相关性(实体、上下文和动作、条件),验证物联网应用的代码实现是否符合策略。
    • ARE通过使用应用层数据和产品描述生成(设备检测)规则,自动发现IoT设备。
    • iRuler使用NLP技术推断触发动作信息流,并发现物联网部署中的规则间漏洞。
    • Bookworm Game使用NLP技术从电信中的LTE(长期演进)文档中识别风险操作。然后它提取信息来构造能够满足触发风险操作的条件的测试用例。
  • 我们的工作为安全分析驱动的固件仿真自动构建外围模型。与现有工作相比,这个特定问题有以下挑战:
    • 首先,C-A规则可以以不同的方式触发。我们必须对提取的规则进行分类,以便只在需要时检查它们。
    • 其次,要支持固件仿真,必须将提取的规则的语义形式化为具体的值和布尔运算符,以便能够以编程方式维护外设状态。
    • 最后,为了处理硬件生成的信号和中断相关的操作,我们必须将特定于MCU的领域知识合并到NLP中。最重要的是,现有工作不需要全面的提取相关规则,但是SEmu必须确保提取的规则足够完整。如果SEmu不能实现相关C-A规则(包括链式规则)的充分完整性,固件模拟很可能会失败。这一关键差异激发了SEmu独特的规则诊断机制。

结论

在这项工作中,我们提出了第一个基于规范的固件模拟解决方案,而不是提出另一个固件引导的仿真解决方案。这种新方法利用NLP技术将人类语言描述的外设行为(记录在芯片手册)转换为一组结构化的条件-动作规则。通过在运行时正确地执行和链接这些规则,我们可以为固件执行期间访问的每个外设动态地合成一个外设模型。在机器辅助规则诊断的帮助下,我们的评估证实,在提出的保真度度量方法下,我们的原型与真实设备相比达到了100%的仿真保真度。相比之下,现有工作所达到的保真度在73%到86%之间。有了更好的保真度,我们的解决方案提高了模糊测试的效率。在模糊测试期间没有观察到虚假的崩溃和挂起。通过更高的仿真精度,我们还设计了一个新的动态分析任务来根据规范执行驱动程序代码符合性检查。我们发现了一些不符合,我们后来确认是由竞争条件引起的错误。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/435173
推荐阅读
相关标签
  

闽ICP备14008679号