查看原文
其他

CVE-2021-1732 EXP Win10_1909 KaLendsi 的EXP编写与分析

ExploitCN 看雪学苑 2022-07-01


本文为看雪论坛优秀‍‍‍文章
看雪论坛作者ID:ExploitCN




简介


1.1  概述


CVE-2021-1732 是蔓灵花(BITTER)APT 组织在某次被披露的攻击行动中使用的 0day 漏洞,该高危漏洞可以在本地将普通用户进程的权限提升至最高的 SYSTEM 权限。

目前,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  漏洞类型

该漏洞和传统的内核漏洞不太一样,它不是UAF类型漏洞,也不涉及内存耗尽之后的指针问题,所以没有堆喷射,没有内存消耗,很难检测其存在,漏洞类型可以称之为:逻辑型漏洞。也就是说,漏洞是通过代码审查,分析函数功能流程找到的。这种类型的漏洞确实经典。



POC分析


2.1  漏洞流程图



通过上图可知,Ring3调用CreateWindowsEx时,进入Ring0后,又返回Ring3申请内存空间,此时对函数_xxxClientAllocWindowsClassExtraBytes进行HOOK,通过调用NtUserConsoleControl改变其为offset模式,然后返回恶意地址。这就是漏洞成因,是利用的关键。图片来源:https://bbs.pediy.com/thread-266362.htm。

因此,该漏洞的实质就是,在创建一个带扩展内存的窗口时,内核中xxxClientAllocWindowClassExtraBytes的应用层回调对来自应用层返回的数据校验不严导致,通过hook xxxClientAllocWindowClassExtraBytes,并在hook函数中调用NtUserConsoleControl/NtCallbackReturn可以将目标窗口的poi(tagWND+0x28)+0x128位置设置为任意offset,从而导致越界写入。
 
2.2  POC关键代码
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;}

上面是POC关键代码,可以看到首先调用CreateWindowsExW创建Magic窗口,然后HOOK ClientAllocWindowClassExtraBytes函数,通过在内存中暴力搜索Magic窗口的句柄值之后,返回一个0xFFFFFF00地址,再调用:
DWORD dwRet = SetWindowLongW(g_hWndMagic, 0x128, g_Thrdeskhead_cLockobj_Min);

触发漏洞。

2.3  POC运行结果


反编译xxxSetWindowLong出现异常时的代码,分析可见关于SetWindowsLong存在两种寻址模式,一种是直接寻址,一种是偏移寻址,图中的ptagWNDk+0x128就是pExtraBytes,直接寻址时它是地址;偏移寻址时它是相对于桌面堆基址的偏移量。POC代码就是将其直接寻址模式,改为偏移寻址,触发的漏洞。如下图所示。


运行POC,出现异常:


从上图可以看出,POC运行之后,rdx就是我们在代码里面设置的NtCallBackReturn的0Xffffff00,在xxxSetWindowLong偏移0x110、0x113,可以看到,POC运行之后,rdx就是我们在代码里面设置的NtCallBackReturn的0Xffffff00,在xxxSetWindowLong偏移0x110、0x113,如下图所示:


说明我们现在出于直接寻址模式,桌面堆基址+设置的偏移 = 目标地址,这就意味着有存在任意地址任意写的可能。



EXP分析


3.1  tagWND结构体


该结构体在win7之后就没有符号文件了,需要自己分析。tagWND结构体参考了https://www.anquanke.com/post/id/241804#h3-12。

主要区别是:
1、在1909版本下,tagWND的结构体稍微有所变化,本文对变化进行了更新,且更正了之前网址对dwStyle结构体定义出现的错误。
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 任意写第一步

EXP代码第275行~317行、350行~363行执行完之后,Magic窗口内存占用如下。


由上图可见,hWndMagic的tagWNDk确实是0x2416e8dd5c0,但是g_hWndMagic已经是0x70342了,而不是原来的0x50354。

在代码第366行断点,此时,窗口Min、窗口Max、窗口Magic的tagWNDk如下:

图3.2 窗口Min的tagWNDk

图3.3 窗口Max的tagWNDk

图3.4 窗口Magic的tagWNDk

由图3.2~3.4可知:
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窗口的内存布局如下:


但是函数返回之后,实际值等于0x08000100。通过内存布局,也可以看出,tagWNDk的0x18是dwExStyle,0x1C是dwStyle。这是其他地方介绍这个字段时的错误之处,现在纠正过来。

3.2.2.2 第二步,让窗口Min的pExtraBytes指向自己的ptagWNDk

需要修改两个地方:

1)一个是cbWndExtra为0xFFFFFFFF,使得原来只能32字节的写范围,扩大到0xFFFFFFFF;

2)修改窗口Min的pExtraBytes指向自己;

图3.6 窗口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:


在EXP代码第436行断点,可以得到此时窗口Max的内存布局,如下图:


由上图可知,在调用:
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);逆向代码如下:

图3.8 设置虚假Menu时的逆向代码

由上图可知,调用该函数如果要设置成功,则需要窗口Max要包含WS_CHILD属性。

所以,首先调用:
SetWindowLongPtrA(hWndMin, offset_0x18 + Thrdeskhead_cLockboj_Max - g_Thrdeskhead_cLockobj_Min, g_qwrpdesk ^ 0x4000000000000000);设置窗口Max包含WS_CHILD属性。

3.2.3.2 第二步:泄漏窗口Max的Menu


由图3.8可知,在函数
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的内存如下图。

g_qwExpLoit就是窗口Max的Menu地址。通过该地址,就可以获取想要的关键数据了。

由图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.9 泄漏窗口Max的Menu时窗口Max内存布局

图3.10 Menu的内存布局


3.2.3.3 任意读第三步:移除窗口Max的WS_CHILD属性


因为获取到泄漏的Menu地址后,实际是通过GetMenuBarInfo读取需要的数据的。而通过GetMenuBarInfo读取数据时,窗口是不能有是WS_CHILD属性。

所以需要执行:
SetWindowLongPtrA(hWndMin, offset_0x18 + Thrdeskhead_cLockboj_Max -g_Thrdeskhead_cLockobj_Min, g_qwrpdesk);

恢复窗口的WS_CHILD属性。

图3.10 恢复窗口Max的WS_CHILD属性时的内存


由上图可知,一共有3个检查点、2个构造点、2个取值点,具体细节参考EXP源码。EXP原来有两行是多余的:
1、ref_g_pMem1 = 0x88888888;
2、(QWORD*)(ref_g_pMem3 + 8) = 16;
可以删除。



提权复现






代码


EXP代码于原文附件中,可点击【阅读原文】查看,含我的注释和修改,相信对你有所帮助。





 


看雪ID:ExploitCN

https://bbs.pediy.com/user-home-945611.htm

*本文由看雪论坛 ExploitCN 原创,转载请注明来自看雪社区



# 往期推荐

1.一种将LLVM Pass集成到NDK中的通用方法

2.人工智能竞赛-房价预测

3.Windows PrintNightmare 漏洞复现分析

4.壳小白关于压缩壳的学习心得及基础实战练习

5.Windows平台下栈溢出漏洞学习笔记

6.GKCTF2021 KillerAid






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存