Windows GDI Bitmap

0x1. Windows GDI Bitmap

GDI(Windows Graphics Device Interface),是提供图形、文字显示的 API 接口。

Bitmap 是 GDI 中的图形对象,Bitmap 事实上包含一个二元数组,用于存放图形像素(当然还有图形大小等信息),可以通过 CreateBitmap 和 SetBitmapBits 来创建和修改图形。

0x2. 用户态到内核态的 Bitmap 访问过程

在通过 CreateBitmap 创建 Bitmap 对象后,返回对应的资源句柄,同 CreateFile 类似,其实 Windows 都用句柄(Handle)来标识用户态对内核对象的引用。这个句柄低 16 位其实是数组索引:

UINT index = hBitmap & 0xFFFF;

这个数组指针位于 PEB->GdiSharedHandleTable 中,定义如下:

struct _GDI_CELL GdiSharedHandleTable[0x8fff];

GDI_CELL 的定义如下,这个对象在 x86 下大小是 0x10,x64 是 0x18:

struct _GDI_CELL
{
    IntPtr pKernelAddress;
    UInt16 wProcessId;
    UInt16 wCount;
    UInt16 wUpper;
    UInt16 wType;
    IntPtr pUserAddress;
}

看到 pKernelAddress 了吗?所以在用户态中,通过 PEB 结构,可以索引到 Bitmap 在内核中的真实对象地址:

ULONG_PTR baseObj = PEB->GdiSharedHandleTable + (hBitmap & 0xFFFF) * 0x10

Bitmap 内核对象的真实结构如下:

typedef struct _SURFACE {
    BASEOBJECT BaseObject;
    SURFOBJ surfobj;
    [...]
}

BASEOBJECT 对内核对象进行标记,用于描述最基础的对象信息,真实的 Bitmap 对象信息存放在 SURFOBJ 中:

typedef struct _SURFOBJ {
    DHSURF dhsurf;
    HSURF  hsurf;
    DHPDEV dhpdev;
    HDEV   hdev;
    SIZEL  sizlBitmap;
    ULONG  cjBits;
    PVOID  pvBits;
    PVOID  pvScan0;
    LONG   lDelta;
    ULONG  iUniq;
    ULONG  iBitmapFormat;
    USHORT iType;
    USHORT fjBitmap;
} SURFOBJ, *PSURFOBJ;

在这个结构中,存在一个非常重要的成员:pvScan0。这个指针指向 Bitmap 的图形像素。无论是GetBitmapBits还是SetBitmapBits,都是通过这个指针来读取对应的像素信息。

0x3. 内核利用中的 Bitmap

在内核漏洞利用中,如果获取到一个任意内存写(Arbitrary Memory Write)漏洞,则可以向任意地址写入一个 unsigned long 值。

第一个能想到的,应该是替换某个内核分支地址,然后跳转到我们控制的代码。

但是如果存在 SMEP 等保护,或者说需要通过这个漏洞,来达到可以操作任意其他内核内存的目的。后者听起来好像很难实现?

这时候就需要引入一个 Primitive,用本原这个上帝视角,来构建整个内核世界:

  • 首先找到一个内核内存访问接口,这个 API 中,内核为我们分配内核内存,虽然限制我们只能对这部分内存进行读写
  • 能通过用户态的方式,找到内核内存的地址,这个地址存放在某个数据结构中

如果能符合上面的两个条件,首先我们通过 API 分配一个内核内存(CreateBitmap)。接着,通过内核漏洞,替换数据结构中指向限制内存区域的指针(SURFACE->pvScan0)。最后,再通过 API(GetBitmapBits / SetBitmapBits),对这个被间接引用的指针进行访问,可以自由向这个地址写入、读取数据。

但是如果需要更换地址,都需要重新利用漏洞,来覆盖这个指针。这时候我们可以再叠加一次 :

  • 创建两个 Bitmap 对象
  • 第一个 Bitmap 对象,通过内核漏洞,覆盖 pvScan0 为另外 Bitmap 对象的 pvScan0 地址
  • 往后,操作第一个 Bitmap,可以替换第二个 Bitmap->pvScan0 地址,再通过第二个 Bitmap 来读写任意内存

因此第一个 Bitmap 称为 Manager,用于管理写入地址;第二个 Bitmap 则称为 Worker,负责对真实内存区域进行操作。

https://www.fuzzysecurity.com/tutorials/expDev/21.html