SFuzz: Slice
- SFuzz: Slice 推荐度:
- 相关推荐
SFuzz: Slice
原文地址:SFuzz | Proceedings of the 2022 ACM SIGSAC Conference on Computer and Communications Security
源码地址:NSSL-SJTU/SFuzz (github.com)
发表于2022年CSS
Abstract
RTOS的整体设计将各种任务和服务组合成一个二进制文件,这阻碍了当前的程序测试和分析技术在RTOS上的工作。
本文提出了一种新的基于切片的模糊器SFuzz来检测实时操作系统中的安全漏洞。我们的见解是,RTOS通常将一个复杂的二进制文件分为许多独立但单一的任务。每个任务以确定的方式完成一个特定的事件,其控制流通常是直接和独立的。因此,我们从整体RTOS二进制文件中识别出这样的代码,并合成一个切片以进行有效的测试。具体来说,SFuzz首先确定处理用户输入的函数,构造从这些函数的调用者开始的调用图,并利用前向切片来基于调用图构建执行树,并修剪独立于外部输入的路径。然后,它在粗粒度范围内检测并处理阻碍有效模糊化的障碍,例如与用户输入无关的指令。然后,它对这些代码段进行覆盖引导的模糊分析。最后,SFuzz利用向前和向后切片来跟踪和验证每个路径约束,并确定在fuzzer中发现的错误是否是真正的漏洞。
Motivation
许多研究者提出了各种嵌入式设备上的bug检测机制【7,9,21,31,44】,但能够直接应用在RTOS的却很少,主要原因是RTOS通常以blob-firmware格式呈现,却很少在单个单片机执行的微控制器和CPU上运行。
对于静态分析【9,31】,考虑到整体RTOS二进制文件的大容量,典型的静态方法(例如符号执行【6,1,34】)遭受路径爆炸问题,此外由于缺乏明确的函数符号和RTOSes的复杂性,很难在二进制级别上揭示函数的语义,因此,我们无法轻易是被与敏感数据相关的模块,也无法对这些模块进行分析。
动态解决方案【7,21,37,44】,如fuzzing,要么需要实际设备,要么依赖正确稳定的仿真来测试目标固件和基本服务,由于来自不同供应商的RTOS采用具有不同接口的各种外设,因此模拟所有现实场景具有挑战性。
一些检测RTOS错误的定制工具,要么只能在特定设备上工作,依赖于真实设备,要么只能检测有限的错误类型。还有一些需要人工分析先验的知识,因此很难应用于不同的RTOS系统。均受到了可伸缩性的限制。总体而言,缺乏一种灵活而通用的方法来有效地发现RTOS中的漏洞。
Insight
特定的RTOS特性提供了绕过测试障碍的独特机会,例如多任务机制。具体来说,RTOS通常将复杂的应用程序划分为许多独立但单一的任务。每个任务都以确定的方式完成一个特定的事件。每个原子任务的控制流通常是直接且独立的。更重要的是,如果这些任务属于同一类别,它们的数据流可能具有相似的模式。 因此,我们搜索从不同的外部数据条目开始到潜在sink函数(例如,memcpy)的数据流,并在RTOS的任务中切片相应的代码片段。这些切片足够小,可以使用现有的模糊逻辑进行测试。此外,它们呈现出更小但更关键的控制流范围。它可以大大降低仿真难度和分析复杂性,这将允许我们执行更有效和高效的测试,如灰盒模糊和符号执行。
提出的方法:
一个新颖的模糊测试方法,利用前向切片构建定制的代码空间,在模糊器上驱动灰盒模糊,然后,SFuzz合并了后向切片来执行concolic执行,以验证来自fuzzing的崩溃输入。
四个组成部分:
Forward Slicer:首先确定处理用户输入的函数。由于RTOS二进制文件没有函数名,我们定义了一组启发式方法来定位这样的函数,之后,构造一个调用图,从这一个函数的调用者开始,通过粗粒度传播,通过修剪独立于外部输入的路径,在这个调用图中执行正向切片。还修改了与输入无关的分支条件,以确保控制流达到潜在的sink函数。由于覆盖了直接物理内存访问的数据共享范式(在RTOS中经常使用),我们的前向切片器可以灵活地跨不同的任务进行扩展,并在执行中动态地缝合路径。
Control Flow Node Handler:如果直接执行代码片段,模糊引擎将缺乏RTOS的完整上下文和运行时状态。因此,控制流节点处理器用于指导模糊器确定如何处理与用户输入无关的函数调用和条件分支,帮助模糊引擎提高路径探索的效率和稳定性。
Micro Fuzzing:我们的模糊引擎关注修剪后的执行树中的指令,它从输入源开始,通过指令级模拟执行上下文。引擎将执行与输入相关的代码片段并忽略大量不必要的路径,包括其他输入处理程序。为了检查危险行为,它监视sink函数调用的上下文,并在上下文违反预定义的安全策略时报告潜在的错误。
Concolic Analyzer: 为了在修剪后的调用图中获取缺失的上下文信息,我们恢复修改后的条件分支,并将被忽略函数的上下文符号化。然后,我们基于向前切片执行concolic执行,并使用bugtriggering输入来提供具体的值。执行向后切片,从sink函数开始到写入的对象分配位置和其他输入引用位置。然后,我们从向后切片的结尾开始进行符号执行,得到对象大小的约束,并迭代其他输入的每个谓词,检查相应的条件是否必要。最后,我们可以实现一个完整而准确的路径条件来评估一个漏洞。
Contribution
我们提出了一种基于切片的RTOS测试方法,该方法利用前向切片修剪控制流以进行高效的模糊测试,并结合后向切片验证来自模糊的警报。
我们设计并实现了SFuzz,它通过跨平台CPU仿真来执行基于片的模糊检测,从而有效地检测RTOS固件中的漏洞。
我们在来自11个供应商的35个真实的RTOS固件样本上评估了SFuzz,发现了77个未知的错误。68个bug被分配了CVE/CNVD IDs。
Motivation Example
清单1显示,在第40行包含了缓冲区溢出漏洞,CVE-2020-28877。这段代码功能是,在第7行,使用recvfrom()函数从外部接受数据,接着第9行进入函数protocol_handler()来检查数据包的头部的数据大小(第15行),匹配magic字节(16行),并检查整个数据包的完整性(17行),如果都满足所有的约束,则执行18行,进入msg_handler()函数,根据数据包的版本(23行)调用处理程序(24行)parse_advertisement()解析并提取结构元素的头部(35-36行),并根据元素头部的长度len,复制元素数据到内存空间,如果len大于存储数据的缓冲区长度,将触发堆栈溢出漏洞。
Challenges
(1)如何确定片段的范围?首先,无论目标RTOS是否包含符号文件,我们都需要识别数据读入函数的方法。其次,我们需要一种方法来确定与数据接收点对应的片段的范围。通过函数调用构造函数集将包括一些与输入数据的处理逻辑无关的路径和函数。同时,一些不能接触sink函数的路径也会被包含在作用域中。这两者都会影响基于切模糊的效率。
(2)如何处理代码片段中与控制流相关的点?一些函数调用和条件分支会影响执行路径的可达性和模糊的效率。
例如,模拟器无法模拟的一些函数将影响路径可达性。另一个例子,一些比较指令将使用全局变量或与我们正在突变的输入无关的变量。因为我们不能通过种子突变来改变这些变量的值,所以我们不能控制这些比较指令的下面分支的跳转方向。
(3)如何有效地进行基于切片的模糊和验证PoC?一方面,纯模糊技术在没有应用程序或输入格式的先验知识的情况下,很难生成有效的种子来通过各种条件保护[30,35]。类似于Driller[35],我们将模糊和符号执行结合起来,使路径探索更有效。另一方面,由于我们对代码片段进行模糊处理,并对一些与控制流相关的指令进行预处理,因此我们需要设计一种方法来判断导致崩溃的概念证明(PoC)是否是原始RTO中真正的漏洞。
SFuzz的必要性和合理性
通过以上分析对比,动态分析方法比静态分析方法更适用于RTOS bug的检测。然而,在没有大量人工分析和健壮的全系统仿真方法的情况下,如何通过动态方法有效地检测各种嵌入式设备实时操作系统中的这些漏洞呢?
假设收集到一个包含接收和处理相应数据包的完整函数集的程序片段,我们可以通过混合模糊和指令级别的仿真有效合理地检测代码片段中的漏洞。
在清单1中的,在确定数据接收函数(第7行)和与数据包处理相关的所有函数(例如,第40行中的复制)之后,我们可以构造代码片段并生成输入,通过覆盖引导的混合模糊来触发堆栈溢出漏洞。由于在执行代码片段时缺乏完整的上下文,我们需要处理与外部包无关的控制流节点,以使模糊有效和稳定。一旦发现漏洞,我们根据触发漏洞的输入进行concolic 分析,并获得最终结果,包括与我们处理的控制节点相关的完整约束, 来帮助我们确定漏洞是否是假阳性。通过静态分析,我们的方法的探索范围仅限于有风险的代码片段。同时,我们只使用符号执行来帮助我们在模糊被卡住时创建新的测试用例,并验证崩溃结果。这使得我们的方法与传统的静态分析和符号执行方法相比,具有更好的性能,并能缓解路径爆炸问题。
证明合理性,选择包含符号文件或日志函数的四种设备。我们搜索系统中所有类型的数据读入点,例如recvfrom()函数,并将它们的调用函数(例如devDiscoverHandle)作为数据处理树和特定任务模块的根点。然后递归搜索存在于根点函数及其子函数中的函数调用,形成函数集。以根点函数名称命名函数集。理论上,这些函数集应该对应不同的数据读入点、数据处理任务和功能。只有这样才能证明我们基于切片的模糊方法是合理的。不同函数集之间的函数交集越少,各函数集的函数独立性越高。
提供 CDF图(累积分布函数图)来简化不同功能片段之间的函数交集分布的表示,图1.很少有函数(少于25%)属于一个以上的集合。根据我们的分析,交集主要来自两个方面。一个是标准库的函数(例如recvfrom、memcpy等),不同的功能将使用它们。二是不同函数集之间的相似性。尽管这些函数集具有不同的数据入口点,但它们处理类似的数据包,例如bindRequestHandle和registerRequestHandle。
75%的函数只属于一个功能片段,只有不到5%的函数由六个以上的功能片段共享。全局变量的分布,即它们所使用的功能片段的数量。这些功能片段中87%的全局变量只在一个功能片段中使用。这些结果表明,大多数功能片段是相互独立的。上述分析表明,从RTOS中采集的这些功能集满足了控制流和数据流功能独立性的特点。实验结果验证了基于切片模糊化方法的合理性。
Design
图 2
SFuzz将RTOS设备的固件作为输入并输出他们的错误报告。首先恢复RTOS中函数的语义,并使用正向切片器和控制流节点处理程序提取与外部输入相关的代码片段。然后,它使用基于切片的模糊技术来探索代码片段的执行树。最后构建基于concolic分析器的PoC。
其中,前向切片器将调用图分析与前向污染分析相结合,确定基于切片模糊的每个任务的探索空间;控制流节点处理器用于帮助下面的模糊处理部分跳过不必要的路径探索和那些会使模糊阶段卡住的节点;微模糊引擎是一种混合灰盒模糊器,它结合了一些low-level技术,如错误检测策略,使模糊器能够顺利运行和发现错误;Concolic Analyzer主要是帮助我们过滤由于探索剪枝和上下文缺失造成的假阳性。
3.1 Forward Slicer
为了对目标RTOS的函数片段进行基于切片的模糊化,我们首先恢复固件中关键函数的语义(详见§4),以定位外部数据入口点(如recvfrom)、全局数据共享函数(如nvram_get)和脆弱函数(如memcpy),然后利用正向切片器模块输出与处理外部输入和全局数据相关的执行树。
Forward Slicer包含三个部分,其工作流程如图2所示。Sensitive Call Graph Constructor检测输入获取函数和全局数据的读入点,然后将这些函数的调用者作为根节点来构建调用图。为了使模糊测试集中在脆弱路径上,修剪那些在图中,不能触发潜在sinks函数的分支。Call Graph pruning组件进一步修剪独立于外部输入的子图或者路径。最后Call Graph Stitching组件在不同调用图的节点之间拼接一些边。在构建这些调用图时,由于缺乏直接相关性,这些边会丢失。
Call Graph Pruning:为了判断外部输入或全局数据是否影响潜在的sinks的函数的参数,SFuzz利用轻量级(粗粒度)污染分析技术跟踪调用图中的每条路径(从根节点到叶节点),确定外部输入和全局变量影响的可能范围,并过滤独立于它们的路径。对于每个调用路径,污染引擎将进入路径中每个节点的函数体。它根据函数的语义将input reception和解析函数的参数或返回值标记为污点源,例如清单1中recvfrom的参数Global_addr+0x1c,它指向用于存储输入数据的内存空间。对于每条指令进行污点分析时,污点引擎首先将指令转化为中间指令,然后,对于每条中间指令,如果输入操作数收到外部输入的影响,它将该指令的输出操作数包含到受污染的操作数集中。对于被调用方不属于调用路径的函数调用指令,污点引擎将把污染属性从其受污染的参数传播到返回值(For function call instruction whose callee does not belong to the call path, the taint engine will propagate the taint attribute from its tainted parameters to the return value.)。如果sink函数的危险参数,例如,函数memcpy的count (*dest, *src, count))受到输入的影响,SFuzz保留相应的调用路径。
Call Graph Stitching:Karonte和SaTc提出,外部输入的一些数据流可以通过数据共享范式中断。不幸的是,RTOS也面临着同样的挑战。与之前的方法不同,除了使用静态分析来拼接确定性相关节点外,我们还使用动态技术来检测数据集与使用点之间的非确定性相关性。对于标记为常量字符串的数据共享范式(即set和use point),我们基于常量字符串进行搜索和匹配。然后连接两个调用路径,并使用一个虚拟节点(即二元组,例如<nvram_set,nvram_get>)来表示合并调用图中的范式。对于由动态创建的变量标记的范例,例如清单2中的“wan%d_pppoe_username”,我们基于近似字符串匹配方法获得这些范例,并创建一个虚拟条件节点来连接潜在的数据共享范例。然后在模拟执行期间根据变量的实际值确定是否跳转到全局数据读点。对于有多个对应get点的设定点,我们还建立了一个虚拟条件节点,并基于随机概率确定跳跃方向。
3.2 Control Flow Nodes Handler
在处理Forward Slicer模块之后,我们可以基于调用图构建目标代码段的执行树。但是,为了使模糊测试在执行树上顺利进行,避免不必要的路径探索,我们仍然需要处理与控制流相关的几种指令(即(i)函数调用,(ii)条件分支),这将影响执行路径的可达性和测试的效率。换句话说,由于缺乏RTOS的完整上下文和运行时状态,我们需要策略来指导fuzzer确定如何处理代码段中的函数调用,并选择跳转条件语句的哪个分支。
Call Instruction:将被调用方参数不受外部输入影响的函数调用指令地址加入到PatchedFunc集合中,引导fuzzer跳过函数调用;我们这样做主要是因为它的参数与输入无关,并且它的返回值和参数不会通过改变种子输入而改变。因此,对这类函数进行patch可以帮助模糊器忽略其复杂性,提高模糊效率。对于属于敏感调用图或其参数受输入影响的所有函数调用,例如清单1中的protocol_handler和header_check,我们保留它们。只有进入这些函数,才能保证敏感路径的可达性。
Conditional Branch:条件语句仅在满足指定的分支约束时才将控制流定向到目标地址。然而,在对代码段进行模糊处理时,如果条件跳转与输入数据没有关系,我们就必须面对确定条件跳转方向的问题,这意味着我们不能通过改变输入来改变跳转方向。为了使模糊测试更加有效和合理,我们提出了几种处理各种条件分支的方法。
(1)假设只有一个条件跳转指令的分支可以到达sink函数。如果它的条件受到输入的影响,我们将不可达分支的目标地址插入到PatchedJMP集,这将指导模糊器避免探索这个分支。否则(意味着输入不可控),我们将跳转指令的地址添加到PatchedJMP集中,引导fuzzer将条件跳转替换为可达分支的固定跳转。
(2)假设条件语句的两个分支都可以到达sink函数。如果它的约束与输入数据无关,我们将该指令的地址添加到PatchedJMP集中,这将引导fuzzer用随机跳转语句替换该指令。否则,我们不更改代码。它主要是帮助我们探索尽可能多的不是由输入决定的路径。
(3)如果没有条件跳转指令的分支可以到达sink函数,我们将这两个分支的目标地址添加到PatchedJMP集合中,当遇到这些地址时,它将引导fuzzer退出当前路径探索。
3.3 Micro Fuzzing
作为模糊引擎的核心,我们将基于切片的模糊技术命名为Micro Fuzzing。它接受代码片段作为输入,在执行树中探索路径,并忽略不相关的调用站点和其他输入数据处理程序。该引擎同时检查sink函数调用站点的上下文,并根据预定义的策略访问内存时导出崩溃输入。
Image Loader:加载RTOS固件后,图像加载器将预处理由前一个模块标记的定制代码片段。对于应该被跳过的调用指令(在PatchedFuncset中),图像加载器将调用指令替换为类似nop的指令。对于应该避免被探索的分支(在PatchedJMPset中),SFuzz将AvoidExplore语句添加到其目标地址。当程序执行到相应的地址时,程序将直接退出当前路径探索。对于应该用固定跳转或随机跳转替换的分支,SFuzz使用相应的操作来处理它。
Fuzzing Engine:当核心引擎被调用时,它加载RTOS系统,并从起始部分(执行树的根节点)反复执行目标代码段。该引擎将在输入入口点生成随机数据。基于UnicornAFL,该引擎可以在定制的执行树上执行覆盖引导的模糊和模拟指令执行。当核心模糊引擎卡住时,混合模糊器调用其concolic执行组件。该组件从种子队列中随机选择一个输入,并用符号表示输入中的每个字节。在跟踪与输入对应的执行路径之后,concolic执行组件利用其约束求解引擎来识别将强制执行到以前未探索过的路径的输入。如果在阈值时间内没有找到新路径,模糊引擎将退出。
通过修剪不必要的路径,Micro Fuzzing忽略输入相关的函数调用(在PatchedFuncset中),而是执行类似nop的指令。另一方面,它直接跳过仿真硬指令,避免标记条件分支(在PatchedJMPset中),并在指针引用未初始化的内存时分配具体的数据。仿真硬指令通常与与硬件或HAL(硬件抽象层)模块交互的中断相关(例如,向CPU调度器发送信号),并且不影响从输入源开始的数据流。因此,在模拟中跳过这些指令会带来更少的副作用。最后,这两种方法使模拟更加稳定,并侧重于研究处理目标输入数据的代码片段。
Memory Safety Policies:由于成本敏感性和资源限制,bare-metal和RTOS设备通常缺乏内存sanitizer机制。因此,SFuzz需要为fuzzing引擎提供轻量级的内存安全检查策略,这些策略定义了sink函数调用点的内存访问违规。在这里,我们将重点检测在内存缓冲区操作中发生的错误。我们将内存缓冲区分为两类:可以静态分析以确定大小的缓冲区(包括堆栈上的缓冲区、通过malloc类函数创建的缓冲区、可以根据相邻变量识别其大小的全局变量等),以及不能静态确定其大小的缓冲区。对于大小可以静态识别的缓冲区(SFuzz在正向切片器模块中进行分析),我们通过检测执行sink函数后缓冲区边界数据是否被修改来确定是否发生溢出。对于无法确定大小的缓冲区,我们直接输出告警,并在后续的concolic analyzer模块中进一步识别缓冲区大小。
3.4 Concolic Analyzer
当Micro Fuzzing引擎退出时候,Concolic Analyzer将检查所有触发违规的崩溃输入。将这些cases作为具体的输入,在相应的执行路径上进行concolic执行,进行约束求解。
由于修剪后的函数调用指令(在PatchedFuncset中)和条件分支(在PatchedJMPset中)可能会误导原始执行树中的控制流,因此我们需要检查一个崩溃输入是否会触发原始RTOS中真正的漏洞。为了进行检查,Concolic Analyzer首先恢复这些分支,并在这些修补过的函数调用点中符号化参数和返回值,然后进行基于正向和反向切片的Concolic测试。
我们使用一个真实的样本(CVE-2021-32186)来展示conccolic分析仪的工作原理。正如清单3所示,nvram_set和nvram_get构建成一个数据共享范式。当另一个标记为“LEDStatus”的数据被设置为“2”时,标记为“LEDCloseTime”的数据源被传递给NVRAM(非易失性随机存取存储器),然后在vulnGet中触发堆栈缓冲区溢出。
Forward slicing-based concolic testing:在获取崩溃输入后,Micro Fuzzing还输出相应的执行路径,包括输入入口点和sink函数调用点。如清单3所示,入口点在第8行,由于这个切片是由数据共享范式拼接的,所以当前函数vulnSet函数的sink点事nvram_set(第17行)。因此,我们从第8行开始执行concolic测试,并在第17行中检查执行路径条件。在这个过程中,符号执行引擎收集路径约束,包括参数的符号表达式和第9行、第10行和第14行中其他输入数据读取函数的返回值。最后,路径条件(第17行)包含了所有这些输入数据,因为它们分别构成了第11、13和16行的分支条件。
Backward slicing-based condition verifier:虽然执行路径约束可以给出每个输入数据都应满足的特定约束集,但它仍然缺乏两方面的关键信息:一是对其他输入数据的约束是否必要,这会在错误检测中带来假阴性;第二,对象的大小约束由sink函数写入,这会带来假阳性和假阴性。因此,我们从两个方面解决问题:
(1)如清单3中的vulnSet所示,向后切片从第17行中的sink函数开始,并向后跟踪执行路径。通过检查sink函数中的执行路径条件,我们提取包含其他输入数据的约束,并将相应的源位置定位为第9行、第10行和第14行中向后切片的端点。我们反转这些约束并单独重新运行符号执行,通过从相应的反向切片端点执行符号来检查是否满足。如果状态仍然可以达到sink函数,约束就被证明是不必要的。最后,我们可以删除非必要的约束(第11行),并包含必要的条件(第13和16行)。
(2)如清单3中的vulnGet所示,向后切片从第25行中的sink函数开始,并回溯执行树。在这个过程中,回溯聚焦于当前函数作用域,捕获与这个已写对象相关的任何内存处理程序函数,并将最远的调用位置设置为结束(第23行)。最后,符号执行从端点开始,利用相关处理程序的函数语义解决分配大小的约束。
4 Implementation
我们用大约6200行Python代码、4300行C代码和5100行Java代码实现了SFuzz的原型系统。基于Ghidra[17]实现了污点分析模块和语义恢复部分。基于UnicornAFL和Driller[35]构建了模糊引擎,基于Angr[36]实现了concolic分析器。我们扩展了Driller,使其适用于RTOS图像,包括基于我们自己设计的RTOS加载器重新实现其跟踪记录器,以跟踪目标代码片段的执行跟踪。我们的系统基于以下几个基本程序:
Image Extraction:我们利用嵌入在固件中的字符串来识别RTOS的类型(例如,VxWorks 5.5.1),并利用BinWalk来提取RTOS映像的内容。同时,为了对内容进行分解,我们利用图像中机器代码的特征来确定CPU架构的类型(如MIPS)
Base Address Recognition:因为RTOS系统中的许多数据引用或函数调用操作依赖于基址,错误的地址将导致错误的数据引用或控制流跳转。我们实现这部分的核心思想是,只有正确的基址才能将大多数数据引用指针链接到预期的目标。该方法是在Vxhunter[45]中提出的,并在一些相关的工作中使用,如FirmXRay[37]。这个模块包含两个识别基址的步骤。它首先从系统中识别并提取数据引用指针;其次,它将数据指针的绝对地址与预期目标进行匹配。应该声明(i)我们只使用字符串指针来帮助识别基地址,这已经足够好了(如§5.4中的结果所示);(ii)我们基于PCode实现该方法,PCode是Ghidra对汇编语言指令的中间表示,而不是特定架构的指令。因此,它可以支持更多的体系结构。
Function Semantic Reconstruction:如§3.1所述,我们需要函数语义来指导污染分析和定位敏感片段。SFuzz主要恢复三类函数的语义和功能:(i)接收、解析或共享外部输入数据(即用户输入)的函数;(ii)sink函数(如memcpy);(iii)设置或获取全局数据的函数。我们实现了四种自动恢复函数语义和识别敏感函数的方法。
(1)Symbol File & Log Function:根据我们的分析,一些供应商(例如TP-Link和MERCURY)将发布标记函数名称的符号文件;同时,用来输出运行时错误的日志函数也可以用来恢复函数名。根据我们的分析,一些供应商(例如TP-Link和MERCURY)保留了可以标记固件中函数名称的符号文件。同时,一些厂商使用日志函数输出运行时错误,这也可以帮助恢复函数名,如语句logOutput(ostream, "devDiscover: error, ret = %d", retcode)中的devDiscover。
(2)Virtual Execution:首先,该方法将目标函数的参数和返回值的数量与标准库函数进行比较,以找到潜在的匹配函数,例如strcpy。其次,它分配内存空间,初始化寄存器的状态,并为函数设置参数的初始值。最后在函数体中模拟代码,通过分析输出值和模拟影响的内存空间来确定匹配函数。我们利用这种方法来识别标准库函数,如memcpy和printf。
(3)Web Service Semantic:我们利用共享字符串在前端文件(如HTML、PHP和JavaScript)和后端文件中标记用户输入,以恢复与web服务相关的一些功能的语义。我们基于SaTC[9]中提出的方法实现了该方法。
(4)Open Source Firmware:一些供应商选择开源RTOS项目来构建他们的系统,例如eCos和FreeRTOS。对于这些类型的系统,在预处理固件时确定了它们的版本之后,我们可以利用B2SFinder[42]和一些其他工具[41],根据代码中嵌入的字符串、immediate和其他显式特性,将固件中的函数与开源项目中的函数进行匹配。
5 Evaluation
为了评估我们的方法,我们应该回答以下研究问题:
RQ1:SFuzz能否发现嵌入式设备RTOS中的真实漏洞?(§5.1)
RQ2:SFuzz的每个部分对于有效地发现RTOS中的bug是否都是必要的?与最先进的工具相比,我们的工具表现如何?(§5.2和§5.3)
RQ3:SFuzz是准确和有效的漏洞发现在每一步?(§5.4)
Dataset:如表1所示,我们从11个供应商的17个系列中收集了35个固件样本。这些设备涵盖三种RTOS类型,并提供各种服务,包括23个路由器、7个打印机、2个防火墙、2个交换机和1个BCI(脑机接口)。其中7台采用MIPS-BE架构,13台采用MIPS-LE架构,1台采用ARM-BE架构,14台采用ARM-LE架构。平均来说,每个固件是9兆字节,SFuzz总共处理了314兆字节。
Environment Setup:我们的实验运行在Ubuntu 18.04主机上,内存为256 GB, 32核Intel Xeon处理器为2.4 GHz。特别是在处理一个数据入口点时,我们将每个实验的模糊部分的时间限制为6小时,每个实验使用一个CPU核心进行操作。根据我们的观察,在这个时间线之后,没有一个实验可以找到新的路径或崩溃。
Experiments Design:为了回答RQ2,我们设计了五个实验来测试不同配置的RTOS,如表2所示。SFuzz是一个功能齐全的fuzzer,它利用函数调用和条件跳转指令的处理程序,以及符号执行引擎来增强fuzzer。SFuzz-Handler不使用控制流节点处理程序(§3.2)来处理与控制流相关的关键节点。SFuzz-FHandler只处理条件分支语句,而SFuzz-CHandler只处理函数调用指令。最后一个实验是使用现有的最先进的代码片段模糊器UnicornAFL[25]进行的,我们改进了它的程序加载器,使其适用于我们数据集中的各种RTOS固件。请注意,普通灰盒模糊器(例如UnicornAFL)和其他方法(例如SFuzz)从起点部署在相同的原始执行树上。然后,每种方法根据各自的原则应用量身定制的策略。
SFuzz:利用函数调用和条件跳转指令处理程序,以及符号执行引擎来增强fuzzer。
SFuzz-Handler:不使用控制流节点处理程序来处理与控制流相关的关键节点。
SFuzz_4-FHandler:只处理条件分支语句。
SFuzz-CHandler:只处理函数调用指令。
UnicornAFL:改进了其程序加载器,使其适用于数据集中的各种RTOS固件。
Bug Confirmation:SFuzz生成的每个警报都包含来自源点的唯一崩溃输入和路径约束的符号表达式,其中可能包括其他数据源或全局变量。我们手动验证了每个警报,只有它才能导致真正的错误,我们认为这是一个漏洞。
5.1 Real-world 漏洞
SFuzz在不同设备(包括路由器、打印机、防火墙和BCI)的20个固件样本中发现了77个新bug。到提交时,其中68个已经得到供应商的确认,并且68个已被分配CVE或CNVD id(46例CVE和21例CNVD, 64例严重);9个bug仍在等待供应商的回复。图3显示了与这些错误相对应的代码片段的数据接收点和任务的类型。具体来说,这些错误存在于许多不同的任务中,比如HTTP、UDP和蓝牙。此外,输入数据来自多个数据源,例如Web解析器、NVRAM处理程序和Socket处理程序。此外,我们还列出了数据集中已发现的bug的详细案例研究。
5.2 与现存的方法相比
为了了解SFuzz的每个组件对模糊结果和性能的贡献,并分析我们的工具与其他“类似”工具的实际性能比较,我们设计了这个实验。由于这些工作都不能直接应用于RTOSes,并且难以迁移到RTOSes,因此我们对这些工作进行了模拟,如实验设置部分所示。SFuzz-FHandler中使用的策略是T-Fuzz[26]中使用的策略的超集,T-Fuzz不是完全开源的,不能用于RTOS的代码片段。因此,这里我们使用SFuzz-FHandler来表示T-Fuzz。同时,SFuzz-Handler相当于Driller[35]的一个版本,用于代码片段执行。我们将从五个设备收集的相同执行树提供给所有这些工具,并从读取数据包的相同源点启动它们。通过对五种工具的实验,我们从有效性、稳定性和效率三个方面比较了它们的性能。
Effectiveness:从表3可以看出,这5个工具都能在一定程度上发现真实设备中的漏洞,漏洞数量从4到34不等。UnicornAFL和SFuzz-Handler都只能从4棵执行树中找到错误,而且它们只探索了1390条和1366条执行路径。当我们在SFuzz-Handler(即SFuzz-FHandler或T-Fuzz)的基础上应用Cbranch补丁时,我们可以看到,虽然探索路径的数量增加了149%,达到3397条,但实际可以发现的bug数量基本相同。当我们在sfuzzhandler(即SFuzzCHandler)的基础上应用FuncCall补丁时,虽然路径探索只增加了23%,但在19棵执行树上发现了17个错误。我们的完整方法结合了上述模式,最终可以在5个模型的105个执行树上触发多达134个独特的崩溃,并发现34个错误。SFuzz的性能优于所有比较工具。
Stability:为了比较这些工具之间的稳定性,我们检查了五种设备上所有模糊执行的成功模拟比率。从结果中可以看出,不同的设备在不同的工具之间有不同的稳定性变化,但是我们仍然发现FHandler(处理函数调用指令)极大地提高了稳定性。腾达AC11的稳定性从6.02% (SFuzzHandler)提高到97.69% (SFuzz-CHandler), TP-Link WDR7660的稳定性从27.98% (SFuzz-Handler)提高到64.62% (SFuzz-CHandler)。另一方面,CHandler(处理条件分支)带来了一些轻微的不利影响。因此,SFuzz同时应用FHandler和CHandler,并能保持令人满意的稳定性。
Efficiency:在检查这五种工具的时间消耗时,我们发现应用的方法越复杂,测试所花费的时间就越多。实验结果10展示了不同工具在每个设备上花费的平均模糊时间。其中,UnicornAFL、SFuzz- handler、SFuzz- fhandler、SFuzz- chandler和SFuzz在一台设备的一个执行树上进行fuzz测试的平均时间分别为293s、723s、909s、1006s和1049 s。它表明SFuzz花费了更多的时间,但保持了可接受的范围。
5.3 与符号执行比较
我们进行了全面的实验,将SFuzz与传统的符号执行(SE)技术进行比较。由于没有现有的SE工具可以直接测试rtose,我们基于Angr[36](一个流行且维护良好的SE工具)自己实现了一个原型。我们在表4中列出了结果。结果表明,切片方法显著提高了bug发现的效率。我们还将SFuzz的控制流节点处理程序添加到SE工具中。结果表明,该处理程序可以提高SE在bug发现和路径探索方面的性能。
Bug数量:SFuzz可以在5个供应商的样本中找到34个错误。符号执行(SE)只发现了8个错误。在Control Flow Nodes Handler的帮助下,SE+Handler可以发现19个错误。
Time-to-Expose:与其他方法相比,SFuzz可以在更短的时间内发现错误。对于Tenda样本,SFuzz在33分钟内发现一个bug, SE+Handler需要52分钟,SE需要72分钟;对于TPLink, SFuzz在27分钟内发现了一个错误,SE+Handler花了204分钟,SE没有发现任何错误;对于理光样本,SFuzz在16分钟内发现一个错误,SE+Handler需要97分钟,SE需要178 min;对于FAST, SFuzz在27分钟内找到一个bug, SE+Handler需要79分钟,SE需要213分钟;对于MERCURY, SFuzz在38分钟内找到一个bug, SE+Handler需要47分钟,SE需要226分钟。
Path exploration:我们的切片方法可以比其他方法获取更多的路径,并且比符号执行更适合于模糊场景。对于Tenda样本,SFuzz可以探索2484条路径,SE+Handler执行389条路径,SE运行491条路径;对于TP-Link, SFuzz可以探索235条路径,SE+Handler执行83条,SE只运行40条路径;对于理光,SFuzz可以探索122,SE+Handler运行38,SE运行95;对于Fast, SFuzz可以探索815,SE+Handler运行411,SE只执行143;对于MERCURY, SFuzz可以探索53,SE+Handler运行72,SE运行107。在这些示例中,SFuzz总共探索了3709条路径,SE+Handler执行了993条路径,SE只运行了876条路径。应该注意的是,SFuzz在MERCURY中运行的路径更少,因为它在比其他方法更短的时间内得到错误。
5.4 Accuracy and Efficiency
在本节中,我们评估了SFuzz每个部分的准确性和效率,包括前向切片器(即语义重建和前向切片)、微模糊和Concolic分析器。
Semantic Reconstruction:在我们的数据集中,SFuzz可以分析31个样本。基址识别模型可以正确识别25个固件样例的基址。6种型号(FAST FW325R、FAST FW313R、MERCURY MW325R、TP-Link WDR5660、Tenda AC5、Tenda AC6V2)无法自动识别,其基址由人工分析确定。通过使用符号表和手工验证,我们发现SFuzz自动恢复的大多数语义都是准确的,交叉验证的准确率超过90%。通过符号文件恢复方法,对7个模型进行了语义恢复。web服务语义恢复方法识别了7种Tenda设备的web输入函数语义。虚拟执行方法可用于24个样本的语义恢复。八种模型使用日志函数模式来恢复它们的函数语义。特别是在RICOH-SP330(打印机)中,SFuzz只找到一个用户输入的数据读取函数(即os_file_get)。因此它只提取一个对应的敏感调用图。此外,我们在数据集中展示了所有显示的输入源和sink函数的列表。
Forward Slicer:为了理解切片方法的准确性,我们需要检查此方法是否会导致基于全局变量的敏感数据流影响缺失,这些全局变量是在控制流代码处理程序处理的修补函数的作用域中引用的。因此,我们测量对数据流和控制流的可能影响,具体来说,我们计算修补函数通过全局变量对输入数据和分支条件的影响程度。最后,我们计算每个修补函数的比例。从表6可以看出,这些模型中的两个比值都保持在较低的范围内(4.67%和4.33%),说明敏感数据流通过全局变量泄漏的可能性是可持续的。
为了检查切片方法的效率,我们需要将切片的大小与整个二进制文件进行比较,并检查我们的切片中处理了多少函数调用指令和条件分支,以及在这些被处理的位置中,在接下来的模糊处理过程中会触发多少个站点。如表5所示,切片调用图中的函数数与总函数数的比值平均为1.52%。因此,它表明我们的切片足够小,可以节省分析工作。在这些调用图中,函数调用指令和条件分支被处理的比例平均为89.32%和18.02%。此外,24.71%的调用指令和13.9%的条件分支是在后续的模糊处理过程中触发的。因此,证明了这些修剪站点是必要的,并在接下来的过程中确实做出了努力。
Micro Fuzzing:在表7中,我们工具的Forward切片器可以找到340个独特的执行树,这些执行树可能会在11个不同的模型中引入错误。Micro Fuzzing引擎识别出245个易受攻击的接收器函数,并根据这些潜在的错误构造崩溃输入。一个执行树的平均分析时间从少于7分钟到超过半小时不等。一个模型的总分析时间从20分钟到21小时不等,这取决于要探索的执行树的复杂性。
7 Conclusion
我们提出了一种新的基于切片的模糊检测方法SFuzz来检测实时操作系统中的安全漏洞。基于RTOS单片系统可以被分割成有意义的代码片这一观点,SFuzz利用前向切片来构造一个定制的执行树,它足够小,可以在模拟器上驱动灰盒模糊,并利用前向和后向切片来执行concolic测试,以验证来自模糊的唯一崩溃。SFuzz已成功在20个RTOS设备中发现77个零日软件漏洞,并已分配67个CVE或CNVD id。我们的评估结果表明,SFuzz的每个部分都帮助它在发现RTOS中的错误方面优于最先进的工具(例如UnicornAFL)。