原文地址:https://iamelli0t.github.io/2021/03/25/CVE-2021-1732.html
CVE-2021-1732是APT组织BITTER利用过的一个0-Day漏洞,该漏洞于今年2月被曝光[1][2][3]。实际上,该漏洞会利用win32kfull模块中的用户模式回调机会,破坏正常的执行流程,并设置window对象(tagWND)额外数据的错误标志,从而导致内核空间的越界访问。
导致CVE-2021-1732漏洞的根本原因是:
在创建窗口(CreateWindowEx)的过程中,当窗口对象tagWND带有额外数据(tagWND.cbwndExtra != 0)时,将在用户模式下通过nt!KeUserModeCallback回调机制,来调用保存在ntdll!_PEB.kernelCallbackTable (offset+0x58)处的use***!_xxxClientAllocWindowClassExtraBytes函数指针,并使用系统堆分配器(ntdll!RtlAllocateHeap)在用户空间分配保存额外数据所需的内存。
通过在用户模式下钩住use***!_xxxClientAllocWindowClassExtraBytes函数,并在钩子函数中手动修改窗口对象额外数据的属性,入侵者就可以破坏内核模式下为额外数据分配内存的原子操作,最终获取基于额外数据内存的越界读写原语。
窗口对象创建(CreateWindowEx)过程的正常流程如下图所示:
从上图可以看到:当窗口额外数据的长度(tagWND.cbWndExtra)不为0时,win32kfull!xxxCreateWindowEx会通过内核回调机制来调用用户模式函数use***!_xxxClientAllocWindowClassExtraBytes,以便在用户空间中为窗口的额外数据分配相应的内存空间。分配相应的内存后,指向在用户空间中分配的内存的指针将返回给tagWND.pExtraBytes属性:
下面介绍保存tagWND额外数据地址(tagWND.pExtraBytes)的两种模式:
[模式1]保存在用户空间系统堆中
在正常情况下(具体如上图所示),将指向在用户空间系统堆中分配的额外数据内存的指针直接保存到tagWND.pExtraBytes中。
下图为我们展示了模式1下tagWND内存空间的布局情况:
[[模式2]保存到内核空间的桌面堆中。
在这种模式下,函数ntdll!NtUserConsoleControl将通过函数DesktopAlloc在内核空间桌面堆中为额外数据分配内存,并计算分配的额外数据内存地址与内核桌面堆基地址的偏移量,将偏移量保存到tagWND.pExtraBytes,并修改tagWND.extraFlag |= 0x800,具体如下图所示:
下面展示了模式2下tagWND的内存布局情况:
因此,我们可以在用户空间中钩住函数use***!_xxxClientAllocWindowClassExtraBytes,然后在hook函数中手动调用NtUserConsoleControl,并将tagWND的额外数据存储模式从模式1改为模式2,最后,在回调函数返回前调用ntdll!NtCallbackReturn,具体如下图所示:
然后,通过ntdll!NtCallbackReturn函数,将用户模式可控偏移值返回给tagWND.pExtraBytes,最终实现基于内核空间桌面堆基地址的可控偏移量越界读写能力。
修改后能触发该漏洞的进程如下图所示:
根据上面修改后的流程图,现将触发该漏洞的关键步骤说明如下:
1. 将PEB.kernelCallbackTable中的use***!_xxxClientAllocWindowClassExtraBytes函数的指针修改为自定义的hook函数。
2. 创建一些正常的窗口对象,并通过use***!hmvalidateHandle泄露这些tagWND内核对象的用户空间内存地址。
3. 销毁前一步中创建的部分正常窗口对象,并使用指定的tagwnd.cbwndextra创建一个名为hwnd magic的新窗口对象。这样的话,对象hwndMagic就可能重用之前释放的窗口对象内存。因此,通过在自定义hook函数中用指定的tagWND.cbwndExtra搜索之前泄露的窗口对象用户空间的内存地址,就可以在CreateWindowEx返回之前找到hwndMagic。
4. 调用自定义hook函数中的NtUserConsoleControl,以修改标记为0x800的tagWNDMagic.extraFlag。
5. 调用自定义hook函数中的NtCallbackReturn,从而为tagWNDMagic.pExtraBytes分配一个伪造的偏移量。
6. 调用SetWindowLong将数据写入内核空间桌面堆基地址+指定偏移量的地址中,从而导致越界内存访问违规。
下面给出hook函数的示例代码:
void* WINAPI MyxxxClientAllocWindowClassExtraBytes(ULONG* size) {
do {
if (MAGIC_CBWNDEXTRA == *size) {
HWND hwndMagic = NULL;
//search from freed NormalClass window mapping desktop heap
for (int i = 2; i < 50; ++i) {
ULONG_PTR cbWndExtra = *(ULONG_PTR*)(g_pWnd[i] + _WND_CBWNDEXTRA_OFFSET);
if (MAGIC_CBWNDEXTRA == cbWndExtra) {
hwndMagic = (HWND)*(ULONG_PTR*)(g_pWnd[i]);
printf("[+] bingo! find &hwndMagic = 0x%llx in callback \n", g_pWnd[i]);
break;
}
}
if (!hwndMagic) {
printf("[-] Not found hwndMagic, memory layout unsuccessfully \n");
break;
}
// 1. set hwndMagic extraFlag |= 0x800
CONSOLEWINDOWOWNER consoleOwner = { 0 };
consoleOwner.hwnd = hwndMagic;
consoleOwner.ProcessId = 1;
consoleOwner.ThreadId = 2;
NtUserConsoleControl(6, &consoleOwner, sizeof(consoleOwner));
// 2. set hwndMagic pExtraBytes fake offset
struct {
ULONG_PTR retvalue;
ULONG_PTR unused1;
ULONG_PTR unused2;
} result = { 0 };
//offset = 0xffffff00, access memory = heap base + 0xffffff00, trigger BSOD
result.retvalue = 0xffffff00;
NtCallbackReturn(&result, sizeof(result), 0);
}
} while (false);
return _xxxClientAllocWindowClassExtraBytes(size);
}
下面是BSOD的截图:
通过分析该漏洞的成因,我们可以发现:
“通过利用这个漏洞,攻击者可以获得在内核空间桌面堆基地址+指定偏移量处读写数据的机会”。
对于内核模式下的漏洞利用,攻击目标一般是获取系统令牌,常见的方法如下所示:
1. 利用该漏洞获取内核空间的任意内存读/写原语。
2. 泄露某个内核对象的地址,通过EPROCESS链找到系统进程。
3. 将系统进程令牌复制到攻击进程令牌上,从而实现权限提升。
在这个过程中,最困难的往往是第1步:如何利用“在内核空间桌面堆基地址+指定偏移量处读写数据的机会”,获得内核空间的任意内存读写原语。
其中,一种解决方案如下图所示:
对于tagWNDMagic额外数据的偏移量(wndMagic_extra_bytes),其实是可以通过该漏洞来进行控制的,因此,我们可以使用SetWindowLong来修改桌面堆基地址+可控偏移量得到的指定地址处的数据。
l 利用该漏洞将tagWNDMagic.pExtraBytes改为tagWND0的偏移量(tagWND0的偏移量等于tagWND0+0x8),并调用SetWindowLong来修改tagWND0.cbWndExtra=0x0fffffff,从而获得篡改后的tagWND0.pExtraBytes,进而实现读写越界。
l 计算出从tagWND0.pExtraBytes到tagWND1的偏移量,并调用SetWindowLongPtr,以便将tagWND1的spMenu替换为伪造的spMenu(其tagwnd0.pextrabytes已遭篡改),然后,借助伪造的spMenu和函数GetMenuBarInfo实现任意内存读取能力。
l GetMenuBarInfo读取指定地址数据的逻辑如下图所示,其中,会将16个字节的数据存储到MENUBARINFO.rcBar结构体中:
l 利用篡改后的tagWND0.pExtraBytes修改指定地址的tagWND1.pExtraBytes,并利用tagWND1的SetWindowLongPtr获得任意内存写入能力。
l 在获得任意内存读写原语后,我们需要在桌面堆中泄露一个内核对象的地址来寻找EPROCESS。幸运的是,在步骤3中为tagWND1设置伪造的spMenu时,SetWindowLongPtr的返回值就是原spMenu的内核地址,我们可以直接使用这个地址。
l 最后,通过遍历EPROCESS链找到系统进程,并将系统进程令牌复制到攻击进程中,完成权限提升工作。由于这种方法比较常见,所以这里就不赘述了。
最后,权限提升的效果如下所示:
[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-1732
[2] https://ti.dbappsecurity.com.cn/blog/index.php/2021/02/10/windows-kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack-cn/
[3] https://www.virustotal.com/gui/file/914b6125f6e39168805fdf57be61cf20dd11acd708d7db7fa37ff75bf1abfc29/detection
[4] https://en.wikipedia.org/wiki/Privilege_escalation
本文作者:mssp299
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/157286.html