ring0下多核环境hook高并发函数
字数 1008 2025-08-07 08:22:25

Ring0下多核环境Hook高并发函数技术详解

0x00 前言

在Ring0层多核环境下Hook高并发函数时,使用如memcpy等函数进行字节拷贝存在严重问题:当拷贝5字节硬编码时,可能只拷贝到一半就被其他线程执行,导致蓝屏。本文介绍三种解决方案:

  1. 短跳中转
  2. 中断门
  3. 使用一次性修改8字节的指令

本文将重点介绍第三种方法,以Windows核心线程切换函数SwapContext为例进行实现。

0x01 获取SwapContext函数地址

1.1 获取ntoskrnl.exe基址和大小

在Ring0下获取内核模块信息的方法:

  1. 通过fs:[0]指向KPCR结构
  2. fs:[0x34]指向KdVersionBlock
  3. KdVersionBlock+18h指向PsLoadedModuleList
  4. 遍历模块列表找到ntoskrnl.exe的KLDR_DATA_TABLE_ENTRY结构
  5. 从结构中获取DllBase(基址)和SizeOfImage(大小)

注意:只有CPU编号为1的KPCR才有有效的KdVersionBlock值,因此需要绑定线程到1号CPU:

PVOID DllBase = NULL;
SIZE_T viewSize = 0;
KeSetSystemAffinityThread(1);  // 绑定到1号CPU
__asm {
    push eax;
    push ebx;
    mov eax, fs:[0x34];
    add eax, 18h;
    mov eax, [eax];
    mov eax, [eax];
    mov ebx, [eax + 18h];
    mov DllBase, ebx;
    mov ebx, [eax + 20h];
    mov viewSize, ebx;
    pop ebx;
    pop eax;
}
KeRevertToUserAffinityThread();  // 恢复线程CPU绑定

1.2 特征码搜索

提取SwapContext函数前16字节作为特征码(4个DWORD):

ULONG opCodeArray[TRAITCODELEN] = { 0xc626c90a, 0x9c022d46, 0x05408b8d, 0xdde80000 };

搜索代码:

ULONG endDllAddr = (ULONG)DllBase + viewSize;
for (ULONG i = (ULONG)DllBase; i < endDllAddr; i++) {
    try {
        if (*(PULONG)i == OpCode1 && *(PULONG)(i + 4) == OpCode2
            && *(PULONG)(i + 8) == OpCode3 && *(PULONG)(i + 0xC) == OpCode4) {
            return i;  // 返回函数地址
        }
    } except(1) {
        continue;
    }
}

0x02 系统环境检测

2.1 获取操作系统版本

ULONG GetWindowsVersion() {
    RTL_OSVERSIONINFOW lpVersionInformation = { sizeof(RTL_OSVERSIONINFOW) };
    if (NT_SUCCESS(RtlGetVersion(&lpVersionInformation))) {
        ULONG dwMajorVersion = lpVersionInformation.dwMajorVersion;
        ULONG dwMinorVersion = lpVersionInformation.dwMinorVersion;
        if (dwMajorVersion == 5 && dwMinorVersion == 1) return WINXP;
        else if (dwMajorVersion == 6 && dwMinorVersion == 1) return WIN7;
        else if (dwMajorVersion == 6 && dwMinorVersion == 2) return WIN8;
        else if (dwMajorVersion == 10 && dwMinorVersion == 0) return WIN10;
    }
    return 0;
}

2.2 获取分页模式

通过检测CR4寄存器的PAE位判断分页模式:

ULONG GetWindowsPageMode() {
    ULONG PageMode = 0x1;  // 默认为2-9-9-12分页
    __asm {
        _emit 0x0F;     // mov eax, cr4;
        _emit 0x20;
        _emit 0xE0;
        test eax, 0x20;
        jnz  End;
        mov dword ptr[PageMode], 0x0;  // 10-10-12分页
    End:;
    }
    return PageMode;
}

0x03 内存保护处理

3.1 去除写保护

修改CR0的WP位(第16位),需要先提升IRQL:

KIRQL RemoveP() {
    // 提升IRQL等级为DISPATCH_LEVEL
    KIRQL irQl = KeRaiseIrqlToDpcLevel();
    ULONG_PTR cr0 = __readcr0();
    cr0 &= ~0x10000;  // 将WP位清0
    _disable();       // 相当于cli指令,屏蔽软中断
    __writecr0(cr0);
    return irQl;
}

3.2 恢复写保护

KIRQL ResumeP(KIRQL irQl) {
    ULONG_PTR cr0 = __readcr0();
    cr0 |= 0x10000;   // WP位复原为1
    _disable();
    __writecr0(cr0);
    KeLowerIrql(irQl); // 恢复IRQL等级
    return irQl;
}

0x04 使用CMPXCHG8B指令

CMPXCHG8B指令可以原子性地比较并交换8字节数据:

ULONG DataLow = 0x0, DataHigh = 0x0;

VOID _declspec(naked) _fastcall FastSwapMemory(ULONG* TargetAddr, ULONG* SoulAddr) {
    __asm {
        pushad;
        pushfd;
        mov esi, ecx;  // ecx = TargetAddr
        mov edi, edx;  // edx = SoulAddr
        mov edx, 0x0;
        mov eax, 0x0;
        // 读取ShellCode
        lock CMPXCHG8B qword ptr[edi];
        mov DataLow, eax;
        mov DataHigh, edx;
        // 读取目标内存
        lock CMPXCHG8B qword ptr[esi];  // edx:eax = [TargetAddr]
        mov ebx, dword ptr[DataLow];
        mov ecx, dword ptr[DataHigh];
        // HOOK目标内存
        lock CMPXCHG8B qword ptr[esi];  // 把[SoulAddr]存储到[TargetAddr]
        popfd;
        popad;
        retn;
    }
}

0x05 Hook实现

5.1 Hook函数实现

SwapContextedi为当前线程,esi为下一线程:

void _declspec(naked) HookSwapContextFunction() {
    _asm {
        mov dword ptr[CurrentThread], edi;
        mov dword ptr[NextThread], esi;
        pushad;
        pushfd;
    }
    
    DbgPrint("当前线程为:%x\t\t下一个线程为:%x\n", CurrentThread, NextThread);
    
    _asm {
        popfd;
        popad;
        mov eax, dword ptr[SwapContext];
        jmp eax;
    }
}

0x06 实现效果

成功Hook SwapContext函数后,可以实时监控线程切换信息,输出格式如下:

当前线程为:[地址]    下一个线程为:[地址]

关键点总结

  1. 多核环境处理:必须绑定到特定CPU获取内核信息
  2. 原子性操作:使用CMPXCHG8B确保8字节修改的原子性
  3. 内存保护:修改CR0前需提升IRQL并禁用中断
  4. 版本兼容:需检测系统版本和分页模式
  5. 线程安全:Hook高并发函数时要确保不会引起竞态条件

这种方法相比传统Hook方法在多核环境下更加稳定可靠,适合Hook Windows核心调度函数。

Ring0下多核环境Hook高并发函数技术详解 0x00 前言 在Ring0层多核环境下Hook高并发函数时,使用如 memcpy 等函数进行字节拷贝存在严重问题:当拷贝5字节硬编码时,可能只拷贝到一半就被其他线程执行,导致蓝屏。本文介绍三种解决方案: 短跳中转 中断门 使用一次性修改8字节的指令 本文将重点介绍第三种方法,以Windows核心线程切换函数 SwapContext 为例进行实现。 0x01 获取SwapContext函数地址 1.1 获取ntoskrnl.exe基址和大小 在Ring0下获取内核模块信息的方法: 通过 fs:[0] 指向KPCR结构 fs:[0x34] 指向KdVersionBlock KdVersionBlock+18h指向PsLoadedModuleList 遍历模块列表找到ntoskrnl.exe的KLDR_ DATA_ TABLE_ ENTRY结构 从结构中获取DllBase(基址)和SizeOfImage(大小) 注意 :只有CPU编号为1的KPCR才有有效的KdVersionBlock值,因此需要绑定线程到1号CPU: 1.2 特征码搜索 提取SwapContext函数前16字节作为特征码(4个DWORD): 搜索代码: 0x02 系统环境检测 2.1 获取操作系统版本 2.2 获取分页模式 通过检测CR4寄存器的PAE位判断分页模式: 0x03 内存保护处理 3.1 去除写保护 修改CR0的WP位(第16位),需要先提升IRQL: 3.2 恢复写保护 0x04 使用CMPXCHG8B指令 CMPXCHG8B 指令可以原子性地比较并交换8字节数据: 0x05 Hook实现 5.1 Hook函数实现 SwapContext 的 edi 为当前线程, esi 为下一线程: 0x06 实现效果 成功Hook SwapContext 函数后,可以实时监控线程切换信息,输出格式如下: 关键点总结 多核环境处理 :必须绑定到特定CPU获取内核信息 原子性操作 :使用 CMPXCHG8B 确保8字节修改的原子性 内存保护 :修改CR0前需提升IRQL并禁用中断 版本兼容 :需检测系统版本和分页模式 线程安全 :Hook高并发函数时要确保不会引起竞态条件 这种方法相比传统Hook方法在多核环境下更加稳定可靠,适合Hook Windows核心调度函数。