突破性能极限:Honeybee结合Intel处理器追踪实现高速覆盖引导模糊测试

本文介绍Honeybee——基于Intel处理器追踪技术的实验性覆盖引导模糊测试工具,通过创新的静态缓存设计和定制化内核驱动,将IPT解码速度提升44倍,实现接近源码级插桩的性能表现。

Un-bee-lievable Performance: Fast Coverage-guided Fuzzing with Honeybee and Intel Processor Trace

Allison Husain, UC Berkeley
2021年3月19日
fuzzing, internship-projects, research-practice

今天我们发布了一款名为Honeybee的实验性覆盖引导模糊测试工具,它使用Intel处理器追踪(IPT)技术记录程序控制流。此前,由于捕获系统问题和低效的追踪分析,IPT一直因性能严重不足而受到质疑。我的冬季实习重点就是解决这些挑战,使基于IPT的模糊测试变得实用且高效。

IPT是一种硬件功能,可异步记录程序控制流,记录时的开销仅为8-15%。然而,除了高度实验性的仅二进制覆盖解决方案外,将IPT用作模糊测试覆盖机制并不实用,因为源代码插桩通常能提供更好的性能。Honeybee解决了这一限制,使IPT捕获速度显著提升,分析速度提高数百倍。因此,我们现在可以实现覆盖引导的模糊测试——即使在没有源代码的情况下——其性能与源码级覆盖插桩相当,有时甚至更快。在这里,我将描述Honeybee的开发过程及其设计概述。

它是如何开始的……

IPT是Intel特有的处理器功能,可用于记录任何进程的完整控制流历史,每次记录数分钟,且性能损失极小。IPT极大地改进了Intel旧的硬件追踪系统(如分支追踪存储),后者的性能损失可能超过100%。更棒的是,IPT支持按特定进程或虚拟地址范围进行粒度选择追踪目标。

像IPT这样的硬件机制对安全研究人员特别有吸引力,因为它可以为闭源、未修改的目标二进制文件提供代码覆盖信息。一个较少被认识到的事实是,IPT不仅比任何现有的黑盒方法(如QEMU、中断或二进制提升)快得多,而且应该比插入源码级插桩更快。

覆盖插桩通过频繁、随机地向大型覆盖缓冲区写入,导致各种CPU缓存抖动,从而抑制运行时性能。源码级插桩还抑制了许多重要的编译时优化,如自动向量化。此外,由于大多数程序的多线程特性,插桩代码需要原子操作位图,这显著限制了流水线吞吐量。

IPT应适用于闭源和开源软件,覆盖生成策略无需改变,且仅产生8-15%的性能损失。告诉你,这简直令人惊叹!

进展如何……

不幸的是,8-15%的性能损失并不能说明全部问题。虽然IPT的捕获开销低,但分析开销并不低。为了在商用硬件上捕获长追踪,IPT使用各种技术来最小化每条追踪存储的数据量。一种技术是只记录无法从底层二进制静态分析中轻易获得控制流信息,如采用/未采用的分支和间接分支目标。虽然这种优化假设IPT解码器可以访问底层程序二进制文件,但这种假设通常是正确的。(例如,参见图1。)

IPT是一种非常密集的二进制格式。为了展示存储的信息,我在图1中将其转换为更可读的格式。数据包类型在左列,数据包有效载荷在右列。

示例IPT追踪,展示IPT如何最小化程序执行期间存储的数据。

  1. 追踪开始于程序在0x7ffff7f427ef处执行。
  2. 程序遇到条件分支并接受它。(第2行的第一个!。)
  3. 程序遇到两个条件分支但未接受它们。(第2行的. .。)
  4. 程序遇到条件分支但未接受它。(第2行的最后一个!。)
  5. 程序遇到条件分支并接受它。(第4行。)
  6. 程序遇到间接分支,此时它跳转到最后一条指令指针,低两位字节被替换为0x3301。
  7. 程序遇到条件分支并接受它。
  8. 程序继续执行,没有其他条件/间接分支,直到指令指针的最后四个字节为0xf7c189a0,此时追踪停止,因为程序退出或开始执行另一段不匹配过滤器的代码。

尽管提供了所有追踪数据,但仍有许多信息被省略。追踪提供其起始虚拟地址、最终的条件分支以及它们是否被采用。然而,无条件和无可争议的控制流转移(即call和jmp指令)和条件分支目标未被提供。这减少了追踪大小,因为:1)非分支代码从不记录;2)条件分支用单个位表示;3)间接分支仅通过指令指针的变化表示。

那么,如何从这些部分数据中重建真实控制流?IPT解码器可以将追踪数据与底层二进制文件配对,并从追踪起始地址“遍历”二进制文件。当解码器遇到无法轻易确定的控制流转移(如条件分支)时,它会咨询追踪。追踪中的数据指示哪些分支被采用/未采用以及间接控制流转移的结果。通过遍历二进制文件和追踪直到追踪结束,解码器可以重建完整流程。

但这就是IPT的陷阱:虽然捕获速度快,但遍历代码显然不快,因为解码器必须在每个解码步骤反汇编和分析多个x86-64指令。虽然反汇编和分析的开销对调试和分析场景不是问题,但它严重阻碍了模糊测试的吞吐量。不幸的是,这种昂贵的分析基本上无法避免,因为不分析原始程序就无法解码追踪。

但是……这就结束了吗?IPT模糊测试的美好承诺只是……一个梦?一个纯粹的幻觉?告诉我不是这样!

让IPT更快!

在分析Intel的参考IPT解码器libipt时,我注意到超过85%的CPU时间花在追踪分析期间的指令解码上。这并不奇怪,因为IPT数据必须通过遍历二进制文件寻找控制流转移来解码。然而,指令解码花费的大量时间实际上是个好消息。

为什么?模糊测试器需要对同一二进制文件解码大量追踪。对单个二进制文件的单条追踪持续分析指令可能是合理的,但对同一二进制文件的相同指令重复分析数百万次是极其浪费的。如果你的“嘿,我们可能应该使用缓存”感觉在刺痛,你完全正确!当然,指令解码缓存的重要性并不是一个新发现。

一个声称是最快IPT解码器的开源解码器(稍后会详细说明)名为libxdc,试图使用快速运行时缓存解决这个问题。使用运行时缓存和其他性能编程技术,libxdc比Intel的参考解码器快10到40倍,这表明缓存非常重要。

但我认为我可以做得更好。我对libxdc的批评是,其动态指令缓存以两种方式引入了不必要且昂贵的开销。首先,动态缓存通常有昂贵的查找,因为它需要计算目标的哈希并确保缓存实际上是命中的。这在整个算法最热的部分引入了更多开销和复杂性,不容忽视。

其次,更糟糕的是,动态缓存通常期望填充和驱逐旧结果。即使是最好的缓存驱逐策略也会导致未来的工作:任何被驱逐的指令最终都需要重新解码,因为模糊测试器从不只针对代码一次。这会在每次缓存驱逐时产生重复工作和解码器性能损失。

我的想法是引入一个静态的、提前生成的缓存,保存IPT解码可能需要的所有数据。缓存可以在多个线程之间共享而无惩罚,并且可以在没有昂贵哈希或锁的情况下访问。通过完全消除二进制分析和缓存访问开销,我可以比libxdc显著更快地解码追踪,因为我的解码器只需做更少的工作。

Honeybee架构

遵循Honeybee的主题,我将这些静态的、提前生成的缓存命名为“hives”,因为它们需要工作来创建但只需制作一次。为了制作hives,我创建了hive_generator,它消耗ELF可执行文件并捕获解码追踪可能需要的信息。hive_generator搜索所有控制流指令,并生成所有基本块和代码执行可以继续的位置的极其紧凑的编码。这个新设计有两个重要特性值得讨论。(完整细节可在Github上找到。)

首先,这种编码对数据缓存友好,因为不仅块的大小是缓存行的大小,编码块还按原始二进制的相同顺序存储,这是一个小而重要的细节。这意味着Honeybee的解码器可以充分利用原始二进制的缓存局部性优化,因为编译器通常将相关的基本块放在彼此附近。这在动态缓存(如libxdc)中通常不可能,因为缓存的哈希函数设计上会将相邻块发送到随机位置。这对性能有害,因为它会从CPU的数据缓存中驱逐有意义的数据。

另一个重要特性是块以位友好的格式编码,因此Honeybee可以专门使用高吞吐量ALU操作处理压缩块。这种设计使几个关键操作完全无分支——如确定块是以直接、间接还是条件分支结束。结合高吞吐量ALU操作,避免了许多昂贵的分支误预测和流水线清除。

这些变化似乎相对微不足道,但我希望它们能结合当前最先进的技术libxdc,带来可观的性能提升。

Honeybee解码器基准测试

为了比较Honeybee解码器与Intel参考解码器的性能,我运行了从几十KB到1.25 GB的追踪,二进制文件大小从100 KB到45 MB。测试进行了25次,我验证了两个解码器对相同追踪文件遍历了相同的控制流路径。

比较Honeybee的解码速度与libipt。

这些测试显示了有希望的结果(图3)。在大型程序如clang上,Honeybee比Intel的参考解码器和libxdc快一个数量级(在一种情况下快两个数量级)。

作为背景,此测试套件中最大的追踪“honey_mirror_1/clang_huge.pt”为1.25GB,源自一个复杂的静态分析程序的追踪,该程序反汇编了整个35MB的clang二进制文件。

Honeybee仅需3.5秒完成Intel参考解码器两分半钟的工作,这是44倍的改进!这是追踪解码时离开与等待时喝一口水之间的区别。

这种差异在小追踪上更加明显,这些小追踪更类似于模糊测试负载,如“html_fast_parse/6_txt.pt”。在这种情况下,Honeybee仅需6.6微秒完成Intel参考解码器451微秒的工作。一个数量级的改进!

将Honeybee与honggfuzz集成

现在将这种新的覆盖机制实际集成到模糊测试器中。我选择了Google的honggfuzz,因为它是模块化的,而且值得注意的是,它实际上已经有了另一个使用Intel参考解码器的基于IPT的覆盖版本,该版本速度慢且部分损坏。我的计划是简单地移除Intel的解码器,将Honeybee固定到位,并获得美妙的加速。然而,这比我想象的更复杂。

挑战在于Linux通常如何收集IPT数据,这应该是相当简单的,因为主线内核实际上在perf中内置了对IPT的支持。但我发现,Honeybee需要的复杂和积极的过滤机制来清理IPT数据,暴露了perf中的稳定性和性能问题。

这有问题。不仅perf一开始就不太快,而且极不稳定。Honeybee使用的复杂配置触发了perf中的严重错误,可能导致CPU配置错误,需要完全系统重启才能从锁定中恢复。可以理解,这两个问题最终使perf无法用于捕获任何Honeybee相关模糊测试任务的IPT数据。

但是,正如我母亲所说,如果你想做好某事,有时你只需要自己动手。追随她的脚步,我写了一个名为“honey_driver”的小型内核模块用于IPT,专门为模糊测试优化。虽然这个新内核模块 certainly 比perf功能少,且可能存在安全风险,但honey_driver非常快,并使用户空间客户端能够快速重新配置追踪和分析结果,开销很小。

因此,通过这一小部分自定义代码,honggfuzz准备使用来自Honeybee的IPT数据!

模糊测试器基准测试

模糊测试器性能测量是复杂的,因此有更多可靠和确定的性能测量方法。作为一个粗略的基准,我使用四种不同的覆盖策略持续模糊测试一个小型HTML解析器。然后,我允许honggfuzz使用所选覆盖技术模糊测试二进制文件,然后记录测试期间的平均执行次数。

比较Honeybee与源码级插桩。

实验中的第一个竞争者没有任何覆盖。我将其视为基线,因为它基本上是通过向测试程序提供随机输入时honggfuzz可以运行的最快速度。在此配置中,honggfuzz平均每秒达到239K次执行。在我的系统上下文中,这相当快,但仍然 certainly 受模糊测试目标的CPU性能限制。

接下来,我测试了honggfuzz的源码级软件插桩,通过使用插桩编译器编译我的目标,没有启用其他功能。这导致平均每秒98K次执行,或与无覆盖基线相比效率下降41%,这是模糊测试时普遍接受和预期的惩罚,由于错过编译器优化、许多函数调用、昂贵的锁定以及由于 essentially 随机写入覆盖位图导致的缓存抖动。

在软件插桩之后,我们进入更有趣的覆盖技术。如前所述,honggfuzz支持使用libipt进行分析和使用未过滤的perf数据进行IPT捕获的处理器追踪。然而,honggfuzz现有的IPT支持不生成完整甚至可能正确的覆盖信息,因为honggfuzz只从追踪中提取间接分支IP,并完全忽略任何条件分支。此外,由于使用perf时没有采用过滤,honggfuzz为进程中的每一段代码生成覆盖,包括无趣的库如libc。这导致位图污染。

即使有这些捷径,honggfuzz现有的IPT只能平均达到每秒1.1K次执行(大约理论最大值的0.5%)。由于不准确的覆盖数据,无法与软件插桩进行真实比较,因为可能它更早找到了更困难的路径。然而,现实地,差距如此巨大,给定先前建立的perf和libipt的性能问题,这样的问题不太可能占大部分开销。

最后,我们有Honeybee及其自定义解码器和捕获系统。与现有的honggfuzz实现不同,Honeybee解码整个追踪,因此能够生成完整、正确的基本块和边缘覆盖信息。Honeybee平均达到每秒171K次执行,与基线相比仅性能下降28%。

这不应令人震惊,因为IPT只有8-15%的记录时间开销。这留下基线总执行时间的14-21%来处理IPT数据并生成覆盖。鉴于Honeybee解码器的惊人性能及其快速解码追踪的能力,完全有理由假设Honeybee的数据处理总开销可能加起来达到29%的性能损失。

我分析了Honeybee的覆盖,确认它操作正常并正确处理所有数据。因此,我很高兴地说,Honeybee(至少在这种情况下)能够比传统软件插桩方法更快、更高效地模糊测试闭源和开源软件!

下一步是什么?

虽然声称推翻行业标准模糊测试方法非常令人兴奋,但这些方法尚未在大规模或跨大量模糊测试目标上进行严格测试或验证。然而,我可以证明,Honeybee在模糊测试许多不同的大型开源项目(如libpcap、libpng和libjpegturbo)时,要么更快,要么至少能够与软件插桩相媲美。

如果这些模式更普遍适用,这可能意味着需要执行源码级模糊测试的人将获得巨大加速。但更令人兴奋的是,这对于需要执行黑盒模糊测试并一直依赖慢速和不可靠工具(如QEMU插桩、分支追踪存储(BTS)或二进制提升)的人来说,是一个绝对美妙的加速,因为它意味着他们可以以等于或大于有源码的速度进行模糊测试,而无需做出任何严重妥协。然而,即使在模糊测试之外,Honeybee仍然是一个经过验证且极其快速的IPT解码器。这种高性能解码在模糊测试之外也有用,因为它支持许多新颖应用,从实时控制流完整性实施到更高级的调试器和性能分析工具。

Honeybee是一个非常年轻的项目,仍在GitHub上的家中积极开发。如果你对IPT、模糊测试或任何组合感兴趣,请随时通过Twitter(@ezhes_)或电子邮件(allison.husain@berkeley.edu)与我联系!

致谢

如前所述,IPT吸引了许多研究人员的注意,因此,自然,许多人尝试将其用于模糊测试。在开始我自己的项目之前,我研究了其他人的研究,从他们的工作中学习,并看看我可以在哪里提供改进。我首先要感谢PTrix(PTrix: Efficient Hardware-Assisted Fuzzing for COTS Binary)和PTfuzz(PTfuzz: Guided Fuzzing With Processor Trace Feedback)的作者,因为他们提供了关于如何围绕IPT数据构建模糊测试器的重要见解。此外,我要感谢libxdc背后的团队,因为他们快速的IPT数据包解码器构成了Honeybee中数据包解码器的基础。最后,我要大大感谢Trail of Bits的团队,特别是我的导师Artem Dinaburg和Peter Goodman,感谢他们在这个项目中的支持,并让我作为实习生度过这个冬天!

如果你喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News

页面内容
它是如何开始的……
进展如何……
让IPT更快!
Honeybee解码器基准测试
模糊测试器基准测试
下一步是什么?
致谢
近期文章
非传统创新者奖学金
劫持你的PajaMAS中的多代理系统
我们构建了MCP一直需要的安全层
利用废弃硬件中的零日漏洞
Inside EthCC[8]:成为智能合约审计员
© 2025 Trail of Bits.
使用Hugo和Mainroad主题生成。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计