您的位置:首页 > 其它

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

2016-09-01 13:48 169 查看

前导分析

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

调试分析

准备

尽可能的准备好dump,二进制文件,以及pdb或者是map文件。加载dump到windbg中,设置调试符号的路径。

我们首先看看主线程的堆栈示意,因为我们的模块基本位于主线程中运行,而底层库会运行在工作线程中,我们先查看一下自己模块的工作情况。

0:000> kn
# ChildEBP RetAddr
00 0012e5c8 77946a04 ntdll!KiFastSystemCallRet
01 0012e5cc 75d069dc ntdll!NtWaitForMultipleObjects+0xc
02 0012e668 7601bc8e KERNELBASE!WaitForMultipleObjectsEx+0x100
03 0012e6b0 7601bcfc kernel32!WaitForMultipleObjectsExImplementation+0xe0
04 0012e6cc 7602f165 kernel32!WaitForMultipleObjects+0x18
05 0012e738 7602f202 kernel32!WerpReportFaultInternal+0x186
06 0012e74c 7602ef30 kernel32!WerpReportFault+0x70
07 0012e75c 7602eeaa kernel32!BasepReportFault+0x20
08 0012e7e8 102143dd kernel32!UnhandledExceptionFilter+0x1af
09 0012e80c 00547077 MSVCRTD!_XcptFilter+0x3d
0a 0012ff88 76023c45 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345]
0b 0012ff94 779637f5 kernel32!BaseThreadInitThunk+0xe
0c 0012ffd4 779637c8 ntdll!__RtlUserThreadStart+0x70
0d 0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b


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

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

0:000> .ecxr
Minidump doesn't have an exception context
Unable to get exception context, HRESULT 0x80004002


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

异常函数参数定位

UnhandledExceptionFilter

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

LONG
WINAPI
UnhandledExceptionFilter(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 L5
0012e7e8  0012e80c                              //保存上一函数基地址
0012e7ec  102143dd MSVCRTD!_XcptFilter+0x3d     //保存函数返回地址
0012e7f0  00000000                              //第一个参数
0012e7f4  00000000
0012e7f8  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+0x3d
0a 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 779471b9
0012e848  0012e92c 0012ff78 0012e948 0012e900
0012e858  0012ed90 779471cd 0012ff78 0012e914
0012e868  7794718b 0012e92c 0012ff78 0012e948
0012e878  0012e900 0054709a 00000000 0012e92c
0012e888  0012ff78 7791f96f 0012e92c 0012ff78
0012e898  0012e948 0012e900 0054709a 0012eca4
0012e8a8  0012e92c 0012ec48 0012e8dc 75d7c4e7
0:000> dd 0012e92c
0012e92c  c0000005 00000000 00000000 5f42d84a
0012e93c  00000002 00000000 dddddddd 0001003f
0012e94c  00000000 00000000 00000000 00000000
0012e95c  00000000 00000000 0000027f 00004000
0012e96c  0000ffff 00000000 00000000 00000000
0012e97c  00000000 00000000 00000000 00000000
0012e98c  00000000 00000000 00000000 00000000
0012e99c  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 L200
0012e7e8  0012e80c
0012e7ec  102143dd MSVCRTD!_XcptFilter+0x3d
0012e7f0  00000000
......
0012e80c  0012ff88
0012e810  00547077 XXXXXX!WinMainCRTStartup+0x1d7 [crtexe.c @ 345]
0012e814  c0000005
0012e818  0012e838
0012e81c  10211673 MSVCRTD!_except_handler3+0x4b
0012e820  0012e840
......
0012e840  0012e864
0012e844  779471b9 ntdll!ExecuteHandler2+0x26
0012e848  0012e92c
0012e84c  0012ff78
0012e850  0012e948
0012e854  0012e900
0012e858  0012ed90
0012e85c  779471cd ntdll!ExecuteHandler2+0x3a
......
0012e8a0  0054709a XXXXXX!except_handler3
0012e8a4  0012eca4
.......

0012e918  77947017 ntdll!KiUserExceptionDispatcher+0xf
0012e91c  0012e92c
0012e920  0012e948
0012e924  0012e92c
0012e928  0012e948
0012e92c  c0000005
0012e930  00000000
0012e934  00000000
0012e938  5f42d84a MFC42D!CWnd::OnDestroy+0x23   //异常地址
0012e93c  00000002
0012e940  00000000

0012ec38  dddddddd
0012ec3c  dddddddd
0012ec40  0012eca4
0012ec44  0041c79e XXXXXX!CFloatMenuDlg::OnDestroy+0x6e //这里我们便发现的自己模块调用位置。
0012ec48  0012ee1c
0012ec4c  00193f20
0012ec50  00000000


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

KiUserExceptionDispatcher

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

复现分析

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

0:000:x86> r
eax=03371b70 ebx=0557c070 ecx=0557c070 edx=724af314 esi=0557c070 edi=007c6c28
eip=00000002 esp=0018ee34 ebp=0018ee5c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
00000002 ??              ???


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

0:000:x86> kn
# ChildEBP RetAddr
00 0018ee30 724bdb6b 0x2
01 0018ee44 0040bdde mfc42!CWnd::Default+0x1d
02 0018ee5c 724bcba9 XXXXXX!CFloatMenuDlg::OnDestroy+0x51
03 0018eee8 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0
04 0018ef08 724bd8a5 mfc42!CWnd::WindowProc+0x27
05 0018ef80 724bd808 mfc42!AfxCallWndProc+0xab
06 0018efa4 724bddbf mfc42!AfxWndProc+0x3d
07 0018efe4 753077d8 mfc42!AfxWndProcBase+0x59
08 0018f010 753078cb user32!InternalCallWinProc+0x23
09 0018f08c 75307b6f user32!UserCallWinProcCheckWow+0x100
0a 0018f0f0 75307c44 user32!DispatchClientMessage+0x15d
0b 0018f12c 77a12f02 user32!__fnDWORD+0x2b
0c 0018f160 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e
0d 0018f174 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x18
0e 0018f188 00410752 mfc42!CWnd::DestroyWindow+0x3c
0f 0018f204 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x32c
10 0018f290 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0
11 0018f2b0 724bd8a5 mfc42!CWnd::WindowProc+0x27
12 0018f328 724bd808 mfc42!AfxCallWndProc+0xab
13 0018f34c 724bddbf mfc42!AfxWndProc+0x3d
14 0018f388 753077d8 mfc42!AfxWndProcBase+0x59
15 0018f3b4 753078cb user32!InternalCallWinProc+0x23
16 0018f430 75307b6f user32!UserCallWinProcCheckWow+0x100
17 0018f494 75307c44 user32!DispatchClientMessage+0x15d
18 0018f4d0 77a12f02 user32!__fnDWORD+0x2b
19 0018f504 74840c00 ntdll_779d0000!KiUserCallbackDispatcher+0x2e
1a 0018f518 724c5701 apphelp!DWM8AND16BitHook_DestroyWindow+0x18
1b 0018f52c 004a0c90 mfc42!CWnd::DestroyWindow+0x3c
1c 0018f5d4 724bcba9 XXXXXX!XXXXXX::OnDestroy+0x49c
1d 0018f660 724bcd9e mfc42!CWnd::OnWndMsg+0x3d0
1e 0018f680 724bd8a5 mfc42!CWnd::WindowProc+0x27
1f 0018f6f8 724bd808 mfc42!AfxCallWndProc+0xab
20 0018f71c 724bddbf mfc42!AfxWndProc+0x3d
21 0018f758 753077d8 mfc42!AfxWndProcBase+0x59
22 0018f784 753078cb user32!InternalCallWinProc+0x23
23 0018f800 75307b6f user32!UserCallWinProcCheckWow+0x100
24 0018f864 75307c44 user32!DispatchClientMessage+0x15d
25 0018f8a0 77a12f02 user32!__fnDWORD+0x2b
26 0018f8d4 7530a14b ntdll_779d0000!KiUserCallbackDispatcher+0x2e
27 0018f970 7530ee31 user32!RealDefWindowProcWorker+0xa4
28 0018f990 7530ee92 user32!RealDefWindowProcA+0x4d
29 0018f9d8 753077d8 user32!DefWindowProcA+0x75
2a 0018fa04 753078cb user32!InternalCallWinProc+0x23
2b 0018fa80 7530bd11 user32!UserCallWinProcCheckWow+0x100
2c 0018fab8 7531921c user32!CallWindowProcAorW+0xb1
2d 0018fad4 724bcb51 user32!CallWindowProcA+0x1c
2e 0018faf4 724bcdb5 mfc42!CWnd::DefWindowProcA+0x47
2f 0018fb10 724bd8a5 mfc42!CWnd::WindowProc+0x3e
30 0018fb88 724bd808 mfc42!AfxCallWndProc+0xab
31 0018fbac 724bddbf mfc42!AfxWndProc+0x3d
32 0018fbe8 753077d8 mfc42!AfxWndProcBase+0x59
33 0018fc14 753078cb user32!InternalCallWinProc+0x23
34 0018fc90 7530899d user32!UserCallWinProcCheckWow+0x100
35 0018fd04 7530ef74 user32!DispatchMessageWorker+0x3ef
36 0018fd10 724c5faf user32!DispatchMessageA+0xf
37 0018fd20 724dfaf2 mfc42!CWinThread::PumpMessage+0x40
38 0018fd40 724f198e mfc42!CWnd::RunModalLoop+0xd2
39 0018fd8c 00401f40 mfc42!CDialog::DoModal+0x122
3a 0018fec0 724c43dd XXXXXX!XXXXXX::InitInstance+0x40b
3b 0018fed4 00567b97 mfc42!AfxWinMain+0x47
3c 0018fee8 004cdf8c touchpcmt!WinMain+0x15 [appmodul.cpp @ 30]
3d 0018ff84 77498543 touchpcmt!WinMainCRTStartup+0x134
3e 0018ff90 77a2ac69 kernel32!BaseThreadInitThunk+0xe
3f 0018ffd4 77a2ac3c ntdll_779d0000!__RtlUserThreadStart+0x72
40 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,edi
724bdb50 56              push    esi
724bdb51 8bf1            mov     esi,ecx
724bdb53 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,esi
724bdb62 ff7038          push    dword ptr [eax+38h]
724bdb65 ff92a8000000    call    dword ptr [edx+0A8h]
724bdb6b 5e              pop     esi
724bdb6c c3              ret
724bdb6d 90              nop
724bdb6e 90              nop
724bdb6f 90              nop
724bdb70 90              nop
724bdb71 90              nop


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

总结

内存dump分析的过程也是推测验证的过程,我们尝试各种方法,通过点滴细节,推测全貌,然后反复的推测验证,再信息会总,从而分析出崩溃的真实原因,进而解决问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息