ring0下多核环境hook高并发函数
字数 1008 2025-08-07 08:22:25
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:
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函数实现
SwapContext的edi为当前线程,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函数后,可以实时监控线程切换信息,输出格式如下:
当前线程为:[地址] 下一个线程为:[地址]
关键点总结
- 多核环境处理:必须绑定到特定CPU获取内核信息
- 原子性操作:使用
CMPXCHG8B确保8字节修改的原子性 - 内存保护:修改CR0前需提升IRQL并禁用中断
- 版本兼容:需检测系统版本和分页模式
- 线程安全:Hook高并发函数时要确保不会引起竞态条件
这种方法相比传统Hook方法在多核环境下更加稳定可靠,适合Hook Windows核心调度函数。