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