通过硬件断点绕过hook检测
字数 1679 2025-08-07 08:22:39
通过硬件断点绕过Hook检测技术详解
0x00 前言
Hook技术主要分为两类:
- 基于修改函数地址的Hook(如IAT Hook、SSDT Hook)
- 只能Hook已导出函数
- 对未导出函数无效
- 基于修改函数代码的Hook(如Inline Hook)
- 可以Hook任意函数
- 在实际应用中更受青睐
0x01 Hook检测与常规绕过方法
检测示例
- 示例程序:Win32 MessageBox程序
- 检测函数:Hook_E9
- 检测MessageBoxA是否被Hook
- 检测到Hook则调用ExitProcess退出程序
常规Inline Hook方法
- 修改5字节硬编码(E9跳转)
- 通过DLL远程线程注入
- 问题:会被E9硬编码检测拦截
改进方法
- 使用call短跳转到未监控区域
- 再从该区域跳转到处理函数
- 问题:会被CRC校验检测拦截
结论:任何修改内存的Hook方法都无法绕过CRC检测
0x02 硬件断点基础
断点类型对比
- 软件断点:
- 原理:修改指令为0xCC
- 需要修改内存
- 内存断点:
- 原理:修改PTE属性触发异常
- 需要修改内存
- 硬件断点:
- 原理:利用CPU调试寄存器
- 不修改程序内存
调试寄存器(Dr0-Dr7)
- Dr0-Dr3:存储断点地址(最多4个断点)
- Dr4-Dr5:保留
- Dr6:调试状态寄存器
- Dr7:调试控制寄存器(最重要)
Dr7寄存器详解
-
L0/G0 - L3/G3:
- 控制Dr0-Dr3是否有效
- Lx=1:局部有效(异常后清零)
- Gx=1:全局有效(异常后不清零)
-
断点长度(LENx):
- 00:1字节
- 01:2字节
- 11:4字节
-
断点类型(R/Wx):
- 00:执行断点
- 01:写入断点
- 11:访问断点
硬件断点流程
- CPU检测当前地址与调试寄存器地址匹配
- 查IDT表找到中断处理函数(nt!_KiTrap01)
- CommonDispatchException
- KiDispatchException
- DbgkForwardException发送调试事件
0x03 绕过Hook检测的思路
核心思想
利用硬件断点触发异常,在异常处理函数中修改目标值
实现步骤
- 定位目标线程(Hook检测线程)
- 获取线程句柄(OpenThread)
- 注册异常处理函数(SetUnhandledExceptionFilter)
- 设置硬件断点(修改Dr0-Dr3和Dr7)
- 通过SetThreadContext应用设置
0x04 具体实现
1. 获取关键函数地址
g_fnOpenThread = (FNOPENTHREAD)::GetProcAddress(LoadLibrary("kernel32.dll"), "OpenThread");
g_dwHookAddr = (DWORD)GetProcAddress(GetModuleHandle("user32.dll"), "MessageBoxA");
2. 遍历线程定位目标
HANDLE hTool32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 thread_entry32;
if (Thread32First(hTool32, &thread_entry32)) {
do {
if (thread_entry32.th32OwnerProcessID == GetCurrentProcessId()) {
// 找到目标线程
break;
}
} while (Thread32Next(hTool32, &thread_entry32));
}
3. 获取线程句柄
hHookThread = g_fnOpenThread(
THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,
FALSE,
thread_entry32.th32ThreadID
);
4. 注册异常处理函数
SetUnhandledExceptionFilter(MyExceptionFilter);
5. 异常处理函数实现
LONG WINAPI MyExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
if ((DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress == g_dwHookAddr) {
PCONTEXT pContext = pExceptionInfo->ContextRecord;
ChangeContext(pContext); // 修改上下文
pContext->Eip = (DWORD)&OriginalFunc; // 修改EIP
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
6. 修改上下文函数
void ChangeContext(PCONTEXT pContext) {
char szNewText[] = "SEH Hook successfully";
LPSTR lpOldText = (LPSTR)(*(DWORD*)(pContext->Esp + 0x8)); // 获取MessageBox文本参数
DWORD dwLength = strlen(lpOldText);
DWORD dwOldProtect;
VirtualProtect(lpOldText, dwLength, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(lpOldText, szNewText, dwLength); // 修改文本内容
VirtualProtect(lpOldText, dwLength, dwOldProtect, 0);
}
7. 原始函数跳转
g_dwHookAddrOffset = g_dwHookAddr + 2; // 跳过mov edi,edi
void __declspec(naked) OriginalFunc() {
__asm {
mov edi,edi
jmp [g_dwHookAddrOffset]
}
}
8. 设置硬件断点
CONTEXT threadContext;
threadContext.ContextFlags = CONTEXT_DEBUG_REGISTERS;
threadContext.Dr0 = g_dwHookAddr; // 断点地址
threadContext.Dr7 = 1; // L0=1,其他位为0(1字节执行断点)
SetThreadContext(hHookThread, &threadContext);
CloseHandle(hHookThread);
0x05 技术要点总结
-
硬件断点优势:
- 不修改目标进程内存
- 绕过CRC等内存校验检测
- 基于线程上下文,不影响其他线程
-
关键设置:
- Dr0-Dr3:设置断点地址
- Dr7:控制断点行为
- LENx:断点长度
- R/Wx:断点类型
- Lx/Gx:断点作用域
-
异常处理:
- 检测EXCEPTION_SINGLE_STEP
- 验证异常地址是否为Hook目标
- 修改上下文后继续执行
-
注意事项:
- 每个线程只能设置4个硬件断点
- 需要正确处理线程上下文
- 注意API函数前导指令(如mov edi,edi)
0x06 实际应用效果
- 常规Inline Hook(E8/E9跳转)会被CRC检测拦截
- 使用硬件断点方法:
- 成功注入Hook_SEH.dll
- 未被任何检测机制拦截
- 成功修改MessageBox文本内容
0x07 扩展思考
-
防御措施:
- 检测Dr0-Dr7寄存器异常修改
- 使用更复杂的校验机制
- 监控SetThreadContext等API调用
-
其他应用场景:
- 反调试绕过
- 代码流劫持
- 敏感API监控
-
进阶技术:
- 结合VEH(向量化异常处理)
- 多硬件断点协同工作
- 动态调整断点策略
通过硬件断点实现Hook绕过是一种高级技术,它充分利用了CPU的调试功能,避免了传统Hook方法的内存修改问题,在对抗强检测机制时具有显著优势。