弹出异常提示框的崩溃分析

来源:互联网 发布:matlab矩阵分析与处理 编辑:程序博客网 时间:2024/06/07 07:24

前导分析

当程序执行崩溃的时候,如果程序并没有设置任何崩溃捕获钩子时,操作系统会弹出提示框,提醒用户是否调试或是关闭程序,如果这个时候我们使用任务管理器或者外部第三方程序创建了一个崩溃程序的dump,那么我们该如何分析呢。

调试分析

准备

尽可能的准备好dump,二进制文件,以及pdb或者是map文件。加载dump到windbg中,设置调试符号的路径。
我们首先看看主线程的堆栈示意,因为我们的模块基本位于主线程中运行,而底层库会运行在工作线程中,我们先查看一下自己模块的工作情况。

0:000> kn # ChildEBP RetAddr  00 0012e5c8 77946a04 ntdll!KiFastSystemCallRet01 0012e5cc 75d069dc ntdll!NtWaitForMultipleObjects+0xc02 0012e668 7601bc8e KERNELBASE!WaitForMultipleObjectsEx+0x10003 0012e6b0 7601bcfc kernel32!WaitForMultipleObjectsExImplementation+0xe004 0012e6cc 7602f165 kernel32!WaitForMultipleObjects+0x1805 0012e738 7602f202 kernel32!WerpReportFaultInternal+0x18606 0012e74c 7602ef30 kernel32!WerpReportFault+0x7007 0012e75c 7602eeaa kernel32!BasepReportFault+0x2008 0012e7e8 102143dd kernel32!UnhandledExceptionFilter+0x1af09 0012e80c 00547077 MSVCRTD!_XcptFilter+0x3d0a 0012ff88 76023c45 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345]0b 0012ff94 779637f5 kernel32!BaseThreadInitThunk+0xe0c 0012ffd4 779637c8 ntdll!__RtlUserThreadStart+0x700d 0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

这里隐去了重要模块的模块名,我们且看一下堆栈示意情况。

简要分析一下堆栈,从堆栈中我们暂时无法发现崩溃的位置,只有我们自己模块的代码启动函数,这并不能确认问题,代码运行主模块的时候发生了异常,异常经由系统派发,系统并没有找到可处理的SEH处理程序,进而派发到线程最后一个异常处理,这个处理就是未处理异常的过滤函数,UnhandledExceptionFilter,一般这个函数会判断用户是否设置了未处理异常过滤函数,自然我们的模块中并没有设置异常捕获,接着UnhandledExceptionFilter开始调用错误提示框显示错误给用户,并等待用户的操作。然而这里有个比较重要的信息,虽然我们无法立即定位到崩溃位置,但是我们可以知道崩溃的信息,我尝试下面的方法。

0:000> .ecxrMinidump doesn't have an exception contextUnable to get exception context, HRESULT 0x80004002

于是乎,我使用.ecxr命令看看能不能显示出异常信息的详细情况,却事与愿违,由于我们使用的任务管理器创建的dump,这种情况并不会记录特别的异常指针或堆栈信息到异常上下文中,情况要比看起来略微的复杂。

异常函数参数定位

UnhandledExceptionFilter

既然我们知道了UnhandledExceptionFilter在当前堆栈上被调用了,通过参考reactos的代码,我们会知道,这个函数带有异常信息,我们能不能通过这个异常信息了解到相关崩溃的位置呢?可以试试。

LONGWINAPIUnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo){......}typedef struct _EXCEPTION_POINTERS {    PEXCEPTION_RECORD ExceptionRecord;    PCONTEXT ContextRecord;} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;typedef struct _EXCEPTION_RECORD {    DWORD    ExceptionCode;    DWORD ExceptionFlags;    struct _EXCEPTION_RECORD *ExceptionRecord;    PVOID ExceptionAddress;    DWORD NumberParameters;    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];    } EXCEPTION_RECORD;typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;

我们参考ReactOs的代码会发现,这个函数的参数是一个指针,指向一个EXCEPTION_POINTERS,其中一个重要的结构时PEXCEPTION_RECORD,内部数据中含有ExceptionAddress,我们需要找到的就是这个异常地址,这个地址是实际的崩溃位置。

我们回到dump分析中,我们要显示出函数的参数信息,回到之前的堆栈,我们会寻找UnhandledExceptionFilter,所对应的栈基址为0012e7e8。我们打印出参数信息

0:000> dds 0012e7e8 L50012e7e8  0012e80c                              //保存上一函数基地址    0012e7ec  102143dd MSVCRTD!_XcptFilter+0x3d     //保存函数返回地址0012e7f0  00000000                              //第一个参数0012e7f4  000000000012e7f8  0012e844

然而比较惨的是这个函数并没有传递参数信息。

_XcptFilter

我们可以顺这个思路去查找另外一个函数_XcptFilter,这个函数是UnhandledExceptionFilter的父函数。我们查看一下这个函数的代码,以确认参数的类型。

int __cdecl _XcptFilter (        unsigned long xcptnum,        PEXCEPTION_POINTERS pxcptinfoptrs        ){......}

发现这个函数的第二个参数信息同样是PEXCEPTION_POINTERS,我们可以查看一下这个函数。换个获取参数的方法,使用kv.

0:000> kv # ChildEBP RetAddr  Args to Child              00 0012e5c8 77946a04 75d069dc 00000002 0012e61c ntdll!KiFastSystemCallRet (FPO: [0,0,0])01 0012e5cc 75d069dc 00000002 0012e61c 00000001 ntdll!NtWaitForMultipleObjects+0xc (FPO: [5,0,0])02 0012e668 7601bc8e 0012e61c 0012e690 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100 (FPO: [Non-Fpo])03 0012e6b0 7601bcfc 00000002 7ffd7000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0 (FPO: [Non-Fpo])04 0012e6cc 7602f165 00000002 0012e700 00000000 kernel32!WaitForMultipleObjects+0x18 (FPO: [Non-Fpo])05 0012e738 7602f202 0012e838 00000001 00000001 kernel32!WerpReportFaultInternal+0x186 (FPO: [Non-Fpo])06 0012e74c 7602ef30 0012e838 00000001 0012e7e8 kernel32!WerpReportFault+0x70 (FPO: [Non-Fpo])07 0012e75c 7602eeaa 0012e838 00000001 e479dbd8 kernel32!BasepReportFault+0x20 (FPO: [Non-Fpo])08 0012e7e8 102143dd 00000000 00000000 0012e844 kernel32!UnhandledExceptionFilter+0x1af (FPO: [Non-Fpo])09 0012e80c 00547077 c0000005 0012e838 10211673 MSVCRTD!_XcptFilter+0x3d0a 0012ff88 76023c45 7ffd7000 0012ffd4 779637f5 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345]0b 0012ff94 779637f5 7ffd7000 7614288e 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])0c 0012ffd4 779637c8 00546ea0 7ffd7000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])0d 0012ffec 00000000 00546ea0 7ffd7000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

锁定_XcptFilter所在行,会发现,第一个参数是c0000005,这应该是一个异常的类型,而第二个参数是0012e838,我们找到了异常指针的地址。

0:000> dd 0012e838 0012e838  0012e92c 0012e948 0012e864 779471b90012e848  0012e92c 0012ff78 0012e948 0012e9000012e858  0012ed90 779471cd 0012ff78 0012e9140012e868  7794718b 0012e92c 0012ff78 0012e9480012e878  0012e900 0054709a 00000000 0012e92c0012e888  0012ff78 7791f96f 0012e92c 0012ff780012e898  0012e948 0012e900 0054709a 0012eca40012e8a8  0012e92c 0012ec48 0012e8dc 75d7c4e70:000> dd 0012e92c 0012e92c  c0000005 00000000 00000000 5f42d84a0012e93c  00000002 00000000 dddddddd 0001003f0012e94c  00000000 00000000 00000000 000000000012e95c  00000000 00000000 0000027f 000040000012e96c  0000ffff 00000000 00000000 000000000012e97c  00000000 00000000 00000000 000000000012e98c  00000000 00000000 00000000 000000000012e99c  00000000 00000000 00000000 00000000

根据EXCEPTION_POINTERS的结构类型,我们先对0012e838取值,得到的0012e92c是EXCEPTION_RECORD的指针,而0012e948是PCONTEXT的指针,我们比较关心的是EXCEPTION_RECORD,于是乎,我们再次取值,便得到了异常结构数据。

typedef struct _EXCEPTION_RECORD {    DWORD    ExceptionCode;                                         //c0000005    DWORD ExceptionFlags;                                           //00000000     struct _EXCEPTION_RECORD *ExceptionRecord;                      //00000000    PVOID ExceptionAddress;                                         //5f42d84a    DWORD NumberParameters;                                         //00000002    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];    } EXCEPTION_RECORD;

这里我们最为关心的是ExceptionAddress的数据为5f42d84a,可是这个地址是什么位置的存在呢,代表着哪行代码呢。。。

真实堆栈还原

我们得到了异常地址,可是我们并不知道堆栈啊,并且这个地址代表什么意思呢?于是我们开始着手还原堆栈。以求了解代码的执行过程。
我们从UnhandledExceptionFilter函数基址处向上还原堆栈。这里省略了部分无用的信息

0:000> dds 0012e7e8 L2000012e7e8  0012e80c0012e7ec  102143dd MSVCRTD!_XcptFilter+0x3d0012e7f0  00000000......0012e80c  0012ff880012e810  00547077 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345]0012e814  c00000050012e818  0012e8380012e81c  10211673 MSVCRTD!_except_handler3+0x4b0012e820  0012e840......0012e840  0012e8640012e844  779471b9 ntdll!ExecuteHandler2+0x260012e848  0012e92c0012e84c  0012ff780012e850  0012e9480012e854  0012e9000012e858  0012ed900012e85c  779471cd ntdll!ExecuteHandler2+0x3a......0012e8a0  0054709a XXXXXX!except_handler30012e8a4  0012eca4.......0012e918  77947017 ntdll!KiUserExceptionDispatcher+0xf0012e91c  0012e92c0012e920  0012e9480012e924  0012e92c0012e928  0012e9480012e92c  c00000050012e930  000000000012e934  000000000012e938  5f42d84a MFC42D!CWnd::OnDestroy+0x23   //异常地址0012e93c  000000020012e940  000000000012ec38  dddddddd0012ec3c  dddddddd0012ec40  0012eca40012ec44  0041c79e XXXXXX!CFloatMenuDlg::OnDestroy+0x6e //这里我们便发现的自己模块调用位置。0012ec48  0012ee1c0012ec4c  00193f200012ec50  00000000

这里我们便复原出原始的堆栈,从XXXXXX!CFloatMenuDlg OnDestroy调用了MFC42D!CWnd OnDestroy,完成了崩溃位置的定位。

KiUserExceptionDispatcher

在上面的堆栈中,我们亦可以分析KiUserExceptionDispatcher,这个也是系统分发异常的流转函数之一,从上面的原始堆栈打印中可以看该函数的参数,正是和之前的分析一样,0012e92c指向异常结构。

复现分析

后来测试人员发现了复现的方法,主动挂上了windbg,而当windbg调试程序的时候,发生异常首先会派发给windbg。我们发生异常的时候就已经位于异常的堆栈了,此时就不需要寻找异常上下文以及异常堆栈了。

0:000:x86> reax=03371b70 ebx=0557c070 ecx=0557c070 edx=724af314 esi=0557c070 edi=007c6c28eip=00000002 esp=0018ee34 ebp=0018ee5c iopl=0         nv up ei pl nz na po nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=0021020200000002 ??              ???

我们查看上下文会发现,这个EIP的指针00000002,显然是个非法的地址,这个地址不可访问,导致崩溃。

0:000:x86> kn # ChildEBP RetAddr  00 0018ee30 724bdb6b 0x201 0018ee44 0040bdde mfc42!CWnd::Default+0x1d02 0018ee5c 724bcba9 XXXXXX!CFloatMenuDlg::OnDestroy+0x51 03 0018eee8 724bcd9e mfc42!CWnd::OnWndMsg+0x3d004 0018ef08 724bd8a5 mfc42!CWnd::WindowProc+0x2705 0018ef80 724bd808 mfc42!AfxCallWndProc+0xab06 0018efa4 724bddbf mfc42!AfxWndProc+0x3d07 0018efe4 753077d8 mfc42!AfxWndProcBase+0x5908 0018f010 753078cb user32!InternalCallWinProc+0x2309 0018f08c 75307b6f user32!UserCallWinProcCheckWow+0x1000a 0018f0f0 75307c44 user32!DispatchClientMessage+0x15d0b 0018f12c 77a12f02 user32!__fnDWORD+0x2b0c 0018f160 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e0d 0018f174 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x180e 0018f188 00410752 mfc42!CWnd::DestroyWindow+0x3c0f 0018f204 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x32c 10 0018f290 724bcd9e mfc42!CWnd::OnWndMsg+0x3d011 0018f2b0 724bd8a5 mfc42!CWnd::WindowProc+0x2712 0018f328 724bd808 mfc42!AfxCallWndProc+0xab13 0018f34c 724bddbf mfc42!AfxWndProc+0x3d14 0018f388 753077d8 mfc42!AfxWndProcBase+0x5915 0018f3b4 753078cb user32!InternalCallWinProc+0x2316 0018f430 75307b6f user32!UserCallWinProcCheckWow+0x10017 0018f494 75307c44 user32!DispatchClientMessage+0x15d18 0018f4d0 77a12f02 user32!__fnDWORD+0x2b19 0018f504 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e1a 0018f518 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x181b 0018f52c 004a0c90 mfc42!CWnd::DestroyWindow+0x3c1c 0018f5d4 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x49c 1d 0018f660 724bcd9e mfc42!CWnd::OnWndMsg+0x3d01e 0018f680 724bd8a5 mfc42!CWnd::WindowProc+0x271f 0018f6f8 724bd808 mfc42!AfxCallWndProc+0xab20 0018f71c 724bddbf mfc42!AfxWndProc+0x3d21 0018f758 753077d8 mfc42!AfxWndProcBase+0x5922 0018f784 753078cb user32!InternalCallWinProc+0x2323 0018f800 75307b6f user32!UserCallWinProcCheckWow+0x10024 0018f864 75307c44 user32!DispatchClientMessage+0x15d25 0018f8a0 77a12f02 user32!__fnDWORD+0x2b26 0018f8d4 7530a14b ntdll_779d0000!KiUserCallbackDispatcher+0x2e27 0018f970 7530ee31 user32!RealDefWindowProcWorker+0xa428 0018f990 7530ee92 user32!RealDefWindowProcA+0x4d29 0018f9d8 753077d8 user32!DefWindowProcA+0x752a 0018fa04 753078cb user32!InternalCallWinProc+0x232b 0018fa80 7530bd11 user32!UserCallWinProcCheckWow+0x1002c 0018fab8 7531921c user32!CallWindowProcAorW+0xb12d 0018fad4 724bcb51 user32!CallWindowProcA+0x1c2e 0018faf4 724bcdb5 mfc42!CWnd::DefWindowProcA+0x472f 0018fb10 724bd8a5 mfc42!CWnd::WindowProc+0x3e30 0018fb88 724bd808 mfc42!AfxCallWndProc+0xab31 0018fbac 724bddbf mfc42!AfxWndProc+0x3d32 0018fbe8 753077d8 mfc42!AfxWndProcBase+0x5933 0018fc14 753078cb user32!InternalCallWinProc+0x2334 0018fc90 7530899d user32!UserCallWinProcCheckWow+0x10035 0018fd04 7530ef74 user32!DispatchMessageWorker+0x3ef36 0018fd10 724c5faf user32!DispatchMessageA+0xf37 0018fd20 724dfaf2 mfc42!CWinThread::PumpMessage+0x4038 0018fd40 724f198e mfc42!CWnd::RunModalLoop+0xd239 0018fd8c 00401f40 mfc42!CDialog::DoModal+0x1223a 0018fec0 724c43dd XXXXXX!XXXXXX::InitInstance+0x40b 3b 0018fed4 00567b97 mfc42!AfxWinMain+0x473c 0018fee8 004cdf8c touchpcmt!WinMain+0x15 [appmodul.cpp @ 30]3d 0018ff84 77498543 touchpcmt!WinMainCRTStartup+0x1343e 0018ff90 77a2ac69 kernel32!BaseThreadInitThunk+0xe3f 0018ffd4 77a2ac3c ntdll_779d0000!__RtlUserThreadStart+0x7240 0018ffec 00000000 ntdll_779d0000!_RtlUserThreadStart+0x1b

查看一下线程的堆栈情况,与之前我们的分析不谋而合,mfc42!CWnd Default函数发生了崩溃。

LRESULT CWnd::Default(){    // call DefWindowProc with the last message    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();    return DefWindowProc(pThreadState->m_lastSentMsg.message,        pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam);}//汇编代码如下mfc42!CWnd::Default:724bdb4e 8bff            mov     edi,edi724bdb50 56              push    esi724bdb51 8bf1            mov     esi,ecx724bdb53 e86a86ffff      call    mfc42!CThreadLocal<_AFX_THREAD_STATE>::GetData (724b61c2)724bdb58 ff7040          push    dword ptr [eax+40h]724bdb5b 8b16            mov     edx,dword ptr [esi]724bdb5d ff703c          push    dword ptr [eax+3Ch]724bdb60 8bce            mov     ecx,esi724bdb62 ff7038          push    dword ptr [eax+38h]724bdb65 ff92a8000000    call    dword ptr [edx+0A8h]724bdb6b 5e              pop     esi724bdb6c c3              ret724bdb6d 90              nop724bdb6e 90              nop724bdb6f 90              nop724bdb70 90              nop724bdb71 90              nop

我们找一下MFC的代码,分析一下这个代码,一切都明晰了,这里面是一个成员函数的调用,对比汇编,call指令是一个改变EIP指针的一个指令之一,此处传递过来的this指针有问题,导致索引偏移的地址数据无效,进而无法执行。联系相关人员对比代码发现确实这样,在销毁窗口前进行了类指针内存的释放,导致崩溃。修改代码避免了崩溃的发生。

总结

内存dump分析的过程也是推测验证的过程,我们尝试各种方法,通过点滴细节,推测全貌,然后反复的推测验证,再信息会总,从而分析出崩溃的真实原因,进而解决问题。

0 0
原创粉丝点击