-
与软件断点与内存断点不同,硬件断点不依赖被调试程序,而是依赖于CPU中的调试寄存器。
-
调试寄存器有7个,分别为Dr0~Dr7。
-
用户最多能够设置4个硬件断点,这是由于只有Dr0~Dr3用于存储线性地址。
-
其中,Dr4和Dr5是保留的。
假如在Dr0寄存器中写入线性地址,是否所有线程都会受影响?其实不会,每个线程都拥有一份独立的寄存器,切换线程时,寄存器的值也会被切换。
设置硬件断点
Dr0~Dr3用于设置硬件断点,由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点。在7个寄存器里面,Dr7是最重要的寄存器
L0/G0 ~ L3/G3:控制Dr0~Dr3是否有效,局部还是全局;每次异常后,Lx都被清零,Gx不清零。
若Dr0有效,L0=1则为局部,G0=1则为全局,以此类推
断点长度(LENx):00(1字节)、01(2字节)、11(4字节)
通过DR7的LEN控制
断点类型(R/Wx):00(执行断点)、01(写入断点)、11(访问断点)
流程
被调试进程:
1)CPU执行时检测当前线性地址与调试寄存器(Dr0~Dr3)中的线性地址相等。
2)查IDT表找到对应的中断处理函数(
nt!_KiTrap01
)3)CommonDispatchException
4)KiDispatchException
5)DbgkForwardException收集并发送调试事件
最终调用DbgkpSendApiMessage(x, x)
,第一个参数:消息类型,第二个参数:是否挂起其它线程
调试器进程:
1)循环判断
2)取出调试事件
3)列出信息:寄存器、内存
4)用户处理
处理硬件断点
1)硬件调试断点产生的异常是 STATUS_SINGLE_STEP
(单步异常) 2)检测Dr6寄存器的B0~B3:哪个寄存器触发的异常
这里硬件断点有两种情况,一种情况是dr0-dr3寄存器引发的异常,另外一种情况就是TF=1
引发的异常
这里如果是DR0寄存器引发的异常,那么B0=1,以此类推,如果是TF=1
引发的异常,那么DR6的低4位为全0
首先看一下异常处理函数
之前我们是在创建进程的时候进行断点,但是因为硬件断点需要在线程创建完成之后,设置在被调试程序的上下文中
因此当被调试程序触发调试器设置的INT 3断点时,此时设置硬件断点较为合理
再就是硬件断点的代码,这里把Dr0寄存器置1,然后把16、17为置0为执行断点,异常长度为1字节(18、19位置0),地址的话就是int3
断点的地址+1
完整代码如下
// Debug4.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>#define DEBUGGEE "C:\\ipmsg.exe"//被调试进程ID,进程句柄,OEP
DWORD dwDebuggeePID = 0;//被调试线程句柄
HANDLE hDebuggeeThread = NULL;
HANDLE hDebuggeeProcess = NULL;//系统断点
BOOL bIsSystemInt3 = TRUE;//被INT 3覆盖的数据
CHAR OriginalCode = 0;//原始内存属性
DWORD dwOriginalProtect;//线程上下文
CONTEXT Context;typedef HANDLE (__stdcall *FnOpenThread) (DWORD, BOOL, DWORD);VOID InitDebuggeeInfo(DWORD dwPID, HANDLE hProcess)
{dwDebuggeePID = dwPID;hDebuggeeProcess = hProcess;
}DWORD GetProcessId(LPTSTR lpProcessName)
{HANDLE hProcessSnap = NULL;PROCESSENTRY32 pe32 = {0};hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if(hProcessSnap == (HANDLE)-1){return 0;}pe32.dwSize = sizeof(PROCESSENTRY32);if(Process32First(hProcessSnap, &pe32)){do {if(!strcmp(lpProcessName, pe32.szExeFile))return (int)pe32.th32ProcessID;} while (Process32Next(hProcessSnap, &pe32));}else{CloseHandle(hProcessSnap);}return 0;
}BOOL WaitForUserCommand()
{BOOL bRet = FALSE;CHAR command;printf("COMMAND>");command = getchar();switch(command){case 't':bRet = TRUE;break;case 'p':bRet = TRUE;break;case 'g':bRet = TRUE;break;}getchar();return bRet;
}VOID SetHardBreakPoint(PVOID pAddress)
{//1. 获取线程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 设置断点位置Context.Dr0 = (DWORD)pAddress;Context.Dr7 |= 1;//3. 设置断点长度和类型Context.Dr7 &= 0xfff0ffff; //执行断点(16、17位 置0) 1字节(18、19位 置0)//5. 设置线程上下文SetThreadContext(hDebuggeeThread, &Context);
}BOOL Int3ExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo)
{BOOL bRet = FALSE;//1. 将INT 3修复为原来的数据(如果是系统断点,不用修复)if(bIsSystemInt3){bIsSystemInt3 = FALSE;return TRUE;}else{WriteProcessMemory(hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1, NULL);}//2. 显示断点位置printf("Int 3断点:0x%p \r\n", pExceptionInfo->ExceptionRecord.ExceptionAddress);//3. 获取线程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//4. 修正EIPContext.Eip--;SetThreadContext(hDebuggeeThread, &Context);//5. 显示反汇编代码、寄存器等/*硬件断点需要设置在被调试进程的的线程上下文中。因此当被调试程序触发调试器设置的INT 3断点时,此时设置硬件断点较为合理。*/SetHardBreakPoint((PVOID)((DWORD)pExceptionInfo->ExceptionRecord.ExceptionAddress+1));//6. 等待用户命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet;
}BOOL AccessExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo)
{BOOL bRet = FALSE;DWORD dwAccessFlag; //访问类型 0为读 1为写DWORD dwAccessAddr; //访问地址DWORD dwProtect; //内存属性//1. 获取异常信息,修改内存属性dwAccessFlag = pExceptionInfo->ExceptionRecord.ExceptionInformation[0];dwAccessAddr = pExceptionInfo->ExceptionRecord.ExceptionInformation[1];printf("内存断点 : dwAccessFlag - %x dwAccessAddr - %x \n", dwAccessFlag, dwAccessAddr);VirtualProtectEx(hDebuggeeProcess, (VOID*)dwAccessAddr, 1, dwOriginalProtect, &dwProtect);//2. 获取线程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//3. 修正EIP(内存访问异常,不需要修正EIP)printf("Eip: 0x%p \n", Context.Eip);//4. 显示汇编/寄存器等信息//5. 等待用户命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet;
}BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo)
{BOOL bRet = FALSE;//1. 获取线程上下文Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;GetThreadContext(hDebuggeeThread, &Context);//2. 判断是否是硬件断点导致的异常if(Context.Dr6 & 0xF) //B0~B3不为空 硬件断点{//2.1 显示断点信息printf("硬件断点:%x 0x%p \n", Context.Dr7&0x00030000, Context.Dr0);//2.2 将断点去除Context.Dr0 = 0;Context.Dr7 &= 0xfffffffe;}else //单步异常{//2.1 显示断点信息printf("单步:0x%p \n", Context.Eip);//2.2 将断点去除Context.Dr7 &= 0xfffffeff;}SetThreadContext(hDebuggeeThread, &Context);//6. 等待用户命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet;
}BOOL ExceptionHandler(DEBUG_EVENT *pDebugEvent)
{ BOOL bRet = TRUE;EXCEPTION_DEBUG_INFO *pExceptionInfo = NULL;pExceptionInfo = &pDebugEvent->u.Exception;//得到线程句柄,后面要用FnOpenThread MyOpenThread = (FnOpenThread)GetProcAddress(LoadLibrary("kernel32.dll"), "OpenThread");hDebuggeeThread = MyOpenThread(THREAD_ALL_ACCESS, FALSE, pDebugEvent->dwThreadId);switch(pExceptionInfo->ExceptionRecord.ExceptionCode){//INT 3异常case EXCEPTION_BREAKPOINT:bRet = Int3ExceptionProc(pExceptionInfo);break;//访问异常case EXCEPTION_ACCESS_VIOLATION:bRet = AccessExceptionProc(pExceptionInfo);break;//单步执行case EXCEPTION_SINGLE_STEP:bRet = SingleStepExceptionProc(pExceptionInfo);break;}return bRet;
}VOID SetInt3BreakPoint(LPVOID addr)
{CHAR int3 = 0xCC;//1. 备份ReadProcessMemory(hDebuggeeProcess, addr, &OriginalCode, 1, NULL);//2. 修改WriteProcessMemory(hDebuggeeProcess, addr, &int3, 1, NULL);
}VOID SetMemBreakPoint(PCHAR pAddress)
{//1. 访问断点VirtualProtectEx(hDebuggeeProcess, pAddress, 1, PAGE_NOACCESS, &dwOriginalProtect); //PTE P=0//2. 写入断点//VirtualProtectEx(hDebuggeeProcess, pAddress, 1, PAGE_EXECUTE_READ, &dwOriginalProtect); //PTE R/W=0
}int main(int argc, char* argv[])
{BOOL nIsContinue = TRUE;DEBUG_EVENT debugEvent = {0};BOOL bRet = TRUE;DWORD dwContinue = DBG_CONTINUE;//1.创建调试进程STARTUPINFO startupInfo = {0};PROCESS_INFORMATION pInfo = {0};GetStartupInfo(&startupInfo);bRet = CreateProcess(DEBUGGEE, NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &pInfo);if(!bRet){printf("CreateProcess error: %d \n", GetLastError());return 0;}hDebuggeeProcess = pInfo.hProcess;//2.调试循环while(nIsContinue){bRet = WaitForDebugEvent(&debugEvent, INFINITE);if(!bRet){printf("WaitForDebugEvent error: %d \n", GetLastError());return 0;}switch(debugEvent.dwDebugEventCode){//1.异常case EXCEPTION_DEBUG_EVENT:bRet = ExceptionHandler(&debugEvent);if(!bRet)dwContinue = DBG_EXCEPTION_NOT_HANDLED;break;//2.case CREATE_THREAD_DEBUG_EVENT:break;//3.创建进程case CREATE_PROCESS_DEBUG_EVENT://int3 断点SetInt3BreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);//内存断点//SetMemBreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);break;//4.case EXIT_THREAD_DEBUG_EVENT:break;//5.case EXIT_PROCESS_DEBUG_EVENT:
break;//6.case LOAD_DLL_DEBUG_EVENT:break;//7.case UNLOAD_DLL_DEBUG_EVENT:break;//8.case OUTPUT_DEBUG_STRING_EVENT:break;}bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);}return 0;
}
实现效果如下