一个处于原型期的debugger的简单分析

最近在看《软件调试》这本书,为了加深理解,就到google code里搜了一个叫opendbg的开源debugger看看代码。这个项目的最后更新日期是08年8月,似乎又烂尾了,这种烂尾开源项目真是数不胜数,不过幸好opendbg的整体架构已经搭出来了。

让我们直接进ring0,看作为一个debugger需要在内核里做什么事情
要进ring0,一定要写driver,driver的入口多半是DriverEntry,果然一搜就搜到在src\kernel\sys里有DriverEntry函数。这个函数非常简单,就做了三件事情:先后调用init_dbg_item,init_debug,和init_syscall函数。init_dbg_item函数初始化了一个dbg_item_list,所谓的dbg_item是用来替代windows中原有的debug port和debug消息循环的,open dbg的作者似乎想完全脱离windows的原生debug支持,全部重新写一套。i

nit_debug调用了几次set_sdt_hook函数,替换了几个系统调用,把ZwTerminateProcess,ZwCreateThread和KiDispatchException函数hook成自己的函数。我们知道断点,单步等都是走异常这条路的,再加上线程的创建和销毁,基本上常有的时间都能被捕获到了,但是没见到有fake create等的事件,所以该opendbg应该是不支持attach to process功能的。

set_sdt_hook函数非常简单,因为你已经在ring0了,在这个空间里只有想不到的,没有做不了的,好的坏的什么事情都能做。让我们看看set_sdt_hook都干了些什么:

void set_sdt_hook(int num, void *np, void *op)
{
	u64 cr0;

	cr0          =  mem_open();
	ppv(op)[0]   = SYSCALL(num);
	SYSCALL(num) = np;
	mem_close(cr0);
}

关键一步就是把系统调用表里的num号指定的项替换成参数np,替换后,相应的系统调用就会走np指定的那条路。

SYSCALL是一个宏,原型如下

#define SYSCALL(function) KeServiceDescriptorTable->ntoskrnl.ServiceTable[function]

KeServiceDescriptorTable是系统的一个全局变量,指向系统调用表。SYSCALL(num) = np; 就是替换的关键语句。

hook住关键系统调用后,debug引擎就能开始正常运转了。比如debugee程序触发一个断点后,被替换掉的KiDispatchException就会被调用到,新的KiDispatchException函数检测是否是用户态程序触发的异常(也就是说该opendbg不支持内核调试),如果是便转入dbg_process_exception函数。

static
int dbg_process_exception(
	   PEXCEPTION_RECORD except_record,
	   BOOLEAN           first_chance
	   )
{
	dbg_item *debug   = NULL;
	int       handled = 0;
	u32       i, code = except_record->ExceptionCode;
	dbg_msg   msg;
	cont_dat  cont;

	do
	{
		if ( (debug = dbg_find_item(NULL, IoGetCurrentProcess())) == NULL ) {
			break;
		}

		/* check event mask */
		if ( (debug->filter.event_mask & DBG_EXCEPTION) == 0 ) {
			break;
		}

		/* check exception filters */
		for (i = 0; i < debug->filter.filtr_count; i++)
		{
			if ( (code >= debug->filter.filters[i].filtr_from) &&
				 (code <= debug->filter.filters[i].filtr_to) )
			{
				break;
			}
		}

		/* setup debug message */
		msg.process_id             = PsGetCurrentProcessId();
		msg.thread_id              = PsGetCurrentThreadId();
		msg.event_code             = DBG_EXCEPTION;
		msg.exception.first_chance = first_chance;

		/* copy exception record */
		fastcpy(
			&msg.exception.except_record, except_record, sizeof(EXCEPTION_RECORD)
			);

		/* send debug message and recive replay */
		if (channel_send_recv(debug->chan, &msg, &cont) == 0) {
			break;
		}

		if (cont.status & RES_CORRECT_FRAME)
		{
			/* correct exception record */
			fastcpy(
				except_record, &cont.new_record, sizeof(EXCEPTION_RECORD)
				);
		}

		if (cont.status & RES_CONTINUE) {
			handled = 1;
		}
	} while (0);

	if (debug != NULL) {
		dbg_deref_item(debug);
	}

	return handled;
}

dbg_process_exception函数首先寻找本线程上是否附着了debug_item,如果有,则说明自己正在被调试,于是便准备好一个dbg_item结构,往里边填充processid,threadid,event_type,first_chance等,然后调用channel_send_recv函数。channel_send_recv函数会一直等待直到debugger有回应让线程继续执行或者终止或者怎样。在此过程中,debugger程序可以利用ReadProcessMemory,WriteProcessMemory等一系列函数对debuggee程序进行监控,修改等。

至此,opendbg的核心内容就做完了,剩下一堆跟用户交互啥的事情没有处理。即使作为原型它也还是有不少需要解决的问题,比如整个过程中,触发异常的线程都没有采取措施让其他线程也挂起等。考虑到原作者似乎也没什么动力继续更新了,我看这些问题被解决的可能性也很渺茫了吧。

posted @ 2009-05-28 23:45  gussing  阅读(1339)  评论(0编辑  收藏  举报