地狱怪客

使用二进制差分来发现Windows内核内存泄露漏洞

文章来源:图文:https://googleprojectzero.blogspot.com/2017/10/using-binary-diffing-to-discover.html

由Google Project Zero的Mateusz Jurczyk发表

补丁差异是比较相同代码的两个二进制构建的常见技术 – 一个已知的易受攻击的一个,另一个包含安全修复。它通常用于确定背后含糊不清的公告的技术细节,并确定相关漏洞的根本原因,攻击向量和潜在变体。该方法吸引了大量研究[1] [2] [3]和模具开发[4] [5] [6]多年来,已被证明对于识别所谓的1天错误有用,可以针对采用最新安全补丁的用户缓慢利用。总体而言,补丁后漏洞利用的风险对于可以自由逆向工程化的软件来说是不可避免的,因此被认为是生态系统的一个自然部分。

类似地,如果两个或多个版本的单个产品共享相同的核心代码并且在市场上共存,而是被供应商独立地服务,则可以利用二进制差异来发现单个产品的两个或多个版本之间的差异。这种软件的一个例子是Windows操作系统,其目前有三个版本在主动支持下 – Windows 7,8和10 [7]。虽然Windows 7在撰写本文时仍然在台式机市场上占有近50%的份额[8]微软公司以引入许多结构性安全性改进而闻名,甚至有时甚至是普通错误修复仅适用于最新的Windows平台。这为旧系统的用户创造了一种虚假的安全感,并使其易受到软件缺陷的影响,只能通过在不同版本的Windows中发现相应代码中的微妙更改来检测软件缺陷。

在这篇博文中,我们将展示如何有效地使用一种非常简单的二进制形式来查找0天未初始化的内核内存泄露到用户模式程序的实例。这种类型的错误可能是本地特权提升漏洞链(例如绕过内核ASLR)中的有用链接,或者只是简单地暴露存储在内核地址空间中的敏感数据。如果你不熟悉bug类,我们建议您先阅读[9],检查今年REcon和Black Hat USA会议上提供Bochspwn Reloaded 话题的幻灯片

追逐memset调用

大多数内核信息披露是由于在将它们复制到用户模式之前未初始化大部分存储区域的原因引起的; 无论是结构,工会,阵列还是这些结构的某种组合。这通常意味着内核提供了一个具有比相关信息更多的输出数据的环3程序,原因如下:编译器插入的填充孔,未使用的结构/联合字段,用于可变参数的大型固定大小的数组,长度内容等。最后,通过切换到较小的缓冲区,这些错误很少被修复 – 通常原始行为被保留,通过添加一个额外的memset函数调用,它预先初始化输出内存区域,因此它不不包含任何剩余的堆栈/堆数据。这使得这样的补丁在逆向工程中很容易识别。

在Project Zero bug tracker(Windows Kernel pool memory in win32k!NtGdiGetGlyphOutline Bochspwn 发现)中提交问题#1267并执行一些粗略分析时,我意识到该错误仅存在于Windows 7和8中,由Microsoft在Windows 10内部修复。下图显示了易受攻击和固定形式的代码之间的明显差异,由Hex-Rays插件反编译并由Diaphora分解:

 

图1.在Windows 7和10中执行win32k!NtGdiGetGlyphOutline的一个关键区别

考虑到Windows 10中的补丁有多明显(在顶级系统调用处理程序中是一个全新的memset调用),我怀疑可能还有其他类似的问题潜伏在较早的内核中,这些较早的内核已被Microsoft在最近的默认情况下静默修复。为了验证这一点,我决定比较Windows 7和10之间的所有顶级系统调用处理程序(即由核心内核和图形子系统实现的Nt前缀开始的函数)之间的memset调用数,之后在Windows 8.1之间由于原则上这是一个非常简单的分析,可以使用一个简单的方法来获得足够的结果,这就是为什么我决定对IDA Pro反汇编程序生成的代码列表进行分歧。

当这样做时,我很快发现内核中发现的每个内存归零操作都是以三种方式之一编译的:通过直接调用memset函数,使用rep stosd x86指令实现的内联形式,或者是展开的一系列mov x86说明:

图2.在nt!NtCreateJobObject(Windows 7)中重新设置内存的直接memset函数调用

图3.用于在nt!NtRequestPort(Windows 7)中重置内存的内联memset代码

图4.用于在win32k中重置内存的一系列mov指令!NtUserRealInternalGetMessage(Windows 8.1)

两个最常见的情况(memset调用和rep stosd)都被Hex-Rays反编译器反编译memset()定期调用

图5和6.常规memset调用与Hex-Rays视图中的内联rep movsd结构无法区分

不幸的是,具有零输出寄存器的mov序列作为源操作数不能被Hex-Rays识别为memset,但是这种事件的数量相对较低,因此可以忽略,直到我们手动处理任何结果这个过程中的假阳性。最后,我们决定使用反编译的.c文件执行diffing,而不是定期组装,只是为了使我们的生活更容易一些。

我们遵循的完整的最终结果列表如下所示。我们重复了两次,首先是Windows 7/10,然后是Windows 8.1 / 10:

  1. 从Windows 7和8.1将ntkrnlpa.exe和win32k.sys解压缩到与.c对应的Hex-Rays,并从Windows 10中执行ntoskrnl.exe,tm.sys,win32kbase.sys和win32kfull.sys。
  2. 提取一个包含memset引用的内核函数列表(也考虑其数量),并按字母顺序排列。
  3. 对两个列表执行常规文本差异,并选择了在Windows 10上有更多memset参考的函数。
  4. 根据旧的内核(7或8.1,再次从IDA Pro提取)中的函数列表,筛选出上一步的输出,以确保我们不包括仅在最新系统中引入的例程。

在数量上,我们得出以下结果:

ntoskrnl函数 ntoskrnl系统调用处理程序 win32k功能 win32k系统调用处理程序
Windows 7对10 153 8 89 16
Windows 8.1对10 127 67 11

表1.相对于以前的系统版本,Windows 10中具有新memset用法的旧功能的数量

相当直觉的是,Windows 7/10比较产生的差异比Windows 8.1 / 10更差,因为系统逐渐演变成从一个版本到下一个版本。同样有趣的是,图形子系统通常检测到的变化较少,但比系统调用处理程序中的核心内核更多。一旦我们知道候选人,我们会详细的对每个人进行了详细的调查,发现win32k中的两个新的漏洞!NtGdiGetFontResourceInfoInternalW和win32k!NtGdiEngCreatePalette系统服务。他们都在星期二的九月补丁中解决,由于它们有一些独特的特征,我们将在后面的章节中讨论它们。

win32k!NtGdiGetFontResourceInfoInternalW(CVE-2017-8684)

消除该bug存在的不一致的memset如下:

图8.在Win32k中添加的新memset在Windows 10中添加NtGdiGetFontResourceInfoInternalW

这是一个大约0x5c(92)字节的基于堆栈的内核内存泄漏。该功能的结构遵循Windows中使用的常见优化方案,其中位于堆栈上的本地缓冲区用于短系统调用输出,而池分配器仅适用于较大的系统调用输出。伪代码的相关代码段如下所示:

图9.在系统调用处理程序中找到的优化内存使用情况

有趣的是,即使在例程的易受攻击形式中,内存泄露只有在第一(栈)分支被采取时才可能,因此只能用于高达0x5c字节的请求缓冲区大小。这是因为动态PALLOCMEM池分配器在将请求的内存返回给调用者之前将其清零。

图10. PALLOCMEM总是重置分配的内存

此外,这个问题也是一个很好的例子,说明与用户模式相互作用的另外一个特殊行为可能会导致安全漏洞的引入(见Bochspwn Reloaded deck的幻灯片32-33 )。故障代码模式如下:

  1. 如上所述,根据用户指定的大小分配临时输出缓冲区(在这种情况下称为a4 )。
  2. 通过调用内部win32k!GetFontResourceInfoInternalW函数将请求的信息写入内核缓冲区。
  3. 将整个临时缓冲区的内容写回ring-3,无论Win32k实际填写多少数据!GetFontResourceInfoInternalW。

在这里,脆弱的win32k!NtGdiGetFontResourceInfoInternalW处理程序实际上“知道”有意义的数据的长度(甚至通过第5 系统调用参数传回给用户模式调用者),但仍然决定复制所请求的全部内存量客户端,即使完全不需要系统调用的正确功能:

图11.有v10 输出字节,但该函数复制完整的a4 缓冲区大小。

缺少缓冲区预初始化和允许复制冗余字节的组合是什么使得这是一个可以利用的安全漏洞。在概念验证程序中,我们使用了一个未记录的信息类5,它仅写入输出缓冲区的前四个字节,剩下的88个未初始化,并准备好向攻击者披露。

win32k!NtGdiEngCreatePalette(CVE-2017-8685)

在这种情况下,在Windows 8中,通过将以下memset引入系统调用处理程序,同时仍然将Windows 7暴露出来,此漏洞得到修复:

在Windows 8中,在win32k!NtGdiEngCreatePalette中添加了一个新的memset

所讨论的系统调用负责创建由N个4字节颜色条目组成的内核GDI调色板对象,用于用户控制的N.再次,实现使用存储器使用优化 – 如果N小于或等于256 (总共1024个字节),这些项目使用win32k!bSafeReadBits从用户模式读取到内核堆栈缓冲区; 否则,它们只是通过调用win32k!bSecureBits锁定在ring-3内存中。您可以猜到,应用了额外memset的内存区域是用于临时存储用户定义的RGB颜色列表的本地缓冲区,并且稍后传递给win32k!EngCreatePalette以实际创建调色板对象。问题是,我们如何使缓冲区保持未初始化,但仍然通过创建非空调色板?答案在于win32k的实现!

图13. win32k!bSafeReadBits的功能体

正如您在上面的反编译列表中可以看到的,如果源或目标指针为NULL,则该函数将成功完成,而不执行任何实际工作。这里,源地址与系统调用的3个直接来自第三的说法,在不经过任何卫生处理。这意味着我们可以使系统调用者认为它已经成功地从用户模式捕获了多达256个元素的数组,而实际上堆栈缓冲区根本就不被写入。这是通过我们的概念验证程序中的以下系统调用调用来实现的

HPALETTE hpal =(HPALETTE)SystemCall32(__ NR_NtGdiEngCreatePalette,PAL_INDEXED,256NULL ,0.0f,0.0f,0.0f);

一旦syscall返回,我们将收到一个内部存储泄漏堆栈内存的调色板的句柄。为了将其读回我们的程序,需要再次调用GetPaletteEntries API。为了重申bug的严重性,它的开发允许攻击者披露整个1 kB的未初始化的内核堆栈内存,这是一个非常强大的原始文件。

除了内存公开本身之外,在附近的代码区域可以观察到其他有趣的怪癖。如果您仔细查看Windows 8.1和10中的win32k!NtGdiEngCreatePalette的代码,您将发现它们之间有趣的差异:在两种情况下,堆栈数组都将完全重置,但是以不同的方式实现。在Windows 8.1中,“手动”功能将第一个DWORD设置为0,然后在剩余的0x3FC字节上调用memset(),而Windows 10只是显示整个0x400字节区域的mems,其原因还不清楚,甚至尽管最终结果是一样的,但差异引起了这样的想法,即不仅可以在Windows版本之间比较memset调用的存在,还可以将这些调用的大小操作数进行比较。

图14.用于在Windows 8.1和10上清除256项数组的不同代码结构

在最后一个相关的注释中,win32k!NtGdiEngCreatePalette系统调用对于内核使用期间的堆栈喷涂也可能是非常有用的,因为它允许程序轻松地将1024个受控字节写入堆栈的连续区域。当缓冲区大小小于例如nt!NtMapUserPhysicalPages所提供的缓冲区大小时,缓冲区本身就相对于顶级系统调用处理程序的堆栈帧的偏移量较高,这在某些情况下可能会发生重大变化。

结论

这篇博客文章的目的是为了说明一个产品并发支持的分支机构之间的安全相关差异可能被恶意行为者用来确定重要的缺点,或者仅仅是在更新版本的软件中的常规错误。不仅让一些客户暴露在攻击之下,而且还明显地揭示了攻击目标是什么,直接针对用户的安全。对于具有明显修复的bug类,例如内核内存泄露和添加的memset调用,这一点尤其如此。这篇文章中讨论的“二进制差异”过程实际上是伪代码级差异,并不需要很少的低级专业知识或操作系统内部知识。非高级攻击者可以很容易地使用这些漏洞(CVE-2017-8680,CVE-2017-8684,CVE-2017-8685),很少的努力。我们希望这些是通过差异让研究人员可以访问这种“低挂水果”的一些例子,我们鼓励软件供应商通过在所有受支持的软件版本中始终如一地应用安全性改进来确保这一点。

参考

  1. http://www.blackhat.com/presentations/bh-usa-09/OH/BHUSA09-Oh-DiffingBinaries-SLIDES.pdf
  2. https://beistlab.files.wordpress.com/2012/10/isec_2012_beist_slides.pdf
  3. https://www.rsaconference.com/writable/presentations/file_upload/ht-t10-bruh_-do-you-even-diff-diffing-microsoft-patches-to-find-vulnerabilities.pdf
  4. https://www.zynamics.com/bindiff.html
  5. http://www.darungrim.org/
  6. https://github.com/joxeankoret/diaphora
  7. https://support.microsoft.com/en-us/help/13853/windows-lifecycle-fact-sheet
  8. http://gs.statcounter.com/os-version-market-share/windows/desktop/worldwide
  9. http://j00ru.vexillium.org/slides/2017/recon.pdf
码字很辛苦,转载请注明来自人生在世《使用二进制差分来发现Windows内核内存泄露漏洞》

评论