Windows_kernel_pwn1-OOB
Windows_kernel_pwn1-OOB:
调试过程:
在调试之前需要确保首先用ghidra打开HEVD.sys这个内核驱动,在export处发现驱动程序的入口点是 DriverEntry()。该函数是设备加载时调用的第一个例程,负责初始化设备。
接下来再重启目标机(确保HEVD service打开)用windbg重新连接目标机,此时在用bu HEVD!DriverEntry命令在DriverEntry函数处下断点,当然后会在这个地方断下来,接着观察该函数附近的汇编代码,发现程序会在一定的偏移以后
如果从这里继续反汇编 (u),最终应该会看到对 IoCreateSymbolicLink 的调用。继续在指令
call dword ptr [HEVD! _imp__IoCreateSymbolicLink]对应的位置设置断点,并继续运行到该断点处(即在windbg运行g命令),此时通过r命令可以看到当前寄存器的值,通过运行dd esp L1(功能是查看栈顶的4字节值,此处为9198eacc),再dS 9198eacc即可看出,栈顶的值为”\DosDevices\HackSysExtremeVulner”,即可知用户态下的\DosDevices\HackSysExtremeVulner对应于HEVD初始程序中的创建的内核的设备
(我们可以忽略\DosDevices,因为这是 Windows 用于设备驱动程序的特殊命名空间。为了与它进行交互,我们将使用 HackSysExtremeVulnerableDriver,我们使用它是因为它是 “Win32 设备命名空间 “或 “原始设备命名空间”,我们可以在用户区使用它。虽然我们不需要这样做,但我想看看在创建符号链接时会有哪些参数传递到函数中。)
该函数负责创建我们可以从 Userland 调用的符号链接。(把名为创建的设备,映射到HEVD初始化程序中设置的设备),在HEVD中这个设备的IOcontrol函数被设置为了IrpDeviceIoCtlHandler(主逻辑是用switch/case语句处理io请求)
基于上述获得的信息,可知,当在用户态以\\.\HackSysExtremeVulnerableDriver为设备名进行CreateFileA时,可以得到一个HANDLE,利用这个HANDLE来调用DeviceIoControl,并将相应的参数位置写入stackoverflow对应的io请求代码(即0x222003),并将相应的buffer作为控制信息传入,如果这个buffer的length足够大,即就会成功触发oob。
因此开始写一个简单的poc来观察一下HEVD中的stackoverflow的执行过程:
python版本:
1 | import struct |
C语言版本:
1 |
|
对于BufferOverflowStackIoctlHandler函数,我们需要重点关注的是我们的控制信息(上图中的buffer,在函数执行流中的流动过程)
通过windbg调试可知,在BufferOverflowStackIoctlHandler函数的
param_2->Parameters).FileSystemControl.Type3InputBuffer为指向控制信息的指针,这就验证了我们的控制信息的指针会被BufferOverflowStackIoctlHandler的第二个参数访问,所以根据程序逻辑,会以param_1进入TriggerBufferOverflowStack
所以TriggerBufferOverflowStack 的 param_1 其实就是我们的缓冲区
通过做实验发送一个大于2060的缓冲区信息,就会触发栈溢出,通过windbg调试即可观察到,接下来我们就需要的是如何通过写kernel中的shellcode来提升用户权限,
此时成功改写eip的值为0x414141 !
后续我们将研究如何写kernel shellcode:
token窃取流程总结:
首先在windows中会有一个安全令牌窃取的方式来提升当前进程的权限(通过修改_ EPROCESS结构中token字段的值),通过FS段寄存器中的对应偏移即可找到当前cpu核心中正在执行的线程,即currentthread然后通过_ KTHREAD字段的对应偏移可以找到该线程所属的进程结构 _KTHREAD,在该结构中即可看到该进程的安全令牌(token字段)该字段存储着当前进程的权限信息。然后通过遍历 _EPROCESS字段的Flink,找到System进程(有System权限),并将该进程对应的token值写入当前进程,因此shellcode如下所示:
shellcode:
1 | [BITS 32 ] |
具体调试过程:
1.调shellcode中的偏移:
首先在windbg中运行!thread指令即可查看当前线程以及所属的进程结构的地址信息(用于后续验证)
用dd fs:[0x124] L1可以得出该地址信息
接下来通过指令dt _KTHREAD <地址>可以以 _KTHREAD的形式打印地址信息,从打印出来的信息中可以得到该线程所属的进程相对于该地址的偏移(为0x50)
经过调试(输入命令dt nt!_EPORCESS <地址>,此处输入nt!_EPORCESS <地址>的话可能会根据ntdll中的符号信息输出,所以偏移量会有问题)利用该进程的地址信息加上0xb8即为ActiveProcessLinks字段的位置,该偏移指向_EPROCESS 结构中的前向链接(FLINK)指针,
注:_EPROCESS结构中的ActiveProcessLinks字段的作用:
所有活动的进程通过
ActiveProcessLinks连接成一个环形双向链表。Flink指向链表中 下一个进程 的ActiveProcessLinks地址(非_EPROCESS起始地址)。操作系统通过遍历此链表管理所有进程(如任务管理器显示进程列表)。
观察上图可知偏移量为0xb8为ActiveProcessLinks字段(该字段的首地址为FILNK),偏移量为0xf8为token字段
- 记录一下该token的值,便于后续验证是否成功修改
然后从 EAX 中减去 FLINK 的偏移量,使 EAX 指向链接列表中的下一个 _EPROCESS 结构。然后将 _EPROCESS 结构的进程 ID 与 0x04 进行比较,如果未找到,则继续搜索,直到找到 SYSTEM 进程。(在windbg中将带有shellcode的py脚本运行,并单步运行,直到运行到 mov edx, [eax + 0xf8] ),正确的话此时的 _EPROCESS为system process对应的结构 _EPROCESS)
在上图中将程序断在循环外,验证eax寄存器的值就是搜索出来的system进程的_EPROCESS的首地址
最后验证原进程字段的token值,发现已经发生了变化
2.调试发送buffer的length,使得返回地址被覆盖为shellcode所在的地址:
81C为十六进制的2076,所以buffer和return address的距离为2080,所以 exp中buffer的length为
buffer = b”A” * 2080
buffer += ptrShellcode
其中ptrShellcode为shellcode在分配的具有rwx权限的内存区域的地址。
Exp:
(其中shellcode的部分已经在注释中给出,用Sickle生成,或者从在线网站上生成https://defuse.ca/online-x86-assembler.htm#disassembly)
由于有直接写入这个shellcode是有问题的,经过排查,发现问题的原因出在执行ret语句的时候ebp寄存器的值不合法,因此需要在shellcode的最后加上pop ebp和ret 0x8(在ghidra中查看HEVD中的返回语句是ret 0x8,所以此处延用)即可
1 | import struct |
最终执行该脚本并且在执行前后的控制台输入whoami,如果执行后被修改为了system,则证明利用成功!
参考链接:
https://wetw0rk.github.io/posts/0x00-introduction-to-windows-kernel-exploitation/
- Title: Windows_kernel_pwn1-OOB
- Author: zhaojunqi
- Created at : 2025-06-22 17:12:58
- Updated at : 2025-10-17 17:30:57
- Link: https://redefine.ohevan.com/2025/06/22/Windows_kernel_pwn1-OOB/
- License: This work is licensed under CC BY-NC-SA 4.0.