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

如果你重新运行这个脚本,将看到由软件断点的异常处理函数打印的输出结果。我们已经创建了硬件断点和内存断点的处理模型。接下来我们要详细的实现这三种不同类型断点的 处理函数。