阅读笔记:CapSan-UB: Bug Capabilities Detector Based on Undefined Behavior
CapSan-UB: Bug Capabilities Detector Based on Undefined Behavior
- 非内存错误(如未定义的行为错误)
- CapSan-UB 则利用 UBSan 增加了检测未定义行为错误能力的功能,从崩溃上下文中提取三个维度的错误能力信息
- 收集bug,修改编译选项,让程序继续执行。
摘要
漏洞修复和利用与漏洞暴露的能力密切相关。现有的漏洞挖掘方法主要关注基于内存的漏洞。然而,非内存错误(如未定义的行为错误)也对网络安全构成严重威胁,但却缺乏对其能力的研究。在本文中,我们介绍了基于 UBSan 的能力检测器 CapSan-UB,它专门用于探索未定义行为错误的能力。我们的工具在四个实际软件中发现的九个未定义行为错误上进行了测试。该测试成功地在不同的代码位置发现了新类型的未定义行为错误,从而证明了我们方法的有效性。
引言
近年来,以覆盖率为导向的灰盒模糊器迅速发展,导致互联网上开源软件的安全漏洞激增,给开发人员带来了巨大挑战。然而,即使开发人员完成了漏洞修复过程,在漏洞披露和向最终用户部署安全补丁之间仍存在很大的时间差。这一时间差为攻击者提供了可乘之机,可能导致重大安全风险[1]。
传统的模糊器通常会在遇到第一个程序崩溃点时停止并记录当前种子。但是,这些模糊器只关注程序是否崩溃,而不关注崩溃是如何触发的。事实上,同一个错误的根本原因可能有不同的崩溃点,而同一个崩溃点也可能以不同的方式被激活。这些激活错误的不同方式清晰直观地反映了错误的不同能力。
彻底探索漏洞的功能有助于全面了解漏洞及其安全影响。这些知识有助于确定漏洞的严重程度、确定漏洞修复的优先级以及有效降低相关的安全风险。目前,已有大量研究利用基于机器学习的文本分析方法来确定漏洞的安全影响[2, 3]。此外,聚类技术也可用于根据 PoC 的根本原因对其进行分类,从而帮助进行错误修复[4, 5]。另一方面,错误能力的增强意味着更容易被利用。现有的一些研究表明,可溢出字节的长度越灵活,越有利于漏洞利用,从而导致更严重的安全后果[6, 7, 8]。
在最新的漏洞能力探索工具 Evocatio[9] 中,作者利用种子崩溃信息的六个维度作为描述种子能力的基本要素。这些维度包括错误类型、访问类型、访问字节数、受害对象名称、受害对象中的偏移量和位置。这些维度的信息来自 AddressSanitizer(ASan)[10]。
然而,Sanitizer 并非只有一种类型,仅仅依靠 AddressSanitizer 来检测错误能力是不全面和不充分的。当模糊器检测到 AddressSanitizer 检测不到的未定义行为错误等错误时,错误能力检测器捕获和提取这些错误,并对其能力进行比较和分析,就变得非常具有挑战性。
事实上,根据主流开源漏洞平台上公开的漏洞报告,大量的漏洞不仅被 AddressSanitizer(ASan)[10] 检测到,还被 UndefinedBehaviorSanitizer(UBSan)检测到。此外,大量的未定义行为会给用户带来软件安全风险。因此,有必要丰富和增强现有漏洞能力检测器的检测能力。这可以通过在已检测到的错误能力基础上扩大错误能力类别的范围来实现,从而获得更广泛的能力。
贡献
- 我们对未定义行为错误的错误功能进行建模。
- 我们通过 CapSan-UB 实现了对未定义行为错误的检测。
背景
以能力为导向的模糊测试
AFL++[11] 的崩溃探索模式[12] 将 PoC 作为输入,并快速生成一组 PoC,试图保持程序的崩溃状态并探索代码中最远的路径。这种方法旨在评估漏洞的严重性和可利用性。与 AFL++ 类似,Evocatio 也采用了崩溃探索模式,目的是提高漏洞严重性评估的准确性,并协助确定漏洞修复的优先次序。Evocatio 引入了一种基于模糊的错误能力检测方法,使其成为最新的错误能力探索工具。Syzscope[13]与 Evocatio 一样,侧重于评估漏洞严重性,可以发现低风险漏洞背后的高风险影响。它主要用于内核漏洞的风险评估。虽然 KOOBE 中也有能力引导模糊器,但其研究目标与前两者完全不同。KOOBE 专注于生成特定的漏洞利用基元,而 AFL++ 的崩溃探索模式和 Evocatio 则旨在发现同一漏洞中的不同能力。
代码消毒器
消毒器是一类用于检测代码错误的工具。最早推出的是 Google 推出的 AddressSanitizer (ASan),它是一种低开销内存错误检测器。ASan 对被测程序进行检测,并使用影子内存实时高精度地检测内存损坏问题,误报率几乎为零。
ASan 是各种 “消毒器 “中的一种,仅靠它无法检测出所有代码错误。因此,谷歌还推出了以下工具:
- LeakSanitizer (LSan):检测代码中的内存泄漏。
- ThreadSanitizer (TSan):检测代码中的数据竞赛和死锁。
- MemorySanitizer (MSan):检测程序中未初始化的内存。
- UndefinedBehaviorSanitizer (UBSan):用于检测程序中的未定义行为错误。
设计
能力导向模糊器需要从 PoC 中提取相关特征来描述错误能力。在进行模糊测试时,通常会使用代码消毒器来捕获程序中的错误。开发人员通常使用 AddressSanitizer 来检测内存边界错误。在我们的案例中,为了识别未定义行为错误,我们使用 UndefinedBehaviorSanitizer(UBSan)作为能力检测器,并通过它收集相应的能力特征。UBSan 对被测程序进行检测,每当在检测过程中发现未定义行为错误时,UBSan 就会生成一份错误报告,记录下当时的信息。
图 1 描述了项目的整体工作流程。目标程序被 UBSan 检测后,将对其表现出未定义行为错误的能力进行检测。通过评估未定义行为检测能力的变化并反复进行模糊处理,我们最终发现了程序中未定义行为的新实例。
基于未定义行为错误的错误能力建模
程序中的未定义行为错误会产生有害后果,包括但不限于:
- 不可预测的行为:未定义行为的主要问题是其行为不可预测,导致程序不可靠,出现意外结果和错误。
- 程序崩溃:某些未定义的行为会导致程序崩溃,例如访问未初始化的内存、取消引用空指针或执行无效的类型转换。这些崩溃会导致程序意外终止,影响用户体验和系统稳定性。
- 编译器优化问题:编译器对未定义行为的处理可能导致意想不到的优化结果,使程序在某些情况下表现出错误的行为。开发人员的预期与实际结果之间的这种差异会增加调试和维护过程的复杂性。这些行为有可能导致危险的安全漏洞,构成重大安全威胁。
UBSan 在编译器的帮助下对程序进行检测,以捕捉程序执行过程中的各种未定义行为。UBSan 自十年前的 LLVM 3.3 版本起就可用了,比更常用的 ASan(自 LLVM 3.1 起)稍晚一些。与 ASan 采用的方法类似,UBSan 提供的错误功能建模也需要综合考虑可用资源和资源成本。
一般来说,可以通过调用代码生成器运行时导出的 API 或解析生成的错误报告来获取代码生成器提供的有关错误功能的原始数据。
与 ASan 检测到的内存错误系统地分为 6 类相比,UBSan 检测到的未定义行为多达 28 种,种类繁多,情况多变,并有单个和分组的指定控制选项[16]。通过进一步研究 UBSan 的实现及其在 LLVM 源代码树下的回归测试,我们发现这些检测到的未定义行为可以分为 6 类,即浮点数、整数、指针、类型检查、隐式转换和其他,并且我们可以很容易地获取每种类型及其子类型的错误报告样本。因此,我们可以获得并筛选出具有合理区分度的细粒度错误细节。然而,这些报告的内容在不同类型之间差异很大,这使得解析这些报告变得非常棘手,而且成本很高,可扩展性很差,因为通常需要进行复杂的模式匹配和条件判断。
另一方面,在 UBSan 的运行库中可以找到包括”__ubsan_on_report “在内的多个导出符号,用户可以在错误报告准备就绪并被其主要组件访问后应用自定义钩子。因此,我们可以像 CapSan[9] 中的”__asan_on_error “一样,方便快捷地从粗粒度报告中提取错误能力。然而,这些导出符号仅在最近几年发布的 LLVM 版本中可用,编译器提供的 sanitizer 运行时公共头文件中并未声明,这意味着它们很可能被 LLVM 开发人员视为不稳定的实验特性,使用它们可能会导致潜在的兼容性问题。
总之,我们最终选择了基于后一种方法的错误能力建模。可以肯定的是,UBSan 这种基于钩子的机制在 LLVM 源代码树中有完整的语句、定义和回归测试,尽管这些符号在公共头文件中并不存在。而且它能在紧凑的设计中以可接受的粒度提取和处理能力,带来明显的好处。我们设计了 UBSan 的附加运行时库来实现错误能力模型,以及必要的钩子和 I/O 交互。该运行库和 UBSan 的原始运行库都将在编译器驱动程序的帮助下链接到目标二进制文件。
该模型有三个维度:错误摘要、错误详情和源代码位置。下面是这三个信息的详细说明:
- 错误摘要:它提供了每种未定义行为错误的简明摘要,作为错误详细信息的概览。
- 错误详细信息:它包括详细的错误信息,其中可能包括访问内存的范围、长度和内容等具体信息。
- 源代码位置:说明错误在源代码中的位置,包括文件名、行号和列号。
错误报告准备就绪后,每个维度都可以纯文本形式获取。我们的程序会对它们进行遍历和连接,然后对整个字符串进行规范化处理,以便进行反馈,最后将其保存到通过环境变量指定的文件中。此外,我们还对代码进行了精心设计和审查,确保它不会引入额外的错误,并以最快的速度运行,因为它会在发生未定义的行为后和程序退出前工作。
CapSan 利用 AddressSanitizer (ASan) 检测与内存相关的错误能力,而 CapSan-UB 则利用 UBSan 增加了检测未定义行为错误能力的功能,从崩溃上下文中提取三个维度的错误能力信息,作为特定 PoC 的错误能力。
基于未定义行为错误的错误能力检测
程序中的未定义行为错误会导致各种问题,包括不可预测的行为、程序崩溃和编译器优化问题。其中一些问题会导致程序崩溃,而另一些则不会。然而,传统模糊器只能捕捉程序崩溃并保留导致崩溃的种子,而忽略了不会导致程序崩溃的种子。这种限制使得模糊器难以捕获所有未定义行为错误。为此,我们改进了模糊器的错误检测行为,以捕获所有未定义行为错误。
为了实现这一功能,我们在编译目标文件时使用了”-fno-sanitize-recover =undefined “选项。-fno-sanitize-recover=”选项控制着 sanitizer 的错误恢复模式。启用错误恢复模式后,程序可以继续运行,就像没有发生错误一样,而禁用错误恢复模式则会导致程序崩溃,并在出现错误的第一时间退出。由于该功能是试验性质的,因此在使用 UBSan 作为 sanitizer 时默认为启用。然而,这种默认设置会导致模糊器只能捕获程序崩溃,而经常忽略不可预测的行为和编译器优化问题,从而无法检测到未定义的行为错误。因此,我们有必要在编译目标程序时禁用这一功能。
CapSan-UB 捕获当前 PoC 的错误能力时,会将该能力与之前发现的错误能力进行比较。如果是新能力,则保留;否则,则丢弃。
在模糊测试过程中,能力比较频繁发生,因此需要一种高效的存储和比较方法。在这种情况下,我们采用 DJB 哈希算法作为解决方案。DJB 哈希算法由计算机科学家丹尼尔-伯恩斯坦(Daniel J. Bernstein)设计,是一种简单高效的哈希算法,特别适合计算短字符串的哈希值。
测评
我们对所提出的工具进行了评估,该工具用于检测 9 种不同现实世界软件中与未定义行为相关的错误能力。所有实验均在 Ubuntu 18.04 LTS 服务器上进行,该服务器安装有 60 个逻辑内核、Intel® Xeon® Gold 6254 3.10 GHz CPU 和 200 GiB 内存。实验环境如表 1 所示。
评估分为两个部分。首先,我们对未定义行为错误的错误检测进行了有效性验证。我们分析了与未定义行为相关的错误能力建模的不同维度,全面了解了其特点。
在第二部分中,我们汇总并总结了在真实世界软件上进行的所有实验中捕捉到的错误能力。此外,我们还分析了新发现的错误能力,并将其与现有的错误能力进行比较,以找出任何明显的差异。
未定义的行为错误能力建模验证
我们利用测试用例 “libtiff-issue-548 “来验证检测未定义行为错误的有效性。该漏洞是 libtiff 库 4.5.0 版中未定义行为的一个实例。该错误发生在 “tif_lzw.c “文件的第 655 行第 20 列,涉及解析使用 LZW 压缩编码的图像时的空指针访问。
根据第 3.1 节所述的错误能力建模,我们从三个维度获得了 libtiff-issue-548 的以下信息:
- 错误摘要:问题:空指针使用
- 源代码位置:位置:tif_lzw.c:655:20
- 错误详情:信息:在 “code_t”(又名 “struct code_ent”)类型的空指针内访问成员
在随后的模糊测试中,我们将这三项信息统一起来,并将其作为哈希值存储在错误热图中,以便进一步进行能力比较。我们在 Ubuntu 20.04 平台上进行了长达 90 小时的模糊实验,总共发现了 708 个新的独特错误能力。
此外,就 libtiff-issue-548 而言,除了现有的错误外,我们还发现了新的错误类型。最初的错误是 “空指针使用”(null-pointer-use)运行时错误,发生在代码的第 655 行第 20 列,其中一个空指针被用于访问名为 “code_t”(也定义为 “struct code_ent”)的结构的一个成员。通过实验,我们又发现了两种未定义行为错误:”误对齐指针使用”(misaligned-pointer-use)和 “带偏移的空指针”(nullptr-with-offset)。前者涉及内存对齐问题,后者涉及对空指针的非法操作。此外,我们还在不同位置发现了未定义行为的实例:”tif_lzw.c49:75”、”tif_lzw.c661:28 “和 “tif_dir.c195:24”。图 2 展示了当前目标的已发现错误能力数量与时间流逝之间的关系。纵轴表示数量,横轴表示时间(小时)。
未定义的行为错误功能探索
在本节中,我们将总结 CapSan-UB 的所有实验数据并进行分析。我们构建了一个基于真实世界软件的实验数据库,其中包括 4 个软件程序和 9 个真实错误。图 3 涵盖了本实验中的所有目标,并显示了随着时间推移所发现的 Bug 能力数量的变化情况。
表 2 提供了所有实验数据的综合摘要,包括目标名称、具体错误标识符、新发现的错误能力,以及基于问题类型和位置类型的初步分类。如表所示,对于不同的目标软件,CapSan-UB 能够发现的错误能力数量差异很大。新发现的错误能力数量最少为 1(或可能为零),最多可达 1,737,201 个独特的错误能力。这些不同的崩溃往往表现出不同的溢出长度,从而导致不同的错误能力。根据对实验结果的分析,我们发现这些样本还可以根据错误摘要和源代码位置进行分类,因为我们经常会在不同位置遇到未定义行为错误。
从表中数据可以看出,我们的 CapSan-UB 能够有效探索真实世界目标软件中的未定义行为错误,满足了当前对未定义行为中的错误能力检测的需求。
结论和未来工作
未定义行为错误会导致严重的软件安全漏洞,造成重大损失。通过观察开源平台上的大量错误报告,可以发现许多错误不仅是基于地址检测器(AddressSanitizer)检测到的,而且也是基于未定义行为检测器(UndefinedBehaviorSanitizer)检测到的。然而,现有的能力导向模糊器主要依赖 ASan 作为能力检测器,忽略了未定义行为错误。因此,我们开发了 CapSan-UB,它支持在模糊过程中检测未定义行为错误,并对与未定义行为相关的错误能力进行建模。CapSan-UB 可以识别 CapSan 无法单独检测的错误类型,从而增强了其功能。
在今后的工作中,我们的目标是:
- 设计更合适的调度策略来引导模糊器,并从输出结果中发现更多不同的错误。
- 进一步增强和完善 CapSanUB 的传统功能,努力为模糊器与所有现有的 sanitizers 和错误严重性评估一起运行提供支持。
- 设计新颖的能力描述框架和能力表示策略,尝试与 LLVM 的编译器-rt 分离,更加关注能力的本质。
- 探索设计新的消毒剂,以更好地描述能力的本质。