在windows里面触发异常主要通过三种方式:软件断点、内存断点、硬件断点来实现,本文对这三种方式进行原理分析,通过自己构造代码来实现调试器的效果。
软件断点 当在调试器下一个断点,其实就是把这行汇编语句的硬编码改为CC,即int 3
被调试进程
1 2 3 4 5 1. CPU检测到INT 3 指令2. 查IDT表找到对应的函数3. CommonDispatchException4. KiDispatchException5. DbgkForwardException收集并发送调试事件
首先找到IDT表的3号中断
调用CommonDispatchException
通过KiDispatchException
分发异常
首先用KeContextFromframes
备份,若为用户调用则跳转
进入函数如果没有内核调试器则跳转,也就是说如果有内核调试器的存在,3环调试器是接收不到异常的
然后调用调试事件
DbgkForwardException
主要是通过DbgkpSendApiMessage
来发送调试事件,第二个参数决定线程是否挂起,首先通过cmp判断,如果为0则直接跳转,如果不为0则调用DbgkpSuspendProcess
将被调试进程挂起
也就是说如果要想调试进程,就必须要调用DbgkpSuspendProcess
将调试进程挂起
首先用调试模式创建进程,然后使用调试循环
如果是异常事件则调用ExceptionHandler
ExceptionHandler
主要是通过判断ExcptionRecord
结构里面的ExceptionCode
来判断异常的类型,然后调用相应的函数,这里首先看软件断点,即int 3
,调用Int3ExceptionProc
下断点会把之前的指令修改为CC
,如果不是系统断点,就把下断点的位置修改的指令写回去,然后获取int3
断点的地址
然后获取上下文,所有调试寄存器都存储在ContextFlags
里面
当我们下软件断点的时候,EIP并不会停留在断点的地方,而是会停留在断点+1的地方(这里不同的异常EIP停留的位置不同),所以这里需要进行EIP-1的操作
然后调用处理的函数
当被调试进程收集并发送调试事件之后就会处于阻塞状态,根据异常处理的结果决定下一步的执行
实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 #include "stdafx.h" #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #define DEBUGGEE "C:\\ipmsg.exe" DWORD dwDebuggeePID = 0 ; HANDLE hDebuggeeThread = NULL ; HANDLE hDebuggeeProcess = NULL ; BOOL bIsSystemInt3 = TRUE; 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; if (bIsSystemInt3) { bIsSystemInt3 = FALSE; return TRUE; } else { WriteProcessMemory (hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1 , NULL ); } printf ("Int 3断点 : 0x%p \r\n" , pExceptionInfo->ExceptionRecord.ExceptionAddress); Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); Context.Eip--; SetThreadContext (hDebuggeeThread, &Context); 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) { 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 ); } BOOL ExceptionTest () { BOOL nIsContinue = TRUE; DEBUG_EVENT debugEvent = {0 }; BOOL bRet = TRUE; DWORD dwContinue = DBG_CONTINUE; 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; while (nIsContinue) { bRet = WaitForDebugEvent (&debugEvent, INFINITE); if (!bRet) { printf ("WaitForDebugEvent error: %d \n" , GetLastError ()); return 0 ; } switch (debugEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: bRet = ExceptionHandler (&debugEvent); if (!bRet) dwContinue = DBG_EXCEPTION_NOT_HANDLED; break ; case CREATE_THREAD_DEBUG_EVENT: break ; case CREATE_PROCESS_DEBUG_EVENT: SetInt3BreakPoint ((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress); break ; case EXIT_THREAD_DEBUG_EVENT: break ; case EXIT_PROCESS_DEBUG_EVENT: break ; case LOAD_DLL_DEBUG_EVENT: break ; case UNLOAD_DLL_DEBUG_EVENT: break ; case OUTPUT_DEBUG_STRING_EVENT: break ; } bRet = ContinueDebugEvent (debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE); } return 0 ; } int main (int argc, char * argv[]) { ExceptionTest (); return 0 ; }
实现效果
内存断点 描述:当需要在某块内存被访问时产生中断,可以使用内存断点。
内存断点能够分为两种类型:
内存访问:内存被读写时产生中断 内存写入:内存被写入时产生中断
原理:VirtualProtectEx
1 2 3 4 5 6 7 BOOL VirtualProtectEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ) ;
flNewProtect
内存访问:将指定内存的属性修改为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)用户处理
在创建进程的地方使用内存断点
通过修改PTE的P=0来设置页不可访问
我们首先看一下EXCEPTION_DEBUG_INFO
结构
然后再看ExceptionRecord
定位到_EXCEPTION_RECORD
到msdn里面看一下EXCEPTION_RECORD
,这里主要关注ExceptionInformation
如果这个值为0有线程试图读这块内存,如果这个值为1则有线程试图写这块内存
这里显示出异常的信息,打印异常类型和异常地址
内存断点的EIP就是原EIP,不需要进行减的操作
实现代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 #include "stdafx.h" #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #define DEBUGGEE "C:\\ipmsg.exe" DWORD dwDebuggeePID = 0 ; HANDLE hDebuggeeThread = NULL ; HANDLE hDebuggeeProcess = NULL ; BOOL bIsSystemInt3 = TRUE; 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; if (bIsSystemInt3) { bIsSystemInt3 = FALSE; return TRUE; } else { WriteProcessMemory (hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1 , NULL ); } printf ("Int 3断点 : 0x%p \r\n" , pExceptionInfo->ExceptionRecord.ExceptionAddress); Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); Context.Eip--; SetThreadContext (hDebuggeeThread, &Context); while (bRet == FALSE) { bRet = WaitForUserCommand (); } return bRet; } BOOL AccessExceptionProc (EXCEPTION_DEBUG_INFO *pExceptionInfo) { BOOL bRet = FALSE; DWORD dwAccessFlag; DWORD dwAccessAddr; DWORD dwProtect; 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); Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); printf ("Eip: 0x%p \n" , Context.Eip); 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) { 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 ; ReadProcessMemory (hDebuggeeProcess, addr, &OriginalCode, 1 , NULL ); WriteProcessMemory (hDebuggeeProcess, addr, &int3, 1 , NULL ); } VOID SetMemBreakPoint (PCHAR pAddress) { VirtualProtectEx (hDebuggeeProcess, pAddress, 1 , PAGE_NOACCESS, &dwOriginalProtect); } int main (int argc, char * argv[]) { BOOL nIsContinue = TRUE; DEBUG_EVENT debugEvent = {0 }; BOOL bRet = TRUE; DWORD dwContinue = DBG_CONTINUE; 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; while (nIsContinue) { bRet = WaitForDebugEvent (&debugEvent, INFINITE); if (!bRet) { printf ("WaitForDebugEvent error: %d \n" , GetLastError ()); return 0 ; } switch (debugEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: bRet = ExceptionHandler (&debugEvent); if (!bRet) dwContinue = DBG_EXCEPTION_NOT_HANDLED; break ; case CREATE_THREAD_DEBUG_EVENT: break ; case CREATE_PROCESS_DEBUG_EVENT: SetMemBreakPoint ((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress); break ; case EXIT_THREAD_DEBUG_EVENT: break ; case EXIT_PROCESS_DEBUG_EVENT: break ; case LOAD_DLL_DEBUG_EVENT: break ; case UNLOAD_DLL_DEBUG_EVENT: break ; case OUTPUT_DEBUG_STRING_EVENT: break ; } bRet = ContinueDebugEvent (debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE); } return 0 ; }
实现效果如下
硬件断点
与软件断点与内存断点不同,硬件断点 不依赖被调试程序,而是依赖于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
首先看一下异常处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 BOOL SingleStepExceptionProc (EXCEPTION_DEBUG_INFO *pExceptionInfo) { BOOL bRet = FALSE; Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); if (Context.Dr6 & 0xF ) { printf ("硬件断点:%x 0x%p \n" , Context.Dr7&0x00030000 , Context.Dr0); Context.Dr0 = 0 ; Context.Dr7 &= 0xfffffffe ; } else { printf ("单步:0x%p \n" , Context.Eip); Context.Dr7 &= 0xfffffeff ; } SetThreadContext (hDebuggeeThread, &Context); while (bRet == FALSE) { bRet = WaitForUserCommand (); } return bRet; }
之前我们是在创建进程的时候进行断点,但是因为硬件断点需要在线程创建完成之后,设置在被调试程序的上下文中
因此当被调试程序触发调试器设置的INT 3断点时,此时设置硬件断点较为合理
再就是硬件断点的代码,这里把Dr0寄存器置1,然后把16、17为置0为执行断点,异常长度为1字节(18、19位置0),地址的话就是int3
断点的地址+1
1 2 3 4 5 6 7 8 9 10 11 12 13 VOID SetHardBreakPoint (PVOID pAddress) { Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); Context.Dr0 = (DWORD)pAddress; Context.Dr7 |= 1 ; Context.Dr7 &= 0xfff0ffff ; SetThreadContext (hDebuggeeThread, &Context); }
完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 #include "stdafx.h" #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #define DEBUGGEE "C:\\ipmsg.exe" DWORD dwDebuggeePID = 0 ; HANDLE hDebuggeeThread = NULL ; HANDLE hDebuggeeProcess = NULL ; BOOL bIsSystemInt3 = TRUE; 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) { Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); Context.Dr0 = (DWORD)pAddress; Context.Dr7 |= 1 ; Context.Dr7 &= 0xfff0ffff ; SetThreadContext (hDebuggeeThread, &Context); } BOOL Int3ExceptionProc (EXCEPTION_DEBUG_INFO *pExceptionInfo) { BOOL bRet = FALSE; if (bIsSystemInt3) { bIsSystemInt3 = FALSE; return TRUE; } else { WriteProcessMemory (hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1 , NULL ); } printf ("Int 3断点:0x%p \r\n" , pExceptionInfo->ExceptionRecord.ExceptionAddress); Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); Context.Eip--; SetThreadContext (hDebuggeeThread, &Context); SetHardBreakPoint ((PVOID)((DWORD)pExceptionInfo->ExceptionRecord.ExceptionAddress+1 )); while (bRet == FALSE) { bRet = WaitForUserCommand (); } return bRet; } BOOL AccessExceptionProc (EXCEPTION_DEBUG_INFO *pExceptionInfo) { BOOL bRet = FALSE; DWORD dwAccessFlag; DWORD dwAccessAddr; DWORD dwProtect; 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); Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); printf ("Eip: 0x%p \n" , Context.Eip); while (bRet == FALSE) { bRet = WaitForUserCommand (); } return bRet; } BOOL SingleStepExceptionProc (EXCEPTION_DEBUG_INFO *pExceptionInfo) { BOOL bRet = FALSE; Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext (hDebuggeeThread, &Context); if (Context.Dr6 & 0xF ) { printf ("硬件断点:%x 0x%p \n" , Context.Dr7&0x00030000 , Context.Dr0); Context.Dr0 = 0 ; Context.Dr7 &= 0xfffffffe ; } else { printf ("单步:0x%p \n" , Context.Eip); Context.Dr7 &= 0xfffffeff ; } SetThreadContext (hDebuggeeThread, &Context); 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) { 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 ; ReadProcessMemory (hDebuggeeProcess, addr, &OriginalCode, 1 , NULL ); WriteProcessMemory (hDebuggeeProcess, addr, &int3, 1 , NULL ); } VOID SetMemBreakPoint (PCHAR pAddress) { VirtualProtectEx (hDebuggeeProcess, pAddress, 1 , PAGE_NOACCESS, &dwOriginalProtect); } int main (int argc, char * argv[]) { BOOL nIsContinue = TRUE; DEBUG_EVENT debugEvent = {0 }; BOOL bRet = TRUE; DWORD dwContinue = DBG_CONTINUE; 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; while (nIsContinue) { bRet = WaitForDebugEvent (&debugEvent, INFINITE); if (!bRet) { printf ("WaitForDebugEvent error: %d \n" , GetLastError ()); return 0 ; } switch (debugEvent.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: bRet = ExceptionHandler (&debugEvent); if (!bRet) dwContinue = DBG_EXCEPTION_NOT_HANDLED; break ; case CREATE_THREAD_DEBUG_EVENT: break ; case CREATE_PROCESS_DEBUG_EVENT: SetInt3BreakPoint ((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress); break ; case EXIT_THREAD_DEBUG_EVENT: break ; case EXIT_PROCESS_DEBUG_EVENT: break ; case LOAD_DLL_DEBUG_EVENT: break ; case UNLOAD_DLL_DEBUG_EVENT: break ; case OUTPUT_DEBUG_STRING_EVENT: break ; } bRet = ContinueDebugEvent (debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE); } return 0 ; }
实现效果如下