软件调试学习笔记(五)—— 软件断点&内存断点

  • 调试的本质
  • 软件断点
    • 软件断点的执行流程
    • 分析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并回车,使程序继续运行

内存断点

描述:当需要在某块内存被访问时产生中断,可以使用内存断点。

内存断点能够分为两种类型:

  1. 内存访问:内存被读写时产生中断。
  2. 内存写入:内存被写入时产生中断。

原理: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并回车,使程序继续运行

软件调试学习笔记(五)—— 软件断点内存断点相关推荐

  1. 软件调试学习笔记(六)—— 硬件断点

    软件调试学习笔记(六)-- 硬件断点 硬件断点 设置硬件断点 触发硬件断点 处理硬件断点 实验:硬件断点的设置与处理 硬件断点 描述: 与软件断点与内存断点不同,硬件断点不依赖被调试程序,而是依赖于C ...

  2. 软件调试学习笔记(七)—— 单步步入单步步过

    软件调试学习笔记(七)-- 单步步入&单步步过 单步步入 设置单步异常 处理单步异常 实验1:单步异常的设置与处理 单步步过 实现思路 实验2:实现单步步过 单步步入 描述: 单步步入的实现依 ...

  3. 软件调试学习笔记(四)—— 异常的处理流程

    软件调试学习笔记(四)-- 异常的处理流程 要点回顾 异常的处理流程 实验1:理解调试器与异常的关系 未处理异常:最后一道防线 实验2:理解UnhandledExceptionFilter执行流程 实 ...

  4. 软件调试学习笔记(三)—— 调试事件的处理

    软件调试学习笔记(三)-- 调试事件的处理 要点回顾 调试事件的处理 实验一:实现简单调试器(创建进程) 实验二:分析异常来源 实验三:实现简单调试器(附加进程) 实验四:分析NtDebugActiv ...

  5. 软件调试学习笔记(二)—— 调试事件的采集

    软件调试学习笔记(二)-- 调试事件的采集 要点回顾 调试事件的种类 调试事件采集函数 例:分析PspUserThreadStartup 例:分析PspExitThread 总结 要点回顾 调试器与被 ...

  6. 软件调试学习笔记(一)—— 调试对象

    软件调试学习笔记(一)-- 调试对象 准备工作 调试器与被调试程序 DebugActiveProcess 连接调试器 分析kernel32!DebugActiveProcess 分析ntdll!Dbg ...

  7. TDDFT计算软件Octopus学习笔记(五):介电函数和吸收谱(ZnO)

    本文进行光学性质的计算,以ZnO的介电函数为例.Octopus以时间依赖密度泛函理论(TDDFT)为理论基础,对材料施加一定的外界扰动,通过一定时间的传播,搜集相应的响应数据,进而得出各种光学性质. ...

  8. Windows软件调试学习笔记(1)

    --WINDBG中的表达式 WINDBG接受两种表达式,C++表达式和MASM表达式. 1. MASM表达式中的数值: MASM表达式中的数值可以基于16,10,8,2四种进制.用n命令可以设置WIN ...

  9. 【软考中级】软件设计师学习笔记

    软件设计师学习笔记 计算机系统知识 程序语言设计 数据结构 操作系统 软件工程基础知识 结构化开发方法 创建型设计模式 行为型设计模式 结构型设计模式: 算法设计与分析 数据库技术基础 网络与信息安全 ...

最新文章

  1. EXCEL基础篇(二)
  2. python下什么-python官网下的包是干什么用的?
  3. Android Studio之提示Unable to delete directory ‘*****\MyApplication\app\build‘
  4. Python--协程(gevent模块)
  5. Gem5在全系统(FS)模式下运行自己的测试程序
  6. hadoop项目实战--ETL--(三)实现mysql表到HIVE表的全量导入与增量导入
  7. SAP License:SAP Netweaver
  8. TrueCommand是什么
  9. 开发自测,到底该从哪里做起?
  10. C++课后习题第五章17
  11. linux 常用压缩命令,Linux常用的压缩及解压缩命令
  12. 【English】20190430
  13. POE 网络变压器 Pulse 普思
  14. 新物种IMO:踩在腾讯和阿里之间
  15. PDF文档签名证书帮助您签署可信的电子合同
  16. .net 下如何将文档文件(Word, Pdf等) 中的文本提取出来
  17. D3D管线以及着色器工作原理-画一个三角形
  18. 【数据结构】共享栈详解 判断共享栈满条件栈顶指针变化详解记忆方法例题
  19. 支持向量机SVM(二)
  20. pci数据捕获和信号处理_通过更改数据捕获处理上游数据更改

热门文章

  1. Blockchain:《Blockchain applications in insurance》Deloitte—德勤区块链技术研究报告正文版—听课记录
  2. Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(Find/undo事件)
  3. C/C++调试:gdbserver的简单使用
  4. 基于Apache Thrift的公路涵洞数据交互实现原理
  5. 总结 | 如何测试你自己的 RubyGem
  6. 关于串口接收数据不全的问题
  7. const的用法,特别是用在函数前面与后面的区别
  8. STM32F103CB IAP+APP BIN文件合并烧写
  9. 波卡链Substrate (1)生态介绍
  10. 百度超级链XChain(2)p2p网络