python灰帽子--调试器基础4:实现调试事件处理

本文深入探讨调试器如何处理各种调试事件,包括创建进程、加载DLL、创建线程等,并重点讲解如何捕获和处理由Windows设置断点引发的例外事件。文章通过代码示例展示了如何在Python中实现这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

承接上文,调试器基础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.")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值