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
背景知识
本节内容描述了创建窗口时需要用到的结构体及函数:
-
用户态的窗口数据结构体:WNDCLASSEXW,需要关注cbWndExtra。
-
窗口数据保存在内核态时使用:tagWND和tagWNDK结构体,需要关注tagWNDK。
-
用户态调用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函数形式如下:

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

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

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

在xxxSetWindowLong函数中:
正常情况下cbWndServerExtra为0,157行如果index+4<
cbWndServerExtra,那么修改的是窗口的保留属性,例如GWL_WNDPROC对应-4,含义为设置窗口的回调函数地址。我们需要设置的是窗口扩展内存,所以进入165行的代码区域。
在167行会判断dwExtraFlag属性是否包含0x800,如果包含,那么168行代码destAddress=pExtraBytes+index+内核桌面堆基址,此处pExtraBytes作为相对内核桌面堆基址的相对偏移量,(QWORD)(pTagWnd->field_18+128)为内核桌面堆基地址
,对应的汇编代码为

在171行处,dwExtraFlag属性不包含0x800,此时destAddress=index+pExtraBytes,此处pExtraBytes作为用户态申请的一块内存区域地址。
dwExtraFlag的含义:
dwExtraFlag&0x800 !=
0时,代表当前窗口是控制台窗口。调用AllocConsole申请控制台窗口时,调用程序会与conhost程序通信,conhost去创建控制台窗口,调用栈如下:

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

dwExtraFlag&0x800 ==0时,代表当前窗口是GUI窗口,调用CreateWindow时窗口就是GUI窗口。
总结:
xxxSetWindowLong设置扩展内存数据时,有如下两种模式:
模式1:tagWND的dwExtraFlag属性包含0x800,使用间接寻址模式,基址为内核桌面堆基地址,pExtraBytes作为偏移量去读写内存。
模式2:tagWND的dwExtraFlag属性不包含0x800,使用直接寻址模式,pExtraBytes直接读写内存。xxxSetWindowLong会检查index,如果index+4超过cbWndExtra,那么返回索引越界错误。
漏洞成因
此漏洞是对CVE-2021-1732漏洞的绕过,此处简要介绍下CVE-2021-1732漏洞:
用户调用CreateWindow时,在对应的内核态函数中检查到窗口的cbWndExtra不为0,通过xxxCreateWindowEx->
xxxClientAllocWindowClassExtraBytes->调用回调表第123项用户态函数申请用户态空间,

1027行会调用user32!_xxxClientAllocWindowClassExtraBytes,EXP在回调函数中调用NtUserConsoleControl修改窗口的dwExtraFlag和pExtraBytes,修改窗口类型为控制台。
Windows修复代码在1039行,检查pExtraBytes是否被修改,此处查看汇编代码更为清晰


rdi+0x140-0x118 =
rdi+0x28,得到tagWNDK,偏移0x128得到pExtraBytes,判断是否不等于0,如果不等于0,1045行代码会跳转,最终释放窗口,漏洞利用失败。
也就是说:CVE-2021-1732的修复方法是在调用xxxClientAllocWindowClassExtraBytes函数后,在父函数CreateWindowEx中判断漏洞是否被利用了,这个修补方法之前是没有

最低0.47元/天 解锁文章
1621

被折叠的 条评论
为什么被折叠?



