fuzz闭源pdf查看器

2019-08-18 约 171 字 预计阅读 1 分钟

声明:本文 【fuzz闭源pdf查看器】 由作者 ret2nullptr 于 2019-08-18 07:01:00 首发 先知社区 曾经 浏览数 33 次

感谢 ret2nullptr 的辛苦付出!

本文为翻译文章,原文链接:https://www.gosecure.net/blog/2019/07/30/fuzzing-closed-source-pdf-viewers

此文章介绍了fuzz闭源pdf查看器时出现的典型问题以及可能的解决方案。因此,它着重于两者:输入最小化程序并未终止

这些方法是作为我的硕士论文的一部分找到并实施的,我在德国达姆施塔特工业大学与Fraunhofer SIT合作撰写了这篇论文。

Context(问题背景)

模糊PDF查看器的核心思想非常简单:选择一个PDF文件,稍微破坏它并检查它是否会使查看器崩溃。

虽然这听起来非常简单,但要正确有效地完成它却非常困难。PDF文件格式是目前使用最多且最重要的格式之一。因此,PDF查看器的安全问题在过去已被广泛利用并不是一个令人惊讶的事情

链接:https://www.exploit-db.com/exploits/34603

并且在2018年又被成功攻破

链接:https://blog.malwarebytes.com/threat-analysis/2018/05/adobe-reader-zero-day-discovered-alongside-windows-vulnerability/

报告给主要PDF查看器的非常多的问题表明仍然有许多的强化工作需要完成,我想为这两者做出贡献:PDF查看器的安全性和模糊测试社区。

对PDF查看器进行fuzz时通常会出现的问题是:

  • Fuzzer可以确定没有发生crash吗?

    PDF查看器从不表示它已经完成了解析并呈现给定的PDF,应用程序将在何时关闭?

  • 应该选择哪些PDF作为突变模板

    所选PDF应尽可能多地涵盖目标代码。如果源代码不可用,如何有效地测量代码覆盖率

问题1:非终止程序

Fuzzee的(正常)终止向Fuzzer发出信号,表示它已完成处理并且没有发生崩溃。这对于Fuzzer很重要,因为它现在可以开始下一次测试迭代。PDF查看器的问题在于它们显然从不自行终止,因此Fuzzers缺少一个度量标准来确定何时可以启动下一个测试

大多数现有的Fuzzers所做的是,他们要么使用硬编码超时,否则如果没有发生崩溃就会杀死应用程序,或者他们不断地轮询目标的CPU周期量假设当某个参数低于阈值时程序可以被终止
不管是哪种方法,超时或者阈值都必须精确设定,但或多或​​少都会猜到:对于Fuzzer来说,这意味着它太早(可能会丢失崩溃)或太迟(浪费时间)杀死应用程序。

方法:使程序中止!

我们的想法是找到查看器的最后一个基本块,当它被赋予有效输入时执行。这里的假设是,只有当查看者完全解析并呈现给定的PDF时,才会执行此基本块。下一个测试开始前,必须以终止程序的方式修补此块。

为了找出程序中哪些基本块已在运行时执行,研究人员利用了一个名为程序跟踪(Program Tracing)的概念。我们的想法是让目标生成有关其执行(跟踪)的附加信息,例如内存使用,采用分支或执行的基本块。

由于目标不是创建这些信息,因此必须向其添加说明。此过程称为程序检测(Program Instrumentation)

在开源环境中,目标程序可以简单地使用其他编译器扩展例如AddressSanitizer(ASAN)进行重新编译,这些扩展将负责在编译时添加检测。显然,这对于闭源PDF查看器来说是不可能的。

幸运的是,令人惊奇的框架DynamoRIO不需要任何源代码来应用此工具,因为它在运行时检测程序(动态二进制检测)

drrun.exe -t drcov -dump_text Program.exe

创建的程序跟踪看起来像这样:

上图为DynamoRIO的输出

可以看出,跟踪显示了哪个模块的基本块已经执行,并且它保留了基本块的顺序,这使得确定最后一个基本块变得相当容易。 因此,要找出目标PDF查看器执行的最后一个基本块,通过向其提供不同但有效的PDF来创建多个跟踪。 然后很明显,在靠近迹线末端的某处通常存在一个共同的基本块,这是必须被检测的块。

不幸的是,只有在程序退出后才会将跟踪写入磁盘,因此必须在此处使用较高超时阈值,当然这是一个硬编码数值。

现在找到了最后一个公共基本块,需要对其进行修补,以便终止程序。 这可以通过覆盖基本块来实现:

Xor eax, eax
push eax
Push Address_Of_ExitProcess
ret

这个问题是它需要9 bytes来表示这些指令。 如果基本块的大小不是9 bytes,则后续指令将被破坏。

为了解决这个问题,可以在PE文件中添加一个新的可执行部分,其中包含上面的指令。 因此,可以通过跳转到新添加的部分来修补基本块:

push SectionAddress
ret

为了修补目标,可以使用框架LIEF,这使得更改给定的PE文件变得相当容易。

译者注:这里推荐一个相关的CTF binary patch教程,其中有一些对lief的使用说明

http://p4nda.top/2018/07/02/patch-in-pwn/#%E4%BF%AE%E6%94%B9%E7%A8%8B%E5%BA%8F-eh-frame%E6%AE%B5

显然,使用断点修补基本块要容易得多,这是一个单字节指令。 许多现有的Fuzzers依赖于程序崩溃会终止的事实,但不能用于PDF查看器。 我们应用的退出检测(exit instrumentation)是检测崩溃更为容易和准确。

该方法是自动化的,并成功用于多个PDF和图像查看器:

  • FoxitReader
  • PDFXChangeViewer
  • XNView

下图给出了补丁在汇编层面上的体现。 请注意,修补后的版本将返回到该新添加的部分。

带终止补丁的基本块

FoxIt Reader的行为

问题2:输入最小化

Fuzzing的成功在很大程度上取决于初始输入集(语料库)。 因此,必须确保语料库尽可能多地覆盖目标代码,因为它显然增加了在其中发现错误的机会。 此外,必须避免语料库中的冗余,以便每个PDF触发目标中的独特行为

对此的常见方法是称为语料库蒸馏(Corpus Distillation)

这样做的核心思想是首先收集大量有效的输入。 然后,对于每个输入,测量基本块代码覆盖,并且如果输入仅触发先前输入已经访问过的基本块,则将其从集合中移除。

corpus = []
inputs = [I1, I2, .... In]
for input in inputs:
   new_blocks = execute(program, input)
   if new_blocks:
      corpus.append(input)

同样,需要创建程序跟踪。 由于源代码不可用,动态二进制检测似乎是测量基本块代码覆盖率的唯一机会。
这里的问题是动态二进制检测似乎会产生不可接受的开销

为了证明这一点,FoxitReader使用AutoExit方法来patch,并且测量直至终止的时间

  1. Vanilla: 1,5 seconds
  2. DynamoRIO: 6,4 seconds

在这里,动态二进制检测会导致近5秒的开销,耗时太高而无法执行有效的语料库蒸馏。

解决方案:自定义调试器

由于动态二进制检测显然太慢而无法执行语料库蒸馏,因此必须找到另一种方法来测量基本的块代码覆盖率。这个想法包含两部分:

  1. 静态检测二进制文件
  2. 创建一个处理检测的自定义调试器

首先,使用断点(单字节指令0xcc)对目标中的每个基本块进行修补。补丁静态应用于磁盘上的二进制文件。如果执行了任何基本块,它将触发断点事件(int3),该事件可由监测调试器获取。调试器获取int3事件,并在两者(磁盘上的二进制文件和原始字节的地址空间中的二进制文件)中均覆盖断点。最后,目标的指令指针递减1并恢复执行。

下图显示了检测的基本块:

由于断点,调试器很容易识别哪些基本块已被执行。

为了评估这种方法的性能,FoxitReader的所有基本块都使用断点进行patch(1778291个基本块)。

在第一次迭代中,FoxitReader花了16秒直到最终终止,这比DynamoRIO慢10秒。
但是由于磁盘上的二进制文件中的断点已经被还原,它们将永远不会再触发int 3事件。

因此,可以假设在第一次迭代之后,大多数断点已经被恢复,因此开销应该是合理的。

  • 第一次迭代:16秒(48323个断点)
  • 第二次迭代:2秒(2212个断点)
  • 第三次及之后:~1.5秒(非常少的断点数)

可以看出,在第一次迭代之后,检测导致最小的开销,但是调试器仍然能够确定任何新访问的基本块。

这种方法在主要产品上进行了测试,并在所有这些方面完美运行:

  • Adobe Acrobat Reader
  • PowerPoint
  • FoxitReader

Fuzzing

通过爬虫在互联网收集了80000个PDF,并将该集合最小化为220个独特的PDF,耗时约1.5天。
使用此最小化设置进行Fuzz的 结果非常好,并且所有崩溃都被推送到数据库中:

显示模糊测试结果

结果

最终,Fuzzer在大约2个月的时间框架内发现了43起独特的崩溃事件,其中三起足以将其报告给Zero Day Initiative

它们被分配了以下ID:

  • ZDI-CAN-7423:Foxit Reader解析越界读导致RCE
  • ZDI-CAN-7353:Foxit Reader解析越界读导致信息泄露
  • ZDI-CAN-7073:Foxit Reader解析越界读导致信息泄露

关键词:[‘安全技术’, ‘二进制安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now