逆向调试初探

本文介绍了在开发中遇到服务器崩溃并产生DMP文件的问题,通过windbg进行逆向调试分析。通过!analyze -v命令查看异常信息,发现是cmp指令导致的访问违例。通过调用栈和反汇编,定位到问题发生在第三方库的AysncIO_WorkRoutine_Local函数,由于GetQueuedCompletionStatus返回失败,lpOverlapped参数为NULL,导致尝试访问无效地址,最终找出问题根源是完成端口句柄被关闭。
摘要由CSDN通过智能技术生成

在开发过程中,我们经常会调用第三方库,当程序出了问题时,有时候崩在了第三方库内,问题就比较难查了,这个时候要找出原因,就需要我们有一定的逆向功底,因为一般第三方库的源代码我们是看不到的,只能通过反汇编的方法来查找。

近日,在某项目的开发过程中,遇到一个问题。我们一台服务器在运行过程中出现了几次崩溃,产生若干DMP文件。我们用windbg打开,设置符号路径,并加载符号。输入

!analyze -v,稍等片刻,windbg便输出了初步的分析结果。如下图所示:

 

其中FAULTING_IP显示了发生错误的那行代码,其汇编代码为:

cmp  dword ptr [eax+14h], edi , 即将以eax为基址,偏移14h处的数值与edi比较。

紧接着这行代码的是EXCEPTION_RECORD这个结构体的信息,当异常发生的时候,系统会自动生成这么一个结构体,来记录异常发生时的信息。我们来看ExceptionAddress表示异常发生时那条指令的地址,图中为100057cb,ExceptionCode为异常代码,为c0000005,这个显而易见,是访问违例了。

NumberParameters是一个用于描述异常的附加参数的数组,对于不同类型的异常,有不同的解释。当异常类型为访问违例时,该数组的第一个元素包含了一个读写标志,表示引起访问违归的操作类型。如果这个值为0,表示线程试图读取不可访问的数据,如果这个值为1,表示线程试图写入不可访问的地址,如果这个值为8,表示线程引发了一个用户模式的DEP违规。数组的第二个元素指定了不可访问数据的虚拟地址。因此综合起来就是,试图读取00000014处的数据时,发生了访问违例。再结合之前的汇编代码,可以推断出根本原因是因为eax0,那么接下来,我们就需要找出eax这个值是从哪儿来的。

我们可以看下调用栈,敲入kb,输出如下:

这个显示程序中出现了异常,被google_breakpad这个库捕获到了,最终生成了dmp文件,不过我们最想要看到的,还是异常发生时的堆栈,此时可以输入.ecxr命令,这个命令可以显示当前异常发生时的上下文记录。紧接着再输入kb,此时调用栈如下:

 

最终我们定位到了一个第三方库的AysncIO_WorkRoutine_Local这个函数,这是位于第三方库的一个函数,这个时候,我们首先要排除是否是参数不正确引起的,因此需要先找出这个函数的声明,再找出参数的值,然后判断下参数是否正确。

查找函数的参数列表,可以使用ln这个命令,如下图所示

可以看出这个函数只有一个void*类型的参数,这个参数的值我们可以从调用栈中获取

看这一行:

第一个值代表childEBP,即帧指针的值,第二个代表返回地址,从第三个参数开始,是传给函数的参数,由于该函数只有一个,我们可以得知该参数为00a50010

由于这个函数参数类型是void*,我们暂时不能确定是否是参数的值有问题,不过可以肯定的是,当参数为00a50010时,AysncIO_WorkRoutine_Local执行到第9B个字节出了问题,这样我们的排查范围进一步缩小了。

我们需要搞清楚AysncIO_WorkRoutine_Local这个函数的流程了,首先将报错那行代码附近几行反汇编下,之前提过,地址为100057cb,我们输入ub 100057cb,即可将之前几句显示出来,输入 u 100057cb,即可将后面几句输出,如图:

注意报错那行的前一句,为一条jmp语句,也就是说如果按由上至下这个流程的话,是不可能到达100057cb这行的。猜想一定是由前面某处跳转过来的,我们可以把整个函数反汇编下,输入uf 100057cb,即可反汇编整个函数:

 

然后在这里面搜索100057cb,即跳转到的语句

找到了跳转的地方,而且很幸运的是,离函数起始的地方很近,这意味着我们的工作量会少很多。

通过观察这几行代码,发现是当调用

 BOOL WINAPI GetQueuedCompletionStatus(
  _In_   HANDLE CompletionPort,
  _Out_  LPDWORD lpNumberOfBytes,
  _Out_  PULONG_PTR lpCompletionKey,
  _Out_  LPOVERLAPPED *lpOverlapped,
  _In_   DWORD dwMilliseconds
);

时,该函数返回了失败,然后转去错误处理了,错误处理即100057cb起始的地方。

转去错误处理的之前,将lpOverlapped这个参数的值放在了eax中,然后调用

 cmp  dword ptr [eax+14h],edi

造成的结果是试图读取14h处的内存,报了访问违例异常。这样就可以推断出lpOverlapped这个参数为NULL,至此就跟我们观察的现象很一致了。那么这个lpOverlapped为什么为NULL呢,查阅下msdn,发现对此函数有如此介绍

 If a call to GetQueuedCompletionStatus fails because the completion port handle associated with it is closed while the call is outstanding, the function returnsFALSE, *lpOverlapped will be NULL, andGetLastError will returnERROR_ABANDONED_WAIT_0.

 意思是说如果第一个参数,完成端口的句柄被关闭了,再调用这个函数时,就返回了false,同时lpOverlapped的值为NULL,至此一切真相大白了。我们梳理下整个流程:

首先是调用AysncIO_WorkRoutine_Local时,传入了一个已经失效的完成端口句柄,这样调用GetQueuedCompletionStatus就返回了失败,同时lpOverlapped这个参数为NULL,返回失败之后,代码里做了错误判断,并准备把lpOverlapped包含的信息打印出来,可惜这时候并不知道这个参数其实是NULL,就直接取偏移了,因此造成了程序崩溃。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值