Windows 8.1内核利用—CVE-2014-4113漏洞分析

华盟原创文章投稿奖励计划

4

1.情况简介:

2014年10月14日,CrowdStrike和FireEye两家IT公司各自发布了一篇博文,在其中都不约而同地介绍了一个基于Windows系统的新型内核权限提升漏洞。CrowdStrike公司在文章中提到:他们是在追踪一个名叫Hurricane Panda(飓风熊猫)的黑客组织的过程中,发现并证实该漏洞的。该漏洞在Internet上至少已经活跃了5个月的时间了。

据报道,这个漏洞是由CrowdStrike和FireEye同时发现并提交给微软公司(Microsoft)的,随后,微软就将其命名为了:MS14-058,同时提供了修复补丁。在漏洞提交后不久,很多安全研究人员也在各自的博文中提到了这个漏洞,并介绍了其中的一些细节。在撰写本文时,我曾阅读了多篇关于介绍CVE-2014-4113漏洞的文章,了解了多位作者的分析思路。在其中的一篇文章中,作者从基于二进制与Metasploit渗透框架结合的角度出发,详细分析了该漏洞。这个分析思路适用于除Windows 8和Windows 8.1之外的,所有Windows 32位和64位操作系统。

微软公司表示,CVE-2014-4113漏洞影响了多个版本的Windows操作系统,其中就包括Windows 8.1。有趣的是,FireEye公司在文章中提到:在Windows 8、Windows Server 2012以及之后的版本中,却并不存在该漏洞。这个漏洞的利用程序是黑客组织Hurricane Panda率先发布的,并且该程序只对Windows 7和Windows 8系统有效。

因此,我非常好奇的是:这个漏洞是如何在多个版本的Windows系统中得到体现并加以利用的。在下文中,我将向读者介绍我对这个漏洞的分析过程,以及在Windows 8和Windows 8.1系统上实现利用的步骤。

 2. 漏洞详情:

我所使用的分析测试环境是Windows 7(64位)系统。在分析过程中,我还会给读者分享一些有价值的信息。这些经过分析测试的shellcode代码具有MD5校验能力。

由于之前的很多文章都详细地介绍过该漏洞的整体情况,因而,在这里,我更多是关注该漏洞的一些具体细节。CVE-2014-4113漏洞存在的原因是:在Win 32K.sys驱动程序中,代码出现缺少返回值校验的情况。Win 32K.sys驱动程序负责管理Windows系统的内核模式,处理Windows系统的资源管理以及提供图形编程驱动接口以及处理内核的其他事务。

利用user 32!模块中的TrackPopupMenu函数,可以引发一个用户模式下的安全漏洞。在内核中负责处理API的函数是:Win 32K!xxxHandleMenuMessages,该函数能够调用Win 32K!xxxMNFindWindowsFromPoint API函数。Win 32K!xxxMNFindWindowsFromPoint API函数的返回值是一个win32k!tagWND结构的指针。然而,在调用失败时,该函数会返回错误代码-1和-5。而调用程序在检查返回值的时候,只检查了-1,而没有检查-5,从而出现了执行出错。同时由于系统本身也没有发现该错误,进而使得该函数将继续默认-5为正确值,同时继续提供一个有效的win32k!tagWND结构的指针;实际上,该函数一直使用的都是错误代码-5(0xfffffffb)。这个代码在执行时,会将-5作为参数传递给Win 32K!xxxSendMessage函数。而该函数正好是Win32K!xxxSendMessageTimeout的一个轻量级封装函数。(在Windows 8.1上名叫:win32k!xxxSendTransformableMessageTimeout)。

该漏洞通用的利用规则是:在用户模式地址为0xfffffffb的地方,用ZwAllocateVirtualMemoryAPI函数分配内存空间,并在这个地方存储一个win32k!tagWND结构指针。在内核中,以用户模式访问该结构时,便会引发该漏洞。而win32k!tagWND结构指针也已经准备就绪,之后便会执行win32k!tagWND结构里的函数。该函数的指针指向了一个简单的内核权限shellcode,这个shellcode覆盖了原始的函数返回地址。该函数就是一个EPROCESS结构函数,具有在系统权限下运行的能力。

3. Windows 8.1 内核的利用

这个漏洞的公开利用程序并不直接适用于Windows 8系统,这是因为SMEP(管理模式执行保护机制)将会保护用户模式下的shellcode的执行过程。这个执行过程其实是在内核的上下文中进行的。当CPU在win32k!xxxSendTransformableMessageTimeout函数中执行指令时,这种被错误使用的shellcode在Windows 8系统中依然存在。而Windows 8.1则完全替代了那段代码,同时更加注重对于数组边界的检查。

 

http://p6.qhimg.com/t013053073bf8ef0b10.png
  因此,在Windows 8.1中,系统就不会在程序流中继续使用调用指令。然而,正如我们看到的那样,在下一个区段中,使用一个经过精心设计的win32k!tagWND结构函数能够成功地达到利用该漏洞的目的。

3.1 设计win32k!tagWND结构

为了能在Windows 8.1上利用这个漏洞,我在用户模式下,构造了一个假的win32k!tagWND结构,并为其分配了内存空间。当这个漏洞被引发时,win32k!xxxSendTransformableMessageTimeout函数会率先读取一个64位的数据。该数据是存储在一个偏移地址为0x10的空间内,而win32k!tagWND结构恰好也在该内存里。程序会将其与win32k!gptiCurrent指针进行比较。如果我们在这个地方提供了一个无效数据,那么程序的运行就会出错。接下来,程序就会在偏移地址为0的地方,读取一个字节的数据,并将其作为一个内存地址的索引项。在该索引项指向的内存空间中存储着一段数据,该数据会被程序用来与数据0x01进行比对。

 

http://p2.qhimg.com/t0101453318088d744a.png
  如果我们将win32k!tagWND结构开始的两个字节设为0,那么之后对于0x1的检查将会失败,同时代码的执行将会在调用win32k!xxxInterSendMessageEx函数时结束,前提是我们已经将这个指针作为了第一个要传给win32k!tagWND结构的参数。

这时,win32k!xxxInterSendMessageEx函数将会重新读取存储在偏移地址为0x10处的指针。该指针位于win32k!tagWND结构中;同时,它还会尝试解引该指针,之后重新读取其他指针。0x10处的指针的作用是读取一个存储在0x170处的数值,系统会将它与ntoskml!PsGetCurrentProcessWin32Process函数的返回值进行比较。

 

http://p4.qhimg.com/t01f63fb54838e7ad63.png
  我们之所以要构造win32k!tagWND结构函数,是为了能够成功地从用户内存中读取数据0x0。接下来,win32k!xxxInterSendMessageEx函数会从偏移地址为0x2b0处读取一个字节的数据。这个数据的值是任意的,但不能包括0x20。

 

http://p6.qhimg.com/t012e6a63c9b50c6d3a.png
  当以上所有条件都具备时,程序会给win32k!tagWND结构函数传递一个指针参数,接着win32k!xxxInterSendMessageEx函数就会调用win32k!IsWindowsDesktopComposed函数。

 

http://p2.qhimg.com/t01ffb1132df9dc0f35.png
  win32k!xxxInterSendMessageEx函数将会从win32k!tagWND结构中,偏移地址为0x10处读取一个数值。如果读取的数值为0,那么程序的返回值就是0,表示没有解引win32k!tagWND结构中的任何参数。

 

http://p8.qhimg.com/t019d3a798138c284c0.png
  如果这些条件都满足,win32k!xxxInterSendMessageEx函数将会执行下面的代码:

 

http://p4.qhimg.com/t016f4a113532aeb4c6.png
  这段代码在功能上实现了对一个链表的追加操作。具体操作为:尝试着将在RDI寄存器中发现的数据加到链表的表尾。这个数据实质上是一个我们无法直接控制的内核指针。这段代码将率先读取存储在win32k!tagWND结构中,偏移地址为0x60处的链表表头,同时检查表头是否为空。如果表头为空,那么该数据将会被设为链表的新表头。如果表头不为空,那么系统就会遍历整个链表直到发现一个新的链表插入位置为止,之后将数据插入,并将下一个指针的指针域设为空,之后再覆盖原来存储在RDI寄存器中的旧指针。

 

http://p7.qhimg.com/t0194f5b97d721efb16.png
  此段代码为我们提供了一个非常有效的,能够让我们在内核空间内任意加入8个字节的数据空间。尽管我们不能直接编写这个数据值(同时也受到系统的限制),但这样也足以能够让我们对CVE-2014-4113漏洞加以利用了。

 3.2 在内核空间中寻找一个覆盖目标

由于系统允许我们在内核空间中覆盖一定的存储空间,因而我们就在积极寻找一个初始值为0的64位内核空间。当我们需要在该空间内加入一些数据时,系统会提升我们对内核的使用权限。此外,我们还需从用户模式中分配一些地址到该空间中。

Cesar Cerrudo在他的《Easy local Windows Kernel exploitation》一文中提到:我们可以通过使用NtQuerySystemInformation(SystemHandleInformation),这个API函数,来泄露Windows令牌对象的地址。该函数能让我们获得嵌套到SEP_TOKEN_PRIVILEGES结构中的地址。我的想法是:利用某种可控的方式,使用原始令牌来覆盖这个结构,从而向其中添加一个已认证的新权限。相较于采用覆盖一个潜在指针的办法,利用这个函数的好处是:我们不必再担心SMEP的问题。

所以,我们可以从一个标准用户的原始令牌的角度出发,来看待SEP_TOKEN_PRIVILEGES这个结构

 

http://p3.qhimg.com/t01924dc0cbb9a25b2f.png
  在SEP_TOKEN_PRIVILEGES结构中,以上的三个字段是用位掩码表示的。每个权限都是通过单个字节来表示。其中,我们更感兴趣的位掩码是那些已经启用的位掩码字段,那些字段是Windows内核授予的有效特殊权限。

正如我们看到的那样,在这个结构中,没有连续的8个空字节空间来让我们实现覆盖。然而,如果通过取消特权使用的方式,也许就能实现上述的要求了。

首先,我们应找到那段执行原始令牌的代码,修改之,降低该令牌的权限。

 

http://p5.qhimg.com/t014dc3d585a13238d8.png
  运行结果存储于下面的SEP_TOKEN_PRIVILEGES结构中:

 

http://p7.qhimg.com/t01727c66b27145b742.png
  我们可以看到,系统并没有给我们提供8个连续的空字节空间。但是,我们可通过使用AdjustTokenPrivileges这个API函数的DisableAllPrivileges标志位,来禁用已经启用的特殊权限。

 

http://p0.qhimg.com/t015fa099de3a6ed879.png
  在调用AdjustTokenPrivilege函数后,我们可以有效地删除所有的64位字段,并设置字段数据为0。

http://p9.qhimg.com/t015390efe403708eaf.png

通过这种方式,现在我们可以覆盖之前启用的字段了。但是,我们还是不能有效地控制被写入的内核地址。在填写地址的过程中,可能还会开启某些有趣的特权。然而,我们不能使用那样的方法。我们关于内核地址安全性的假设是:在该地址中,有两个非常显著的字节被设置为了0xff。我们选择部分地覆盖这两个字节,以保证能够开启以下特权:

http://p0.qhimg.com/t01f07913e1f2fb027c.png

这其中有很多有趣的特权,比如:SeDebugPrivilege。接下来,我们将会演示这个特权的提示过程。

3.3 整合所有步骤

为了能够成功地利用CVE-2014-4113漏洞,我们首先需要让程序分配一个假的win32k!tagWND结构。分配方法是:在用户模式下,找到偏移地址为0xfffffffb的内核空间,使用ZwAllocateVirtualMemoryAPI函数进行分配。在3.1节中,我们已经讨论过win32k!tagWND结构中的字段设置问题了,在这里就不再赘述。

其次,我们需在启用权限的字段上,创建一个没有任何权限的令牌,并将内核地址传给SEP_TOKEN_PRIVILEGES结构中的令牌。所使用到的API函数是:NtQuerySystemInformation(SystemHandleInformation)。之后,为了让该地址能够指向当前字段的中部,我们需对该地址进行扩展。方法是:以3为步长,进行自增长。扩展完成后,在偏移地址为0x60处存储该地址。以上所有的操作都是在我们精心设计的win32k!tagWND结构中进行的。

在以上步骤完成后,我们就可以开始引发该漏洞了。引发的方法是:使用一个任意的内核指针,覆盖之前设置的那个没有权限的令牌,进而能够有效地启用该字段。

为了使用这个新特权,我们使用ImpersonateLoggedOnUser,这个API函数来模拟受到限制的安全环境。最终,虽然我们只向系统用户进程中加入了一个shellcode,但也能达到像Windows.exe进程调用WriteProcessMemory函数一样的效果。如果以上步骤都能顺利实现,那么系统会弹出如下的一个界面,并向我们提供一个带有系统权限的shell。这样的话,就大功告成了。

 

http://p6.qhimg.com/t013a433c156e0e8bbb.png
  4. 总结

以上出现的结果是有可能实现的。比如:利用一个内核漏洞,就能够让我们实现控制类似于win32k!tagWND这样的大型内核结构。上述测试过程的测试环境是Windows 8.1,但在Windows 8系统中也是可行的。

总的来说,即使系统中有安全保护机制的存在(比如:SMEP),但只要能够控制系统中的内核,仅仅利用一个微小的漏洞,你就能提升用户访问内核的权限,而不再需要使用诸如覆盖函数指针或执行shellcode等类似方法。

本文原创,作者:congtou,其版权均为华盟网所有。如需转载,请注明出处:https://www.77169.net/html/23200.html

发表评论