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了,不能设置的问题,