软件调试学习笔记(五)—— 软件断点内存断点
軟件調試學習筆記(五)—— 軟件斷點&內存斷點
- 調試的本質
- 軟件斷點
- 軟件斷點的執行流程
- 分析INT 3執行流程
- 實驗:處理軟件斷點
- 內存斷點
- 內存斷點的執行流程
- 實驗:處理內存斷點
調試的本質
描述:
1)調試的本質是觸發異常與調試器接管異常的過程。
2)不論是軟件斷點,硬件斷點還是INT 3斷點,本質都是觸發異常。
軟件斷點
當使用調試器在任意代碼位置設置斷點時,本質上是將當前代碼位置的字節碼改為0xCC,對應的匯編指令為INT 3。
調試器為了界面的美觀,不會直接在反匯編界面將修改后的數據顯示出來。
可以使用WinHex查看內存數據進行驗證。
軟件斷點的執行流程
被調試進程:
1)CPU檢測到INT 3指令
2)查IDT表找到對應的中斷處理函數
3)CommonDispatchException
4)KiDispatchException
5)DbgkForwardException收集并發送調試事件
最終調用DbgkpSendApiMessage(x, x)
第一個參數:消息類型
第二個參數:是否掛起其它線程
調試器進程:
1)循環判斷
2)取出調試事件
3)列出信息:寄存器、內存
4)用戶處理
分析INT 3執行流程
1)在內核文件中定位IDT表
2)定位3號中斷
3)所有的異常最終都會走向CommonDispatchException
4)再跟入0環異常分發函數KiDispatchException
5)分析KiDispatchException
DbgkForwardException最終通過DbgkpSendApiMessage發送調試事件
實驗:處理軟件斷點
1)編譯并運行以下代碼
#include <stdio.h> #include <windows.h> #include <tlhelp32.h>#define DEBUGGEE "C:\\驅動管理.exe"//被調試進程ID,進程句柄,OEP DWORD dwDebuggeePID = 0;//被調試線程句柄 HANDLE hDebuggeeThread = NULL; HANDLE hDebuggeeProcess = NULL;//系統斷點 BOOL bIsSystemInt3 = TRUE;//被INT 3覆蓋的數據 CHAR OriginalCode = 0;//線程上下文 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; }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. 顯示反匯編代碼、寄存器等//6. 等待用戶命令while(bRet == FALSE){bRet = WaitForUserCommand();}return bRet; }BOOL AccessExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = TRUE;return bRet; }BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo) {BOOL bRet = TRUE;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) {ReadProcessMemory(hDebuggeeProcess, addr, &OriginalCode, 1, NULL);BYTE int3[1] = { 0xcc };WriteProcessMemory(hDebuggeeProcess, addr, int3, 1, NULL); }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:SetInt3BreakPoint((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; }運行結果:
2)輸入g并回車,使程序繼續運行
內存斷點
描述:當需要在某塊內存被訪問時產生中斷,可以使用內存斷點。
內存斷點能夠分為兩種類型:
原理:VirtualProtectEx
BOOL VirtualProtectEx(HANDLE hProcess, // handle to processLPVOID lpAddress, // region of committed pagesSIZE_T dwSize, // size of regionDWORD flNewProtect, // desired access protectionPDWORD lpflOldProtect // old protection );內存訪問:將指定內存的屬性修改為PAGE_NOACCESS(修改后,PTE的P位等于0)
內存寫入:將指定內存的屬性修改為PAGE_EXECUTE_READ(修改后,PTE的P位等于1,R/W位等于0)
內存斷點的執行流程
被調試進程:
1)CPU訪問錯誤的內存地址,觸發頁異常
2)查IDT表找到對應的中斷處理函數(nt!_KiTrap0E)
3)CommonDispatchException
4)KiDispatchException
5)DbgkForwardException收集并發送調試事件
最終調用DbgkpSendApiMessage(x, x)
第一個參數:消息類型,共有7種類型
第二個參數:是否掛起其它線程
調試器進程:
1)循環判斷
2)取出調試事件
3)列出消息(寄存器/內存)
4)用戶處理
實驗:處理內存斷點
#include <stdio.h> #include <windows.h> #include <tlhelp32.h>#define DEBUGGEE "C:\\驅動管理.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; }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. 顯示反匯編代碼、寄存器等//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("內存斷點: %x %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 = TRUE;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);//2. 寫入斷點//VirtualProtectEx(hDebuggeeProcess, pAddress, 1, PAGE_EXECUTE_READ, &dwOriginalProtect); }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; }運行結果:
2)輸入g并回車,使程序繼續運行
總結
以上是生活随笔為你收集整理的软件调试学习笔记(五)—— 软件断点内存断点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件调试学习笔记(四)—— 异常的处理流
- 下一篇: 软件调试学习笔记(六)—— 硬件断点