Bootstrap

[论文分享] APICraft: Fuzz Driver Generation for Closed-source SDK Libraries

APICraft: Fuzz Driver Generation for Closed-source SDK Libraries [USENIX 2021]

Cen Zhang Nanyang Technological University
Xingwei Lin Ant Group
Yuekang Li* Nanyang Technological University
Yinxing Xue University of Science and Technology of China
Jundong Xie Ant Group
Hongxu Chen Nanyang Technological University
Xinlei Ying Ant Group
Jiashui Wang Ant Group
Yang Liu Nanyang Technological University

模糊驱动程序需要用于模糊库。模糊驱动是一种程序,它可以通过给模糊器提供输入来执行库函数。实际上,模糊驱动是由安全专家编写的,驱动程序的质量取决于其作者的技能。为了减轻人工工作量和保证测试质量,人们提出了不同的技术来自动生成模糊驱动。然而,现有的技术主要依赖于源代码的静态分析,这使得为闭源SDK库生成模糊驱动程序成为一个开放的问题。闭源库的模糊驱动生成面临两大挑战:1) 只能从库中提取有限的信息; 2) API函数之间的语义关系复杂,需要保证语义关系的正确性。
为了解决这些挑战,我们提出了一种自动模糊驱动生成技术APICRAFT。APICRAFT的核心战略是集采联合。首先,APICRAFT利用静态和动态信息(头文件、二进制文件和跟踪)以实用的方式收集API函数的控制和数据依赖项。然后,利用多目标遗传算法对收集到的依赖项进行组合,构建高质量的模糊驱动。我们实现了APICRAFT作为模糊驱动生成框架,并使用来自macOS SDK的五个攻击面对其进行了评估。在评估中,由APICRAFT生成的模糊驱动程序比人工编写的模糊驱动程序显示出更好的代码覆盖率,平均提高了64%。我们进一步使用APICRAFT生成的模糊驱动进行了长期的模糊测试。经过大约8个月的模糊测试,我们到目前为止已经在macOS SDK中发现了142个漏洞和54个分配的cve,这些漏洞可以影响流行的苹果产品,如Safari, Messages, Preview等。

一句话:APIcraft,利用动静信息收集API函数控制与数据依赖,指导生成高质量fuzz输入

导论

最先进的fuzzers,如AFL [3],libFuzzer [4]和Honggfuzz [5],已经在数百个真实的软件程序和库中检测到超过16000个漏洞 [6]。为了测试一个程序,fuzzer需要找到一个它可以提供输入的入口点。为了测试一个库,fuzzer需要一个库的应用程序作为切入点。这个应用程序被称为模糊驱动程序(fuzz driver)。在实践中,模糊驱动程序的创建主要是安全分析人员的手工构建。一个模糊驱动程序的质量取决于它的作者的技能和知识。因此,创建有效的模糊驱动程序通常是一项耗时且具有挑战性的任务。
为了解决模糊驱动生成的问题,研究者提出了一些技术,如FUDGE, fuzzgen[7,8]。这些技术利用库的现有消费者应用程序的源代码来为它合成模糊驱动程序。一方面,用户程序的源代码提供了正确的API使用示例,这对模糊驱动很重要;另一方面,对源代码的需求限制了在闭源SDK中的库上使用这些技术。然而,由于闭源SDK库的流行和市场主导地位,它们的安全性同样重要。以Preview为例,它可以显示各种类型的文件,支持macOS SDK的不同文件解析库。由于Preview预装在每台苹果PC/笔记本电脑上,相关库中发现的漏洞可能会影响数百万终端用户。总之,闭源SDK库的模糊驱动生成是一个重要但尚未得到充分研究的问题。

Challenges

为闭源sdk生成模糊驱动程序的挑战主要来自两个方面。

  • 第一个挑战是,只能从库中提取有限的信息。库源代码的缺失阻碍了提取模糊驱动合成所需的正确API用法。没有源代码,无法准确提取库api的控制流和数据流信息。
  • 第二个挑战是API函数之间的语义关系很复杂,但需要确保它们的正确性。触发库深处的代码需要测试驱动程序包含语义正确的API调用序列。实际上,不仅API调用组合的搜索空间巨大,而且API调用序列的语义正确性也难以保证。

Contributions

(1)确定闭源SDK模糊驱动生成的主要挑战,并提出了收集组合方法(collect-combine)。
(2)开发APICRAFT作为闭源SDK的第一个自动模糊驱动生成框架,它显示了测试实际应用程序的能力。
(3)在macOS SDK上评估了APICRAFT,发现了142个以前未知的漏洞。

方案

APICRAFT的总体结构如图1所示。基本上,APICRAFT将目标SDK及其消费者程序作为输入,并产生模糊驱动程序作为输出。APICRAFT采用一种自底向上的方法来综合模糊驱动,这种方法可以描述为一种收集组合方法。这种方法包括两个主要阶段。
第一阶段是收集目标SDK库中API函数之间的依赖关系。在这里,APICRAFT使用目标SDK的消费者程序的执行跟踪作为正确API用法的参考。APICRAFT只收集与错误处理相关的API函数间数据依赖项和控制依赖项,而不是收集每个数据和控制依赖项,这是不切实际的。
第二阶段是将收集到的API函数依赖组合起来,以构建具有所需属性(如紧凑性和依赖多样性)的模糊驱动套件。由于期望的属性可能相互冲突,APICRAFT使用基于多目标遗传算法的策略来优化模糊驱动的整体,以满足预定义的目标集。

在这里插入图片描述

假设,安全分析师Jane不想从文档中学习,而是想通过学习现有的消费者程序如何使用库函数来创建模糊驱动程序。由于CoreText库的消费者程序几乎都是闭源的复杂商业软件,很难通过拆解和静态分析来提取库函数的正确用法。或者,Jane可以使用消费者程序的执行跟踪来推断调用库函数的正确顺序。从这个意义上说,Jane可以基于每个执行跟踪构建一个微小的消费者程序。假设Jane有两个消费者程序,如图2所示。
在这里插入图片描述

一旦Jane收集了关于CoreText正确使用的知识,下一步是使用这些知识来构建模糊驱动程序。当然,从执行轨迹中分割出来的两个消费者程序可以用作模糊驱动程序,如图3a所示。然而,Jane很快就会注意到,由于所覆盖的程序行为缺乏多样性,直接创建的模糊驱动器并不理想。在本例中,ExtractFont函数充当连接Font存根的创建和使用的枢纽。具体说,CalcLeadingSpace和DoubleLeadingSpace都接受Font对象作为输入。因此,Jane可以交换这两个函数来创建具有新函数组合的模糊驱动器。
图3b显示了根据枢轴点交换函数生成的模糊驱动器。尽管包含了比直接构建的模糊驱动更多的API函数组合,但用交叉生成的模糊驱动仍然远远不够理想: 1. 一些API函数组合仍然缺失。例如,DoubleLeadingSpace和CalcLeadingSpace都使用ExtractFont的结果。它们可以组合成一个模糊驱动程序并触发更多的程序行为,而不是相互替换。在这种情况下,如果先执行DoubleLeadingSpace, CalcLeadingSpace可能会遇到整数溢出错误。2. 有些组合是多余的。例如,图3b中引入的两个新组合并没有真正触发更多的程序行为。原因是程序使用Font对象的方式通常不受其生成方式的影响,调用CreateFontDescriptor或ProviderCreateWithData将最终生成相同的Font对象。简而言之,由跨界构建的模糊驱动套件缺乏多样性和紧凑性。然而,在大多数情况下,这两个期望的属性是独立的,并且可能相互冲突。因此,构建一组期望的模糊驱动需要平衡不同的目标(例如,紧凑性和多样性)。

在这里插入图片描述

现在,Jane已经意识到从执行跟踪中显式提取的模糊驱动程序需要改进,并且在改进模糊驱动程序的质量方面存在一些缺陷。她能否将所有雕刻的模糊驱动分解成功能之间的依赖关系,然后将这些依赖关系组合起来,重新构建能够实现多个独立目标的新模糊驱动? 经过一些推理和试错,Jane最终会意识到,与消费者程序生成的所需模糊驱动器应该如图3c所示,因为这些模糊驱动器紧凑,但可以触发最多样化的程序行为。
事实上,在APICRAFT中,我们系统地将这个故事的整个推理和试错过程描述为算法,并可以自动生成所需的模糊驱动器,如图3c所示。

API Function Dependency Collection

通常,数据依赖关系可以有多种形式。例如,函数A可以通过读取B写入的套接字来依赖函数B。不同形式的数据依赖需要不同的检测和收集技术。目前,APICRAFT使用以下两种数据依赖关系。
在这里插入图片描述

上述数据依赖关系可以通过匹配API函数参数/返回值的类型和值来提取。类型信息是从SDK的头文件中收集的。通过分析头文件中的函数声明,APICRAFT收集每个API函数的参数类型和返回值。然后,对消费者程序进行跟踪,获取价值信息。通过在执行期间挂钩API函数的入口和出口,APICRAFT记录线程id、嵌套级别以及其输入和输出集(即参数、返回值和输出参数)的递归内存转储。这里,术语嵌套级别用于表示嵌套API函数调用的深度。如果API函数是直接从消费者程序而不是某些API函数调用的,则其嵌套级别为1。如果一个API函数被另一个嵌套级别为x的API函数调用,则其嵌套级别为x + 1。

在这里插入图片描述
APICRAFT对收集到的轨迹进行处理,以高效准确地提取数据依赖关系。注意,收集了多个消费者程序的多个跟踪,每个跟踪都包含按执行顺序排列的API函数列表。APICRAFT首先根据线程id将每个跟踪划分为更短的片段,然后过滤掉嵌套级别不是1的函数。嵌套级别较高的函数被认为不太重要,并被删除,因为它们不会直接从消费者程序中调用。过滤之后,APICRAFT为每个API函数识别可能的输出参数。具体来说,如果输入参数是一个指针,并且它指向的内容在函数执行期间发生了变化,那么它将被标记为输出参数。

在这里插入图片描述

Extraction

算法1给出了简化的数据依赖提取过程。输入T是一段经过处理的跟踪,其中包含已执行API函数的列表,输出R是一组提取的数据依赖项。该算法的关键思想是,对于跟踪(FA, FB)中的任意两个API函数,APICRAFT尝试找到匹配的对<FA, Out>和<FB, in>。
如果它们不相同,则进一步检查这两种类型是否可转换。具有不同属性限定符(例如,const限定符)的相同类型是可转换的。此外,如果指针类型的指针类型大小相等,或者其中一个指针类型为void *类型,则指针类型也是可转换的。

Inference

除了从执行跟踪中提取依赖关系外,APICRAFT还进一步根据现有的依赖关系推断出新的依赖关系。其基本思想是,来自一个SDK的API函数通常共享相同的设计或实现模式。因此,遵循适当的启发式方法,可以根据提取的依赖项推断出没有出现在消费者程序跟踪中的新的有效数据依赖项。APICRAFT使用以下三个推理规则

在这里插入图片描述

在实践中,跟踪不能包含它所覆盖的API函数的数据依赖项的完整列表。R1, R2减轻了这个限制。假设有两组API函数分别创建和使用特定类型的对象,并且跟踪只包含一个或两个相关的依赖项,R1, R2可以帮助推断这两组函数之间的所有链接。另一个观察是,线程与其他线程交换有限的数据,并且通常交换指针(例如,一个线程只为其他线程创建对象)。因此,APICRAFT使用R3来匹配来自不同线程的跟踪之间的指针,以挖掘出这些依赖关系。在推理过程中,首先应用R3,然后重复应用R1, R2,直到不能产生新的数据依赖。

除了数据依赖关系,APICRAFT还收集控制依赖关系以促进模糊驱动的合成。具体来说,APICRAFT收集错误处理信息。通过结合静态和动态分析,可以收集两种类型的信息:API函数的输出是否需要进行错误处理和错误条件。
APICRAFT对不同类型的函数输出使用不同的策略。如果API函数的输出参数或返回值的规范类型是指针,则生成的模糊驱动程序将始终检查输出值是否为NULL(如果为NULL则立即退出)。如果类型是整数,APICRAFT将尝试定位消费者程序应用错误检查的条件分支,并转储错误检查条件(假设消费者程序将在良性输入下执行无错误分支)。首先,APICRAFT在跟踪过程中通过记录API函数的返回地址来定位调用地址。然后,通过静态分析,APICRAFT找到调用终止函数(如_exit、_abort、__cxa_throw)的地方的主导基本块(即检查点)。最后,APICRAFT重新运行消费者程序,从调用站点开始进行动态污染分析,将整数输出标记为污染源。当调用站点的函数返回或检查点被污染时,污染传播停止。受污染的检查点将被视为错误检查分支,其条件将被转储为错误处理条件。对于其他类型的输出,将不会在生成的模糊驱动程序中检查它们的值。

Key Metrics

在这里插入图片描述

在这里插入图片描述

1) Initial Residents

为了构建最小模糊驱动程序,APICRAFT还需要在输入相关函数中填充其他参数的值。它从三个来源搜索以提供值:另一个API函数的输出值,预先配置的基本知识,或转储的参数值。APICRAFT从上述源中随机选择值并生成驱动程序代码。生成的代码使用几个准备好的输入种子进行编译和执行(我们称之为稳定性测试,详见π)。一旦测试通过,它就是一个有效的最小模糊驱动程序。在构建一个或多个最小模糊驱动程序之后,APICRAFT迭代所有数据依赖项,尝试将它们与驱动程序链接,并将链接的驱动程序设置为初始驻留。

2) Objectives

我们设计了三个得分公式来描述确定的三个指标。我们首先介绍核心依赖的概念。在一个模糊驱动中,如果一个FA的输入是输入数据或者是另一个核心依赖的输出,那么<FA, Out, FB, In>就是一个核心依赖。驱动程序中的核心依赖关系应该形成一个自顶向下的树状图,表示输入数据流。换句话说,数据流从根节点(与输入相关的API函数)开始,核心依赖项帮助输入数据流到不同的API函数中。所有非核心依赖项都用于填充核心依赖项内的函数输入。在计算客观分数时,我们主要使用核心依赖项,而不是模糊驱动中的所有数据依赖项。区分不受输入数据影响的非核心依赖的基本原理是,当不同的输入被馈送到模糊驱动程序时,它们在模糊过程中是没有价值的。此外,我们将与核心依赖相关的函数表示为核心函数。

在这里插入图片描述

3) Sequentialization

在进化过程中,模糊驱动是多图形式的,APICRAFT在此基础上应用突变操作。序列化是将图形转换为代码(API函数调用的序列)。然后在演进过程中,模糊驱动程序代码用于动态信息收集(用于计算EFF)和有效性测试(通过编译和执行)。

在这里插入图片描述

图5显示了两个数据依赖关系之间的所有突变操作。突变操作是替换/添加/删除操作与数据依赖项的In/Out的组合。注意,排除了Add/Delete In的组合,因为向输入参数传递多个值或删除输入参数的值都不是有意义的操作。此外,图5中的突变操作简化了,只考虑两个给定的依赖项。将这些操作应用于两个模糊驱动程序需要正确处理驱动程序中的其他依赖项。例如,圆形/矩形节点都可以有其他数据依赖关系(有父/子节点)。APICRAFT通过仔细处理这些情况来保证操作的正确进行。

实验

Implementation

APICRAFT被实现为一个由三个主要组件组成的系统,预处理(Python 1581行,c++ 873行,Bash 450行),依赖收集(Python 716行,Bash 182行)和依赖组合(Python 3749行,Bash 93行)。

Consumer Program Tracing Tool
APICRAFT使用定制的API跟踪工具在预处理期间跟踪一系列信息(例如,线程id,嵌套级别,内存转储)。这个跟踪工具能够处理macOS中的GUI程序,包括Safari,预览,QuickTime播放器等。它可以生成数千行代码来挂钩数百个函数,同时确保GUI程序在跟踪期间顺利执行。与现有的动态挂钩/仪表工具(如Pin[18]、Frida[19])相比,速度更快、精度更高。以下关键特性可以提高其性能。
我们选择Type-II钩子(图6),它提供了精确的函数嵌套级别。当挂起一个函数时,Type-I钩子需要两种类型的钩子点,一个是进入点,一个是退出点。在二进制分析中,识别API函数的起始点(进入点)很容易,而准确识别所有退出点则很困难。原因是不能通过简单地匹配ret指令来检测函数的一些退出点,特别是当它的汇编被高度优化时。
我们使用一种称为函数插入的轻量级钩子技术。通过将钩子代码包装到一个与钩子目标具有相同原型的函数中,并设置环境变量来配置操作系统的动态链接器,可以实现钩子。具体来说,在macOS中,我们为钩子设置了DYLD_PRELOAD和DYLD_INTERPOSE

Fuzz Driver Generation

表1列出了针对每个攻击面跟踪的应用程序。注意,所有这些程序都是内置的macOS应用程序。我们为每个GUI程序准备了一个输入文件,并手动使用这些程序来生成跟踪。
在这里插入图片描述

将APICRAFT应用于macOS SDK中的五个攻击面。表2显示了每个主要步骤的中间结果。第一阶段是预处理。我们选择了一系列GUI程序作为消费者程序。
在这里插入图片描述

Comparison with Manually Written Fuzz Driver

图7 - 9 显示覆盖率/崩溃比较结果。

在这里插入图片描述
在这里插入图片描述

Effectiveness of Each Component

推断规则
表3列出了统计信息。平均而言,R1/R2/R3分别贡献43%/32%/18%,组合时贡献62%(从R1/R2/R3推断的依赖关系可能重叠)。

在这里插入图片描述

Fuzzing Campaign

我们建立了一个长期的模糊测试来fuzzing这五个攻击面。该活动使用所有生成的模糊驱动程序,这些驱动程序具有来自APICRAFT输出的独特数据依赖关系(即最后一轮居民的第一个帕累托边界)。到目前为止,已经发现了142个独特的漏洞。具体来说,其中54个已经被苹果确认并分配了CVE编号,其中16个已经被苹果确认并将在即将到来的苹果安全更新中分配CVE编号。

在这里插入图片描述

Case Study 1: Issue 756641529

以Font攻击面(Table 4 Issue 756641529)中发现的漏洞作为代表性案例进行研究。本案例虽小但完整,可以帮助演示APICRAFT的大部分功能。图10显示了重现问题756641529的最小模糊驱动程序,它是从APICRAFT生成的模糊驱动程序中雕刻出来的。为了简洁和易于理解,简化了函数名,并为每个变量赋予了一个有意义的名称。(生成的模糊驱动程序中的变量名并不人性化。)

在这里插入图片描述

Case Study 2: ExtAudio API Family

对于音频攻击面,我们发现生成的模糊驱动程序可以根据其名称是否包含前缀为ExtAudio的函数分为两组。换句话说,在一些模糊驱动程序中,大多数函数的名称都以ExtAudio开头,而在其他驱动程序中则没有这样的函数。我们进一步调查了这个问题,发现音频攻击面涉及两组独立的服务:音频文件服务[34]和扩展音频文件服务[35]。它们都有自己的api,用于创建文件存根和输入解析。因此,为这两个服务生成的模糊驱动程序是完全不同的。
我们发现安全分析师只为音频文件服务编写模糊驱动程序,因此我们从生成的套件中选择相应的模糊驱动程序进行公平比较。然而,在长期的模糊测试活动中,我们使用两种类型的生成模糊驱动进行模糊测试。事实上,我们发现涉及扩展音频文件服务的模糊驱动程序贡献了大量CVE。

总结

References

[3] M. Zalewski. american fuzzy lop (2.52b). https:// lcamtuf.coredump.cx/afl.
[4] libFuzzer. https://bit.ly/3uS2Uu8.
[5] Honggfuzz. https://bit.ly/3fa4fFG.
[6] Clusterfuzz. https://bit.ly/3hpXimI.
[7] D. Babi ́ c, S. Bucur, Y. Chen, F. Ivanˇ ci ́ c, T. King, M. Kusano, C. Lemieux, L. Szekeres, and W. Wang. Fudge: fuzz driver generation at scale. In Proceedings of the 2019 27th ACM Joint Meeting on European Software Engineering Conference and Symposium on the Foundations of Software Engineering.
[8] K. Ispoglou, D. Austin, V. Mohan, and M. Payer. Fuzzgen: Automatic fuzzer generation. In 29th {USENIX} Security Symposium ({USENIX} Security 20).
[18] C. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney, S. Wallace, V. J. Reddi, and K. Hazelwood. Pin: building customized program analysis tools with dynamic instrumentation.
[19] O. A. V. Ravnås. Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers. https://frida.re.

Insights

(1) 可以参考该工作的静动态信息收集

;