3.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 |
Table 3-1:调试事件
通过观察 dwDebugEventCode 的值,再通过上面的表就能找到与之相对应的存储在 u 里 的变量。让我们修改调试循环,通过获得的事件代码的值,显示当前发生的事件信息。用这些信息,我们能够了解到调试器启动或者附加一个线程后的整个流程。继续更新 my_debugger.py 和 our my_test.py 脚本。
#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.WaitForDebugEvent(byref(debug_event),INFINITE):
# Let's obtain the thread and context information
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.dwThre
kernel32.ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
continue_status )
#my_test.py
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()
如果你用的是 calc.exe,输出将如下所示:
Enter the PID of the process to attach to: 2700
Event Code: 3 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 6 Thread ID: 3976
Event Code: 2 Thread ID: 3912
Event Code: 1 Thread ID: 3912
Event Code: 4 Thread ID: 3912
Listing 3-2: 当附加到 cacl.exe 时的事件代码
基于脚本的输出,我们能看到 CREATE_PROCESS_EVENT (0x3)事件是第一个发生的, 接 下 来 的 是 一 堆 的 LOAD_DLL_DEBUG_EVENT (0x6) 事 件 , 然 后 CREATE_THREAD_DEBUG_EVENT (0x2) 创 建 一 个 新 线 程 。 接 着 就 是 一 个 EXCEPTION_DEBUG_EVENT (0x1)例外事件,它由 windows 设置的断点所引发的,允许在 进程启动前观察进程的状态。最后一个事件是 EXIT_THREAD_DEBUG_EVENT (0x4),它 由进程 3912 结束只身产生。
例外事件是非常重要,例外可能包括断点,访问异常,或者内存访问错误(例如尝试写 到一个只读的内存区)。所有这些都很重要,但是让我们捕捉先捕捉第一个 windows 设置的 断点。打开 my_debugger.py 加入以下代码:
#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
self.exception = None
self.exception_address = None
...
def get_debug_event(self):
debug_event = DEBUG_EVENT()
continue_status= DBG_CONTINUE
if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):
# Let's obtain the thread and context information
self.h_thread = self.open_thread(debug_event.dwThreadId)
self.context = self.get_thread_context(self.h_thread)
print "Event Code: %d Thread ID: %d" % (debug_event.dwDebugEventCode, debug_event.dwThreadId)
# If the event code is an exception, we want to
# examine it further.
if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
# Obtain the exception code
exception = debug_event.u.Exception.ExceptionRecord.ExceptionCod
self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAdd
if exception == EXCEPTION_ACCESS_VIOLATION:
print "Access Violation Detected."
# If a breakpoint is detected, we call an internal
# handler.
elif exception == EXCEPTION_BREAKPOINT:
continue_status = self.exception_handler_breakpoint()
elif ec == EXCEPTION_GUARD_PAGE:
print "Guard Page Access Detected."
elif ec == EXCEPTION_SINGLE_STEP:
print "Single Stepping."
kernel32.ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
continue_status )
...
def exception_handler_breakpoint():
print "[*] Inside the breakpoint handler."
print "Exception Address: 0x%08x" % self.exception_address
return DBG_CONTINUE
如果你重新运行这个脚本,将看到由软件断点的异常处理函数打印的输出结果。我们已经创建了硬件断点和内存断点的处理模型。接下来我们要详细的实现这三种不同类型断点的 处理函数。