Windows 8.1 内核利用– CVE-2014-41132014 – 10 – 31Moritz Jodeit moritz@目录1.介绍2.漏洞详情3.Windows 8.1 内核利用设计WIN32K!TAGWND结构寻找覆盖目标利用步骤整合4.总结介绍在2014年10月14日,CrowdStrike和FireEye公布了一篇博客文章,这篇文章描述了Windows 上的一个新的0day权限提升漏洞。
据CrowdStrike的文章解释:这个漏洞是在一次追踪一个高级组织的时候被发现并证明的,这个组织名叫HURRICANE PANDA。
这个漏洞在Internet 上至少已经活跃了5个月。
这个漏洞据说是CrowdStrike和FireEye同时发现并提交给微软的,随后,微软就将其命名为:MS14-058,并提供了修复补丁。
不久,binaries在他的博客文章中也提到了这个漏洞,在撰写文章时,这里有几个很好的分析思路,这个思路是基于二进制与Metasploit渗透框架,结果是,这个思路能支持目前的WIN32与WIN64版本的系统,但是除了WIN8与WIN8.1。
根据微软的说明,这个漏洞影响了Windows很多版本,也包括WIN8.1。
更有趣的是,FireEye 的博客文章对比了这个漏洞,WIN8,WIN Server 2012以及以后的版本并没有该漏洞,这个漏洞的利用是HURRICANE PANDA率先公布的,并且这个利用程序只能对WIN7和WIN8有效。
所以,我非常好奇的是:这个漏洞怎么在Windows的大多数版本中得到体现并利用成功。
这篇文章记载了我对这个漏洞的分析与利用,并且在Windows8与Windows8.1上利用成功。
漏洞详情下面的文章是基于Windows7(x64)系统的分析与利用,并且公开了一些有价值的信息。
这些分析过的shellcode具有MD5校验能力。
这个漏洞在其他地方早就被详细的披露过,所以,我们仅仅关注关键的细节。
这个漏洞存在的原因是:在WIN32K.SYS驱动程序中,代码出现缺少返回值校验。
这个驱动程序负责Windows系统的内核模式部分,他负责处理Windows系统的资源管理和提供图形编程驱动接口以及相关的其他事情。
User32!模块的trackpopupmenu这个函数的使用,可以引发一个用户模式的安全漏洞。
负责处理的API函数是:Win32K!xxxhandlemenumessages。
这个函数调用了win32k!xxxMNFindWindowFromPointAPI函数,这个API函数的返回值是一个win32k!tagWND结构的指针。
然而,在调用出现故障的情况之下,这个函数也会返回错误代码-1和-5。
调用程序在检查返回值的时候,只检查了-1,没有检查-5。
由于这种错误,Windows没有捕获到,这个函数继续假设有一个有效的指向win32k!tagWND结构的指针,但是他继续使用的是错误代码-5(0xfffffffb)。
这个代码执行时,传递-5给win32k!xxxSendMessage函数作为参数,这个函数恰恰是win32k!xxxSendMessageTimeout的一个轻量级封装函数(在Windows8.1上名叫:win32k!xxxSendTransformableMessageTimeout)。
这个漏洞通用的利用规则是:在用户模式地址为0xfffffffb的地方使用ZwAllocateVirtualMemoryAPI函数分配内存,并在这个地方存储一个win32k!tagWND结构的指针。
这个漏洞在内核以用户模式访问这个结构时被引发。
这个结构以这种方式早已准备好了,就是执行win32k!tagWND结构里的函数。
这个函数的指针指向了一个简单的内核权限的shellcode,这个shellcode覆盖了原始的函数返回地址,这个函数就是当前的EPROCESS 结构的一个函数,该函数具有在系统权限下运行的能力。
3 Windows8.1内核的利用这个漏洞的公开利用程序不是直接适用于Windows8,这是由于SMEP(管理模式执行预防)将要保护位于用户模式空间的shellcode的执行,这个执行其实是在内核上下文中进行的。
当CPU执行的指令在win32k!xxxSendTransformableMessageTimeout函数中时,这种被错误的使用的shellcode在Windows8系统中依然存在。
Windows8.1完全替代了那段代码,Windows81.更加注重适当的数组边界检查。
IDA PRO分析如下:所以,在Windows8.1中,调用指令已经不会在程序流中再使用。
然而,正如我们看到的那样,在下一个区段,一个精心设计的win32k!tagWND结构能够成功地到达利用这个漏洞的目的。
3.1 设计win32k!tagWND结构为了在Windows8.1上利用这个漏洞,我们的程序在用户模式空间分配了一个假的win32k!tagWND结构。
当这个漏洞被引发的时候,win32k!xxxSendTransformableMessageTimeout函数率先读取一个64位的数据储存在偏移地址为0x10的地方,这个地方恰好是win32k!tagWND结构并且程序开始将其与win32k!gptiCurrent指针进行作比较。
如果我们在这个偏移地址上提供一个无效的数据,那么程序就会走向错误的分支。
接下来,程序就会在偏移地址为0的地方读取一个字的数据并且将他当成一个内存的索引。
这个索引指向的内存空间的数据将拿来与数据0x01进行比对。
IDA PRO分析如下:自Windows8开始,win32k!tagWND结构的符号信息已经不再被微软提供。
由于这个原因,这篇文章通过地址的偏移仅仅是描述了这个结构的关键部分。
IDA PRO分析如下:如果我们将win32k!tagWND这个结构的开始两个字节设置为0,那么这个对于0x1的检查将会失败,并且代码将会在调用win32k!xxxInterSendMessageEx函数时结束,前提是我们将这个指针作为第一个参数传递给win32k!tagWND这个结构。
win32k!xxxInterSendMessageEx这个函数将会重新读取在偏移地址为0x10处的指针,这个指针位于win32k!tagWND这个结构中,并且尝试解引这个指针,然后重新读取其他的指针。
这个新指针被用来读取一个偏移地址在0x170处的值,这个值将会与ntoskrnl!PsGetCurrentProcessWin32Process函数的返回值进行比较。
IDA PRO分析如下:我们准备了win32k!tagWND这个结构以便于双解引会成功并且从我们用户内存中读取数据0x0。
接下来,win32k!xxxInterSendMessageEx这个函数将会从偏移地址0x2b0处读取一个字节数据,当然这个数据的值是一个任意的数值,但不包括0x20。
IDA PRO分析如下:当所有这些条件都满足时,win32k!xxxInterSendMessageEx一个函数就会以调用win32k!IsWindowDesktopComposed这个函数而结束,前提是传递一个指针参数到我们精心设计的win32k!tagWND结构中。
IDA PRO分析如下:这个函数将会从win32k!tagWND结构的偏移地址为0x10处读取一个数值。
如果读取的数值为0,这个函数将会返回0而没有解引任何的win32k!tagWND结构的其他成员。
IDA PRO分析如下:如果这些条件都满足,win32k!xxxInterSendMessageEx这个函数最终将进入以下有趣的代码:这段代码基本上实现了一个链表的追加操作,具体操作是:试图将在RDI寄存器中发现的数据追加到这个链表尾部。
这个被发现的数据是一个我们不能直接控制的内核指针。
这段代码将率先读取存储在win32k!tagWND结构中偏移地址为0x60处的链表头部,并且检查这个地址处的数值是否为空。
在这种情况下,这个数据将被直接存储在win32k!tagWND结构中作为一个新的连表头。
如果在win32k!tagWND这个结构的0x60偏移处已经存储了一个链表头,这段代码将开始遍历这个链表直到发现一个链表的入口,并把下一个指针设置为空指针,然后覆盖RDI这个寄存器中指向内核地址的指针。
图解如下:此段代码为我们提供了一个非常有用的原始的和基本上允许我们在一个内核空间的任意字节覆盖8个连续的空字节空间。
尽管我们不能直接控制这个值的写入并且我们拥有仅仅只能覆盖空字节的附加约束,但是那样已经足以在Windows8.1版本上成功利用该漏洞。
3.2 寻找一个覆盖目标我们能在内核空间中覆盖的空间基本上可以说是没有限制的。
我们正寻找一个被0初始化的64位空间,这个空间,当我们使用一些或多或少的任意的数据进行覆盖时,这个时候允许我们进行内核权限的提升。
此外,我们必须从用户空间泄露一些地址到该位置。
Cesar Cerrudo在他的文章“简单定位Windows内核利用点”中说到:他们可以选择一个简单的技术来成功利用该漏洞。
我们能够从用户空间使用NtQuerySystemInformation(SystemHandleInformation)这个API函数泄露Windows令牌对象的地址。
这个需要我们获得嵌入到SEP_TOKEN_PRIVILEGES结构中的地址。
这个想法是:以某种可控的的方式,使用原始令牌来覆盖这个结构,从而添加一个认证的新权限,这个新权限是允许我们进行提权的。
使用这个技术而不是使用覆盖一个潜在的函数指针的另一个好处是:我们一点都不需要关心SMEP的绕过。
所以,我们站在一个标准用户的原始令牌的角度来看SEP_TOKEN_PRIVILEGES这个结构。
在SEP_TOKEN_PRIVILEGES结构中,这三个字段是用位掩码来表示的。
每个权限都是通过单个位来表示的。
我们更感兴趣的位掩码是那些启用的位掩码字段,那些字段是Windows 内核授予的有效特权。
正如我们看到的那样,这个结构中没有连续的8个空字节供我们覆盖。
然而,从令牌中取消特权可能会恢复几位的空间也许可以实现并满足8位空字节的要求。
我们首先打开我们的进程的一个指向原始令牌的句柄,并且让这个令牌的权限非常小。
这个结果存在于下面这个SEP_TOKEN_PRIVILEGES结构的受限制的令牌中:正如我们看到的那样,这里并没有提供给我们8个连续的空字节空间。
然而,我们可以使用AdjustTokenPrivileges这个API函数的DisableAllPrivileges标志位来禁用所用启用的特权。