C/C++ 自写补丁-设置硬件断点 x86_x64

20241126 增加DLL,更新搜索算法极端情况搜索

硬件断点

描述: 1、与软件断点与内存断点不同,硬件断点不依赖被调试程序,而是依赖于CPU中的调试寄存器。 2、调试寄存器有8个,分别为Dr0-Dr7。 3、用户最多能够设置4个硬件断点,这是由于只有Dr0~Dr3用于存储线性地址。 4、其中,Dr4和Dr5是保留的。

设置硬件断点,硬件断点设置一点不友好,使用时有不大不小的坑

1)Dr0-Dr3用于设置硬件断点,由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点。 2)Dr7是最重要的寄存器: L0/G0 ~ L3/G3:控制Dr0-Dr3是否有效,局部还是全局;每次异常后,Lx都被清零,Gx不清零。 断点长度(LENx):00(1字节)、01(2字节)、11(4字节)、10(8字节) 断点类型(R/Wx):00(执行断点)、01(写入断点)、11(访问断点)

处理硬件断点

1)硬件调试断点产生的异常是 STATUS_SINGLE_STEP(单步异常) 2)检测Dr6寄存器的B0~B3:哪个寄存器触发的异常

硬件断点的设置与处理

硬件断点设置与线程、寄存器有关,并且还有读、写、访问、字节大小等一些设置,下面用代码实现这一系列设置和删除的操作,创建一个HardwareBreakpoint.cpp,写如下内容:

#include "HardwareBreakpoint.h"
#include <vector>
#include <TlHelp32.h>

#define BITSET(a,x) (a|=1<<x)
#define BITGET(a,x) (a&(1<<x))

enum HWBP_MODE
{
    MODE_DISABLED = 0,    //00
    MODE_LOCAL = 1,       //01
    MODE_GLOBAL = 2       //10
};
struct DR7
{
    BYTE HWBP_MODE[4];
    BYTE HWBP_TYPE[4];
    BYTE HWBP_SIZE[4];
};
class HardwareBreakpoint
{
public:

    ULONG_PTR hwbpAddr;   // 断点位置
    int threadId;         // 线程ID
    HWBP_DRX Drx;         // 第几个寄存器
    HWBP_TYPE Type;       // 读、写、执行
    HWBP_SIZE Size;       // 字节
    HardwareBreakpoint()  // 初始化
    {
        hwbpAddr = 0;
        threadId = 0;
        Drx = Dr0;
        Type = TYPE_EXECUTE;
        Size = SIZE_1;
    }

    bool operator ==(const HardwareBreakpoint& p)
    {
        return hwbpAddr == p.hwbpAddr;
    }
};
ULONG_PTR dr7uint(DR7* dr7)
{
    ULONG_PTR ret = 0;
    if (BITGET(dr7->HWBP_MODE[0], 0))
        BITSET(ret, 0);
    if (BITGET(dr7->HWBP_MODE[0], 1))
        BITSET(ret, 1);
    if (BITGET(dr7->HWBP_MODE[1], 0))
        BITSET(ret, 2);
    if (BITGET(dr7->HWBP_MODE[1], 1))
        BITSET(ret, 3);
    if (BITGET(dr7->HWBP_MODE[2], 0))
        BITSET(ret, 4);
    if (BITGET(dr7->HWBP_MODE[2], 1))
        BITSET(ret, 5);
    if (BITGET(dr7->HWBP_MODE[3], 0))
        BITSET(ret, 6);
    if (BITGET(dr7->HWBP_MODE[3], 1))
        BITSET(ret, 7);
    if (BITGET(dr7->HWBP_TYPE[0], 0))
        BITSET(ret, 16);
    if (BITGET(dr7->HWBP_TYPE[0], 1))
        BITSET(ret, 17);
    if (BITGET(dr7->HWBP_SIZE[0], 0))
        BITSET(ret, 18);
    if (BITGET(dr7->HWBP_SIZE[0], 1))
        BITSET(ret, 19);
    if (BITGET(dr7->HWBP_TYPE[1], 0))
        BITSET(ret, 20);
    if (BITGET(dr7->HWBP_TYPE[1], 1))
        BITSET(ret, 21);
    if (BITGET(dr7->HWBP_SIZE[1], 0))
        BITSET(ret, 22);
    if (BITGET(dr7->HWBP_SIZE[1], 1))
        BITSET(ret, 23);
    if (BITGET(dr7->HWBP_TYPE[2], 0))
        BITSET(ret, 24);
    if (BITGET(dr7->HWBP_TYPE[2], 1))
        BITSET(ret, 25);
    if (BITGET(dr7->HWBP_SIZE[2], 0))
        BITSET(ret, 26);
    if (BITGET(dr7->HWBP_SIZE[2], 1))
        BITSET(ret, 27);
    if (BITGET(dr7->HWBP_TYPE[3], 0))
        BITSET(ret, 28);
    if (BITGET(dr7->HWBP_TYPE[3], 1))
        BITSET(ret, 29);
    if (BITGET(dr7->HWBP_SIZE[3], 0))
        BITSET(ret, 30);
    if (BITGET(dr7->HWBP_SIZE[3], 1))
        BITSET(ret, 31);
    return ret;
}

// 保存所有下断点的线程ID和相应寄存器
std::vector<HardwareBreakpoint> hwbrk;
// 设置硬件断点,在线程里调用
int RunHwBreakpoint(HANDLE HThread, HWBP_DRX Drx, HWBP_TYPE Type, HWBP_SIZE Size, ULONG_PTR breakpoint)
{
    SuspendThread(HThread);
    CONTEXT context = { 0 };
    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(HThread, &context);

    if (Drx == Dr0) { context.Dr0 = breakpoint; }
    if (Drx == Dr1) { context.Dr1 = breakpoint; }
    if (Drx == Dr2) { context.Dr2 = breakpoint; }
    if (Drx == Dr3) { context.Dr3 = breakpoint; }

    HWBP_MODE hwbpMode;
    DR7 dr7;
    hwbpMode = MODE_LOCAL;
    dr7.HWBP_MODE[Drx] = hwbpMode;
    dr7.HWBP_TYPE[Drx] = Type;
    dr7.HWBP_SIZE[Drx] = Size;
    context.Dr7 |= dr7uint(&dr7);

    SetThreadContext(HThread, &context);
    ResumeThread(HThread);
    // 将当前断点信息存入 vector
    HardwareBreakpoint* h = new HardwareBreakpoint();
    h->hwbpAddr = breakpoint;
    h->threadId = GetThreadId(HThread);
    h->Drx = Drx;
    hwbrk.push_back(*h);
    delete[] h;
    h = NULL;
    return 1;
}

DWORD WINAPI BPThreadProc(LPVOID lpParameter)
{
    HardwareBreakpoint* h = (HardwareBreakpoint*)lpParameter;
    if (!h)return 0;

    if (h->threadId) {
        //方式一:对指定的线程下硬件断点
        HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, false, h->threadId);
        if (hThread)
        {
            RunHwBreakpoint(hThread, h->Drx, h->Type, h->Size, h->hwbpAddr);  //下硬件断点
            CloseHandle(hThread);
            return 1;
        }
    }
    else
    {
        //方式二:对所有线程下硬件断点,加壳程序容易出错
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
        if (hSnap != INVALID_HANDLE_VALUE)
        {
            THREADENTRY32 te;
            te.dwSize = sizeof(THREADENTRY32);
            if (Thread32First(hSnap, &te))
            {
                do
                {    // 因为线程会挂起,排除自己的线程
                    if (te.th32OwnerProcessID == GetCurrentProcessId() && te.th32ThreadID != GetCurrentThreadId())
                    {
                        HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, false, te.th32ThreadID);
                        if (hThread)
                        {
                            RunHwBreakpoint(hThread, h->Drx, h->Type, h->Size, h->hwbpAddr);  //下硬件断点
                            CloseHandle(hThread);
                        }
                    }
                } while (Thread32Next(hSnap, &te));
            }
        }
        if (hSnap) CloseHandle(hSnap);
    }
    return 1;
}

// 设置硬件断点
ULONG_PTR SetHwBreakpoint(int ThreadId, HWBP_DRX Drx, HWBP_TYPE Type, HWBP_SIZE Size, ULONG_PTR breakpoint)
{
    HardwareBreakpoint* h = new HardwareBreakpoint();
    h->threadId = ThreadId;
    h->Drx = Drx;
    h->Type = Type;
    h->Size = Size;
    h->hwbpAddr = breakpoint;

    HANDLE handle = CreateThread(NULL, NULL, BPThreadProc, (LPVOID)h, NULL, NULL);
    WaitForSingleObject(handle, INFINITE); //等待异常消息
    CloseHandle(handle);
    delete[] h;
    h = NULL;
    return breakpoint;
}

// 删除硬件断点 DelHwBreakpoint(ExceptionInfo->ContextRecord, 0x123456);
bool DelHwBreakpoint(PCONTEXT context, ULONG_PTR breakpoint)
{
    if (hwbrk.size() <= 0) return false;
    for (auto& h : hwbrk)
    {
        if (h.hwbpAddr == breakpoint)
        {
            int FlagBit = 0;
            if (h.Drx == Dr0)
            {
                FlagBit = 0;
                context->Dr0 = 0;
            }
            if (h.Drx == Dr1)
            {
                FlagBit = 2;
                context->Dr1 = 0;
            }
            if (h.Drx == Dr2)
            {
                FlagBit = 4;
                context->Dr2 = 0;
            }
            if (h.Drx == Dr3)
            {
                FlagBit = 6;
                context->Dr3 = 0;
            }
            context->Dr7 &= ~(1 << FlagBit);
            context->Dr7 &= ~(1 << (FlagBit + 1));

            // 删除用过的元素
            for (int i = hwbrk.size() - 1; i >= 0; i--) {
                if (hwbrk[i] == h) {
                    hwbrk.erase(hwbrk.begin() + i);
                }
            }
            return true;
        }
    }
    return false;
}

再创建一个HardwareBreakpoint.h文件,写如下内容:

#pragma once
#include <Windows.h>

enum HWBP_DRX
{
    Dr0 = 0,
    Dr1 = 1,
    Dr2 = 2,
    Dr3 = 3
};
enum HWBP_TYPE
{
    TYPE_EXECUTE = 0,    //00  执行
    TYPE_WRITE = 1,      //01  写
    TYPE_READWRITE = 3   //11  读/写
};

enum HWBP_SIZE
{
    SIZE_1 = 0,         //00
    SIZE_2 = 1,         //01
    SIZE_8 = 2,         //10
    SIZE_4 = 3          //11
};

/************************************************************************
函数名称: SetHwBreakpoint
函数功能: 设置线程硬件断点
输入参数: ThreadId 线程ID,0为所有线程下断,否则指定线程下断
          Drx:     参考 enum HWBP_DRX
          Type:    参考 enum HWBP_TYPE
          Size:    参考 enum HWBP_SIZE
          dwAddr   断点位置
返    回: 当前断点位置
调    用: SetHwBreakpoint(GetCurrentThreadId(), Dr0, TYPE_EXECUTE, SIZE_1, bp1);
/************************************************************************/
ULONG_PTR SetHwBreakpoint(int ThreadId, HWBP_DRX Drx, HWBP_TYPE Type, HWBP_SIZE Size, ULONG_PTR dwAddr);

/************************************************************************
函数名称: DelHwBreakpoint
函数功能: 删除硬件断点
输入参数: context  PCONTEXT指针:ExceptionInfo->ContextRecord
          dwAddr   断点地址:0x123456
返    回: 成功返回ture,否则返回false
调    用: DelHwBreakpoint(ExceptionInfo->ContextRecord, 0x123456);
/************************************************************************/
bool DelHwBreakpoint(PCONTEXT context, ULONG_PTR dwAddr);

最后,调用示例如下:

// HWBP_Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "HardwareBreakpoint.h"

using namespace std;

ULONG_PTR bp1, bp2, bp3;

LONG NTAPI ExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
    cout << "异常地址:" << ExceptionInfo->ExceptionRecord->ExceptionAddress << endl;

    if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == bp2)
    {
        cout << "bp2异常" << endl;
        //DelHwBreakpoint(ExceptionInfo->ContextRecord, bp2);
        ExceptionInfo->ContextRecord->EFlags |= 1 << 16; //0x10000
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == bp3)
    {
        cout << "bp3异常" << endl;
        //DelHwBreakpoint(ExceptionInfo->ContextRecord, bp3);
        ExceptionInfo->ContextRecord->EFlags |= 1 << 16; //0x10000
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {//STATUS_SINGLE_STEP
        cout << "写入异常" << endl;
        DelHwBreakpoint(ExceptionInfo->ContextRecord, bp1);
        //ExceptionInfo->ContextRecord->EFlags |= 1 << 16; //0x10000
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
    AddVectoredExceptionHandler(1, ExceptionHandler);

    std::string str = "123456789";
    bp1 = (ULONG_PTR)str.c_str();
    bp2 = (ULONG_PTR)GetProcAddress(GetModuleHandle(L"user32.dll"), "IsWindow");
    bp3 = (ULONG_PTR)GetProcAddress(GetModuleHandle(L"user32.dll"), "GetWindow");

    cout << hex << "bp1:" << bp1 << endl;
    SetHwBreakpoint(0, Dr0, TYPE_WRITE, SIZE_4, bp1); //设置硬件写入断点
    SetHwBreakpoint(0, Dr1, TYPE_EXECUTE, SIZE_1, bp2);//设置硬件执行断点
    SetHwBreakpoint(0, Dr2, TYPE_EXECUTE, SIZE_1, bp3);//设置硬件执行断点

    *(BYTE*)(bp1 + 1) = 0x35;  //模拟写入数据
    IsWindow(NULL);//模拟调用函数
    GetWindow(NULL, 1);//模拟调用函数

    *(BYTE*)(bp1 + 1) = 0x36; //模拟写入数据
    IsWindow(NULL);//模拟调用函数
    GetWindow(NULL, 1);//模拟调用函数

    system("pause");
    return 0;
}

执行效果:

工程源码使用VS2022 C++14

也可使用HijackTingMix.zip静态库,库里为加强版避免了GetThreadContext和SetThreadContext被hook了,不能设置的问题,