承接上文,调试器基础2,调试器基础3,文中一些常量和结构体的定义在这里
为了让调试器能够针对特定的事件采取相应的行动,我们必须给所有调试器能够捕捉到的调试事件,编写处理函数。
回顾一下上文提到的WaitForDebugEvent()函数,每当它捕获到一个调试事件的时候,就返回一个填充好的DEBUG_EVENT结构。
现在我们要用存储在结构里的信息决定如何处理调试事件
DEBUG_EVENT定义如下:
typedef struct DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
}u;
};
在这个结构中,dwDebugEventCode
是最重要的。它表明了是什么事件被WaitForDebugEvent()
捕捉到了。同时也决定了在联合(union)u里存储的是什么类型的值。
u里的变量有dwDebugEventCode
决定,对应如下:
Event Code Event Code Value Union u Value
0x1 EXCEPTION_DEBUG_EVENT u.Exception
0x2 CREATE_THREAD_DEBUG_EVENT u.CreateThread
0x3 CREATE_PROCESS_DEBUG_EVENT u.CreateProcessInfo
0x4 EXIT_THREAD_DEBUG_EVENT u.ExitThread
0x5 EXIT_PROCESS_DEBUG_EVENT u.ExitProcess
0x6 LOAD_DLL_DEBUG_EVENT u.LoadDll
0x7 UNLOAD_DLL_DEBUG_EVENT u.UnloadDll
0x8 OUPUT_DEBUG_STRING_EVENT u.DebugString
0x9 RIP_EVENT u.RipInfo
通过观察dwDebugEventCode的值,在通过上面的表就能找到与之对应的存储在u里的变量。下面我们修改一下 调试器基础2 中调试循环的代码,即get_debug_event()
函数。
通过获得的事件代码的值,显示当前发生的事件信息。用这些信息,我们能够了解到调试器启动或者附加一个线程后的整个流程。
修改程序
#my_debugger.py
class debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
def get_debug_event(self):
debug_event = DEBUG_EVENT()
continue_status = DBG_CONTINUE
if kernel32.WatiForDebugEvent(byref(debug_event),INFINITE):
self.h_thread = self.open_thread(debug_event.dwThread)
self.context = self.get_thread_context(self.h_thread)
print "Event Code:%d Thread ID:%d" % (debug_event.dwDebugEventCode, debug_event.dwThreadId)
kernel32.ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
continue_status
)
测试程序
import my_debugger
debugger = my_debugger.debugger()
pid = raw_input("Enter the PID of the process to attach to: ")
debugger.attach(int(pid))
debugger.run()
debugger.detach()
运行测试程序后,以下是我的终端打印出的部分信息,不同的测试进程打印出的可能不太一样, 但大体都会有以下一些信息
Event Code: 3 Thread ID : 11068
Event Code: 6 Thread ID : 11068
Event Code: 2 Thread ID : 3564
Event Code: 2 Thread ID : 9464
Event Code: 6 Thread ID : 11068
Event Code: 6 Thread ID : 11068
Event Code: 6 Thread ID : 11068
Event Code: 6 Thread ID : 11068
Event Code: 6 Thread ID : 11068
Event Code: 2 Thread ID : 11180
Event Code: 1 Thread ID : 11180
Event Code: 4 Thread ID : 11180
Event Code: 4 Thread ID : 11292
Event Code: 4 Thread ID : 13796
Event Code: 4 Thread ID : 9360
输出说明
基于脚本的输出,我们能看到,
1)第一个Event Code是0x3,也就是CREATE_PROCESS_DEBUG_EVENT事件。
2)接下来有一个0x6,也就是LOAD_DLL_DEBUG_EVENT事件,加载DLL。
3)然后是0x2,CREATE_THREAD_DEBUG_EVENT,创建一个新线程。
4)接着就是一个0x1,EXCEPTION_DEBUG_EVENT例外事件,它由Windows设置的断电所引发,允许进程启动前观察进程的状态。
5)最后有一些0x4,EXIT_THREAD_DEBUG_EVENT,它因不同线程的结束而产生。
重要的例外事件
上面输出的事件中,比较重要的就是例外事件(0x1),因为例外事件的触发情况可能包含断点、访问异常、或者内存访问错误(例如尝试写入一个只读的内存区)。
下面的代码我们先捕获第一个windows设置的断点,下面我们修改一下get_debug_event()
函数。
class debugger():
def __init__(self):
self.h_process = None
self.pid = None
self.debugger_active = False
self.h_thread = None
self.context = None
self.exception = None
self.exception_address = None
def get_debug_event(self):
debug_event = DEBUG_EVENT()
continues_status = DBG_CONTINUE
if kernel32.WaitForDebugEvent(byref(debug_event), INFINITE):
# 获取相关线程的句柄并提取上下文环境信息
self.h_thread = self.open_thread(debug_event.dwThreadId)
self.context = self.get_thread_context(debug_event.dwThreadId)
print("Event Code: %d Thread ID : %d" % (debug_event.dwDebugEventCode, debug_event.dwThreadId))
#判断是否是有例外事件(0x1)触发的
if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
# 获取异常代码
self.exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode
# 获取异常地址
self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress
if self.exception == EXCEPTION_ACCESS_VIOLATION:
print("Access Violation Detected.")
elif self.exception == EXCEPTION_BREAKPOINT:
continues_status = self.exception_handler_breakpoint()
elif self.exception == EXCEPTION_GUARD_PAGE:
print("Guard Page Access Detected.")
elif self.exception == EXCEPTION_SINGLE_STEP:
print("Single Stepping.")
kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continues_status)
def exception_handler_breakpoint(self):
print("[*]Inside the breakpoint handler.")
print("Exception address:0x%08x" % self.exception_address)
return DBG_CONTINUE
重新运行脚本,将看到由软件断点的异常处理函数打印的输出结果。
这里只是简单的打印出了软件断点触发异常的地址。同时,在函数中创建了硬件断点和内存断点的处理模型。
下篇文章我们要详细的实现这三种不同类型断点的处理函数,即对下面这几个触发情况进行详细处理
if self.exception == EXCEPTION_ACCESS_VIOLATION:
print("Access Violation Detected.")
elif self.exception == EXCEPTION_BREAKPOINT:
continues_status = self.exception_handler_breakpoint()
elif self.exception == EXCEPTION_GUARD_PAGE:
print("Guard Page Access Detected.")
elif self.exception == EXCEPTION_SINGLE_STEP:
print("Single Stepping.")