深入剖析CVE-2021-1732漏洞

2021-04-16 6,007


原文地址: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)过程的正常流程如下图所示: 

 

image.png 

从上图可以看到:当窗口额外数据的长度(tagWND.cbWndExtra)不为0时,win32kfull!xxxCreateWindowEx会通过内核回调机制来调用用户模式函数use***!_xxxClientAllocWindowClassExtraBytes,以便在用户空间中为窗口的额外数据分配相应的内存空间。分配相应的内存后,指向在用户空间中分配的内存的指针将返回给tagWND.pExtraBytes属性:

image.png 

下面介绍保存tagWND额外数据地址(tagWND.pExtraBytes)的两种模式:

[模式1]保存在用户空间系统堆中

在正常情况下(具体如上图所示),将指向在用户空间系统堆中分配的额外数据内存的指针直接保存到tagWND.pExtraBytes中。

下图为我们展示了模式1下tagWND内存空间的布局情况:

image.png 

[[模式2]保存到内核空间的桌面堆中。

在这种模式下,函数ntdll!NtUserConsoleControl将通过函数DesktopAlloc在内核空间桌面堆中为额外数据分配内存,并计算分配的额外数据内存地址与内核桌面堆基地址的偏移量,将偏移量保存到tagWND.pExtraBytes,并修改tagWND.extraFlag |= 0x800,具体如下图所示:

image.png 

下面展示了模式2下tagWND的内存布局情况:

image.png 

因此,我们可以在用户空间中钩住函数use***!_xxxClientAllocWindowClassExtraBytes,然后在hook函数中手动调用NtUserConsoleControl,并将tagWND的额外数据存储模式从模式1改为模式2,最后,在回调函数返回前调用ntdll!NtCallbackReturn,具体如下图所示:

image.png 

然后,通过ntdll!NtCallbackReturn函数,将用户模式可控偏移值返回给tagWND.pExtraBytes,最终实现基于内核空间桌面堆基地址的可控偏移量越界读写能力。

修改后能触发该漏洞的进程如下图所示: 

image.png 

根据上面修改后的流程图,现将触发该漏洞的关键步骤说明如下:

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的截图:

image.png 

漏洞的利用方法

通过分析该漏洞的成因,我们可以发现:

“通过利用这个漏洞,攻击者可以获得在内核空间桌面堆基地址+指定偏移量处读写数据的机会”。

对于内核模式下的漏洞利用,攻击目标一般是获取系统令牌,常见的方法如下所示:

1. 利用该漏洞获取内核空间的任意内存读/写原语。

2. 泄露某个内核对象的地址,通过EPROCESS链找到系统进程。

3. 将系统进程令牌复制到攻击进程令牌上,从而实现权限提升。

在这个过程中,最困难的往往是第1步:如何利用“在内核空间桌面堆基地址+指定偏移量处读写数据的机会”,获得内核空间的任意内存读写原语。

其中,一种解决方案如下图所示:

 

image.png 

对于tagWNDMagic额外数据的偏移量(wndMagic_extra_bytes),其实是可以通过该漏洞来进行控制的,因此,我们可以使用SetWindowLong来修改桌面堆基地址+可控偏移量得到的指定地址处的数据。

利用该漏洞将tagWNDMagic.pExtraBytes改为tagWND0的偏移量(tagWND0的偏移量等于tagWND0+0x8),并调用SetWindowLong来修改tagWND0.cbWndExtra=0x0fffffff,从而获得篡改后的tagWND0.pExtraBytes,进而实现读写越界。

计算出从tagWND0.pExtraBytes到tagWND1的偏移量,并调用SetWindowLongPtr,以便将tagWND1的spMenu替换为伪造的spMenu(其tagwnd0.pextrabytes已遭篡改),然后,借助伪造的spMenu和函数GetMenuBarInfo实现任意内存读取能力。

GetMenuBarInfo读取指定地址数据的逻辑如下图所示,其中,会将16个字节的数据存储到MENUBARINFO.rcBar结构体中:

image.png 

利用篡改后的tagWND0.pExtraBytes修改指定地址的tagWND1.pExtraBytes,并利用tagWND1的SetWindowLongPtr获得任意内存写入能力。

在获得任意内存读写原语后,我们需要在桌面堆中泄露一个内核对象的地址来寻找EPROCESS。幸运的是,在步骤3中为tagWND1设置伪造的spMenu时,SetWindowLongPtr的返回值就是原spMenu的内核地址,我们可以直接使用这个地址。

最后,通过遍历EPROCESS链找到系统进程,并将系统进程令牌复制到攻击进程中,完成权限提升工作。由于这种方法比较常见,所以这里就不赘述了。

最后,权限提升的效果如下所示:

image.png 

补丁分析

image.png  

参考资料 

 

[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

Tags:
评论  (0)
快来写下你的想法吧!

mssp299

文章数:51 积分: 662

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号