简介
早在2015年初Udi Yavo[1]发现一枚影响Windows XP到Windows 10(预览版)的Windows内核漏洞,以下为两篇有关CVE-2015-0057的分析文章,大家可以参考一二:
1.FireEye described some technical details about the Dyre malware that exploited this vulnerability [2]. 2.NCC Group explained technical solutions to achieve reliable exploitation on Windows 8.1; however they did not release any code [3].
概述
这是一个use-after-free内核漏洞,它能获取一个专属的write primitive操作,之后侵染临近的一个对象。这个yields语句可以在内核空间或者用户空间随意写入。
更准确的说,这个use-after-free触发器利用scrollbar对象,然后通过一个新的proplist对象(同样大小)替换freed对象。正是因为use-after-free,新对象的header会被一个OR primitive修改。
win32k !xxxEnableWndSBArrows+0xaa: fffff960`0036cdfe 0903 or dword ptr [rbx],eax
该漏洞导致对象属性的最大值增加,这样你就可以将数据push到堆中。通过SetProp(HWND hWnd, LPCTSTR lpString, HANDLE hData)增加一个新属性,可以覆盖下一个对象。
在下文中,我们将使用"SetProp(hWnd, V1, V2)"来代替表示"SetProp(HWND hWnd, LPCTSTR lpString, HANDLE hData)"
在Windows 8.1 (64-bit)上利用
根据FireEye的报告,有恶意软件使用了上面我们讨论的技术侵染一个临近的菜单对象。事实上,其中有一张截图就显示了tagMENU.rgItems和tagMENU.cItems的值被覆盖。
typedef struct _MENU { … DWORD cItems // number of items contained by the items array … PITEM rgItems // pointer to the items array … }
这在Windows XP下提供了一个write primitive操作,覆盖nt!HalDispatchTable+0×4。接下来我们根据FireEye以及NCC Group所提供的技术细节,在Windows 8.1 64-bit下尝试进行漏洞复现。
我们可能会遇到以下障碍:
1.攻击者不能完全控制每个属性中的内容;虽然V2的8个字节是被完全控制的,但是V1只能使用2个字节。 2.上面提到的_MENU结构有变化,并且由于V1的内容缺乏控制,cItems和rgItems无法完全控制 3.需要绕过ASLR和SMEP 4.需要绕过heap entry编码机制 5.最后覆盖的对象还要记得恢复,以避免当内核释放时造成崩溃
获取任意写入操作
为了替代直接覆盖rgItems和cItems,NCC Group发现并提供的一个解决方案涉及对堆的header操作和两个覆盖,以便最终控制一个窗口结构,提供一个读写primitive操作。在此我建议大家可以看看他们提供的解决方案[3],其中的技巧已经写的非常详细了。
我选择控制一个菜单对象,因为它在各种操作系统(从Windows XP到8.1)下提供了一个常见的write primitive;在这种情况下,read primitive的不足对于我们来说也就不存在问题了。
虽然Dyre恶意软件损坏cItems和rgItems,然而我们要做的仅仅只是损坏rgItems字段:
a.创建了一个item的菜单,使用CreateMenu()以及InsertMenuItem() b.使用NCC Group提供的覆盖控制方法损坏rgItems(item数组的地址) c.数组的第一个item指向rgItems包含的一个wID字段(我们使用SetMenuItemInfo()进行修改的字段),这也导致我们获得任意write primitive操作
接下来的这一步骤十分经典:nt!HalDispatchTable+8中的一个函数指针被覆盖,然后会运行的payload包括两个阶段:
1.首先恢复桌面堆(desktop heap)以及原始的函数指针 2.第二阶段是一个用于改变当前系统进程token的Shellcode
绕过heap header编码
在该利用例子中,第一个覆盖的heap header是必须要修改的,之后我们必须在第二个覆盖内容中建立一个假的heap header。这里由于Windows 8.1的heap header是由一个cookie进行编码,为进一步的了解细节,有必要去看看Chris Valasek以及Tarjei Mandt所提供的参考[9][10]
NCC Group提供的解决方案依赖于桌面堆(desktop heap)映射的只读用户区域,暴露cookie的值。
首先我们必须找到用于编码heap header的cookie,进而确认桌面堆(desktop heap)的heap base。然后我们可以获取到存储在0×80下的cookie:
nt!_HEAP +0x000 Entry : _HEAP_ENTRY +0x010 SegmentSignature : Uint4B […] +0x07c EncodeFlagMask : Uint4B +0x080 Encoding : _HEAP_ENTRY […]
现在就可以通过对编码字段进行xor操作解码heap header
接着解码并修改Entry后,我们只需计算新的SmallTagIndex的校验和:
Entry.SmallTagIndex = Entry.SizeLow ^ Entry.SizeHigh ^ Entry.Flags;
在中等完整性等级下绕过ASLR
在中等完整性权限下ASLR对我们来说没有啥问题:
a.使用NtQuerySystemInformation()获取ntoskrnl以及hal.dll的基地址 b.动态获取ROP小工具偏移量,之后恢复后的hal!HaliQuerySystemInformation()会分别读取ntoskrnl.exe和hal.dll
在低等完整性等级下绕过ASLR
NtQuerySystemInformation()是不能从一个低等完整性等级下进行调用的。尽管在exploit源代码中没有实现,但是Alex Ionescu [5]提出了一种使用SIDT作为内核信息泄漏的方法,该方法是不会理会完整性等级的。
绕过SMEP
早在2011年5月,为了使用户模式页面从内核中执行,Dan Rosenberg就提出了一种十分新颖的方式绕过Linux下的SMEP[6]。j00ru以及Gynvael Coldwind将这一技术进一步应用到到Windows[7]。对于该exploit,我使用了siberas在2014 pwn2own所展示的方法[8]
防止post-exploitation崩溃
为了防止post-exploitation崩溃,记得一定要清理桌面堆(desktop heap)。事实上,已损坏的对象以及hal!HaliQuerySystemInformation()的原始指针都是需要进行恢复的。
我们成功了