CVE-2022-21882 Win32k内核提权漏洞深入分析

CVE-2022-21882漏洞是Windows系统的一个本地提权漏洞,微软在2022年1月份安全更新中修补此漏洞。本文章对漏洞成因及利用程序进行了详细的分析。

漏洞介绍

CVE-2022-21882是对CVE-2021-1732漏洞的绕过,属于win32k驱动程序中的一个类型混淆漏洞。

攻击者可以在user_mode调用相关的GUI
API进行内核调用,如xxxMenuWindowProc、xxxSBWndProc、xxxSwitchWndProc、xxxTooltipWndProc等,这些内核函数会触发回调xxxClientAllocWindowClassExtraBytes。攻击者可以通过hook
KernelCallbackTable 中 xxxClientAllocWindowClassExtraBytes 拦截该回调,并使用
NtUserConsoleControl 方法设置 tagWNDK 对象的 ConsoleWindow 标志,从而修改窗口类型。

最终回调后,系统不检查窗口类型是否发生变化,由于类型混淆而引用了错误的数据。flag修改前后的区别在于,在设置flag之前,系统认为tagWNDK.pExtraBytes保存了一个user_mode指针;flag设置后,系统认为tagWNDK.pExtraBytes是内核桌面堆的偏移量,攻击者可以控制这个偏移量,从而导致越界R&W。

本篇文章分析了漏洞成因及漏洞利用手法分析,侧重动态调试及利用手法分析。

漏洞影响版本

Windows 10 Version 21H2 for x64-based Systems
Windows 10 Version 21H2 for ARM64-based Systems
Windows 10 Version 21H2 for 32-bit Systems
Windows 11 for ARM64-based Systems
Windows 11 for x64-based Systems
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 21H1 for ARM64-based Systems
Windows 10 Version 21H1 for x64-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 20H2 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server 2022 (Server Core installation)
Windows Server 2022
Windows 10 Version 21H1 for 32-bit Systems

分析环境

Windows 10 21H2 19044.1415 x64
Vmware 16.2.1
VirtualKD-Redux 2020.4.0.0
Windbg 10.0.22000.194

背景知识

本节内容描述了创建窗口时需要用到的结构体及函数:

  1. 用户态的窗口数据结构体:WNDCLASSEXW,需要关注cbWndExtra。

  2. 窗口数据保存在内核态时使用:tagWND和tagWNDK结构体,需要关注tagWNDK。

  3. 用户态调用SetWindowLong可以设置窗口扩展内存数据,逆向分析SetWindowLong如何设置窗口扩展内存数据。

窗口类拥有如下属性结构,此处仅列出比较重要的结构:

typedef struct tagWNDCLASSEXW {
    UINT        cbSize;             //结构体的大小
    …
    UINT        style;              //窗口的风格
    WNDPROC     lpfnWndProc;        //处理窗口消息的回调函数地址
    int         cbClsExtra;         //属于此类窗口所有实例共同占用的内存大小
    int         cbWndExtra;         //窗口实例扩展内存大小
    LPCWSTR     lpszClassName;      //类名
    …
} WNDCLASSEXW

在用户态创建窗口时,需要调用RegisterClass注册窗口类,每个窗口类有自己的名字,调用CreateWindow创建窗口时传入类的名字,即可创建对应的窗口实例。
当cbWndExtra不为0时,系统会申请一段对应大小的空间,如果回调到用户态申请空间时,可能会触发漏洞。
内核中使用两个结构体来保存窗口数据tagWND和tagWNDK:

ptagWND             //内核中调用ValidateHwnd传入用户态窗口句柄可返回此数据指针
    0x18 unknown
        0x80 kernel desktop heap base   //内核桌面堆基址
    0x28 ptagWNDk   // 需要重点关注这个结构体,结构体在下方:
    0xA8 spMenu

tagWNDK结构体,需要重点关注此结构体:

struct tagWNDK
{
    ULONG64 hWnd;               //+0x00
    ULONG64 OffsetToDesktopHeap;//+0x08 tagWNDK相对桌面堆基址偏移
    ULONG64 state;              //+0x10
    DWORD dwExStyle;            //+0x18
    DWORD dwStyle;              //+0x1C
    BYTE gap[0x38];
    DWORD rectBar_Left;         //0x58
    DWORD rectBar_Top;          //0x5C
    BYTE gap1[0x68];
    ULONG64 cbWndExtra;         //+0xC8 窗口扩展内存的大小
    BYTE gap2[0x18];
    DWORD dwExtraFlag;          //+0xE8  决定SetWindowLong寻址模式
    BYTE gap3[0x10];            //+0xEC
    DWORD cbWndServerExtra;     //+0xFC
    BYTE gap5[0x28];
    ULONG64 pExtraBytes;    //+0x128 模式1:内核偏移量 模式2:用户态指针
};

当WNDCLASSEXW
中的cbWndExtra值不为0时,创建窗口时内核会回调到用户态函数user32!_xxxClientAllocWindowClassExtraBytes申请一块cbWndExtra大小的内存区域,并且将返回地址保存在tagWNDK结构体的pExtraBytes变量中。

使用函数SetWindowLong和GetWindowLong,可对窗口扩展内存进行读写,进入内核后调用栈如下:

win32kfull!xxxSetWindowLong
win32kfull!NtUserSetWindowLong+0xc7
win32k!NtUserSetWindowLong+0x16
nt!KiSystemServiceCopyEnd+0x25
win32u!NtUserSetWindowLong+0x14
user32!_SetWindowLong+0x6e
CVE_2022_21882!wmain+0x25d

SetWindowLong函数形式如下:

image

第二个参数为index,含义为设置扩展内存偏移index处的内容。
在win32kfull!xxxSetWindowLong函数中,会对第二个参数index进行判断,防止越界:

image

137行代码判断index+4如果大于cbWndServerExtra+
cbWndExtra,表明越界,一般情况下cbWndServerExtra为0,如果越界,会跳转到117行LABEL_34,设置v18为1413,跳转到LABEL_55,调用UserSetLastError设置错误值,我们可以在cmd下查看此错误值的含义:

image

如果没有越界的话,接下来会根据不同的模式来使用pExtraBytes,如下:

image

在xxxSetWindowLong函数中:

正常情况下cbWndServerExtra为0,157行如果index+4<
cbWndServerExtra,那么修改的是窗口的保留属性,例如GWL_WNDPROC对应-4,含义为设置窗口的回调函数地址。我们需要设置的是窗口扩展内存,所以进入165行的代码区域。

在167行会判断dwExtraFlag属性是否包含0x800,如果包含,那么168行代码destAddress=pExtraBytes+index+内核桌面堆基址,此处pExtraBytes作为相对内核桌面堆基址的相对偏移量,(QWORD)(pTagWnd->field_18+128)为内核桌面堆基地址
,对应的汇编代码为

image

在171行处,dwExtraFlag属性不包含0x800,此时destAddress=index+pExtraBytes,此处pExtraBytes作为用户态申请的一块内存区域地址。

dwExtraFlag的含义:

dwExtraFlag&0x800 !=
0时,代表当前窗口是控制台窗口。调用AllocConsole申请控制台窗口时,调用程序会与conhost程序通信,conhost去创建控制台窗口,调用栈如下:

image

conhost获取到窗口句柄后,调用NtUserConsoleControl修改窗口为控制台类型,调用栈如下:

image

dwExtraFlag&0x800 ==0时,代表当前窗口是GUI窗口,调用CreateWindow时窗口就是GUI窗口。

总结:

  1. xxxSetWindowLong设置扩展内存数据时,有如下两种模式:
    模式1:tagWND的dwExtraFlag属性包含0x800,使用间接寻址模式,基址为内核桌面堆基地址,pExtraBytes作为偏移量去读写内存。
    模式2:tagWND的dwExtraFlag属性不包含0x800,使用直接寻址模式,pExtraBytes直接读写内存。

  2. xxxSetWindowLong会检查index,如果index+4超过cbWndExtra,那么返回索引越界错误。

漏洞成因

此漏洞是对CVE-2021-1732漏洞的绕过,此处简要介绍下CVE-2021-1732漏洞:

用户调用CreateWindow时,在对应的内核态函数中检查到窗口的cbWndExtra不为0,通过xxxCreateWindowEx->
xxxClientAllocWindowClassExtraBytes->调用回调表第123项用户态函数申请用户态空间,

image

1027行会调用user32!_xxxClientAllocWindowClassExtraBytes,EXP在回调函数中调用NtUserConsoleControl修改窗口的dwExtraFlag和pExtraBytes,修改窗口类型为控制台。

Windows修复代码在1039行,检查pExtraBytes是否被修改,此处查看汇编代码更为清晰

image
image

rdi+0x140-0x118 =
rdi+0x28,得到tagWNDK,偏移0x128得到pExtraBytes,判断是否不等于0,如果不等于0,1045行代码会跳转,最终释放窗口,漏洞利用失败。

也就是说:CVE-2021-1732的修复方法是在调用xxxClientAllocWindowClassExtraBytes函数后,在父函数CreateWindowEx中判断漏洞是否被利用了,这个修补方法之前是没有

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值