前面说到过,栈溢出类型的异常通过编程的方式获取DUMP可能不成功,因为栈溢出会破坏SEH(结构化异常处理)框架。实际上,通过DUMP文件来调试栈溢出同样是困难的,因为栈溢出本身一般不会造成异常,异常往往发生在栈溢出破坏栈上的数据之后,同时,由于栈溢出破坏了栈上的数据,使得我们无法对函数调用进行回溯,从而难以定位问题的发生位置。
本节的示例是经dump1简单修改而来,在Crash Me!按钮的消息处理函数中,写下如下代码:
int a[10];
for( int i = 0; i < 200; i++ )
a[i] = i;
printf("%d\n", a[0]);
需要注意的是,Visual Studio针对栈溢出有保护措施,如果开启了保护措施,则栈溢出会被检测出来,例如:
这里我们使用Release编译,并选择最大化速度优化代码,这样就不会添加栈溢出的检查代码。此时,再运行程序:
就检查不到栈溢出了。同时,我们的MyUnhandledExceptionFilter函数也没有起作用,没有生成mini.dmp。因此,我们用任务管理器手动抓取DUMP文件。
加DUMP文件加载到Windbg中,切换到32位模式,然后使用 !analyze -v进行分析:
WARNING: ole32 overlaps gdi32
*** WARNING: symbols timestamp is wrong 0x569a9398 0x569a8dc2 for ole32.dll
*** ERROR: Module load completed but symbols could not be loaded for mfc90.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for msvcr90.dll -
Cannot find frame 0x7c, previous scope unchanged
Failed calling InternetOpenUrl, GLE=12029
FAULTING_IP:
wow64cpu!CpupReturnFromSimulatedCode+0
7476271e 6744 inc esp
EXCEPTION_RECORD: ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 0000000000000000
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 0
FAULTING_THREAD: 00000000000014b0
DEFAULT_BUCKET_ID: STACKIMMUNE
PROCESS_NAME: dump2.exe
OVERLAPPED_MODULE: Address regions for 'ole32' and 'gdi32' overlap
ERROR_CODE: (NTSTATUS) 0x80000003 - {
EXCEPTION_CODE: (NTSTATUS) 0x80000003 (2147483651) - {
MOD_LIST: <ANALYSIS/>
NTGLOBALFLAG: 0
APPLICATION_VERIFIER_FLAGS: 0
CONTEXT: ffffffffc0000005 -- (.cxr 0xffffffffc0000005)
Unable to read context, HRESULT 0x80004002
ADDITIONAL_DEBUG_TEXT: Followup set based on attribute [Is_ChosenCrashFollowupThread] from Frame:[0] on thread:[PSEUDO_THREAD]
LAST_CONTROL_TRANSFER: from 00000000775b94cf to 000000007752f971
PRIMARY_PROBLEM_CLASS: STACKIMMUNE
BUGCHECK_STR: APPLICATION_FAULT_STACKIMMUNE_ZEROED_STACK
STACK_TEXT:
00000000`00000000 00000000`00000000 dump2.exe+0x0
STACK_COMMAND: .cxr 00000000001AE2D0 ; kb ; ** Pseudo Context ** ; kb
SYMBOL_NAME: dump2.exe
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: dump2
IMAGE_NAME: dump2.exe
DEBUG_FLR_IMAGE_TIMESTAMP: 579d6915
FAILURE_BUCKET_ID: STACKIMMUNE_80000003_dump2.exe!Unknown
BUCKET_ID: X64_APPLICATION_FAULT_STACKIMMUNE_ZEROED_STACK_dump2.exe
FOLLOWUP_IP:
dump2!__ImageBase+0
003c0000 4d dec ebp
WATSON_STAGEONE_URL: http://watson.microsoft.com/StageOne/dump2_exe/1_0_0_1/579d6915/unknown/0_0_0_0/bbbbbbb4/80000003/00000000.htm?Retriage=1
Followup: MachineOwner
---------
没有得到什么有价值的信息。通过 kb 命令查看一下栈:
异常发生之后的栈还完好,之前的栈被破坏,已经解析不出来符号了。还记得上一节怎么做的吗?查看地址 0x39f230:
异常代码是 C0000005,异常地址是 0000000a。实际上,Windbg有专门的指令来解析_EXCEPTION_RECORD结构,即 .exr,如下:
来看看异常发生的地址:
这个地址无法读取(实际上位于NULL的64KB内)。现在的问题是,是谁读取了这个地址?是取数据?还是取指令?(实际上.exr指令已经给出了答案)上节说到了函数KiUserExceptionDispatcher,它的一个参数是_EXCEPTION_RECORD结构的指针,另一个参数是CONTEXT结构的指针,也就是如下原型:
VOID NTAPI KiUserExceptionDispatcher( PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context);
从栈调用栈中可以找到这个CONTEXT的地址:
来看看这个CONTEXT结构:
其中保存了异常发生时的寄存器上下文。Windbg中有专门解析这个结构的指令 .cxr,我们来看看寄存器的值:
看到EIP的值正是发生非法读取的地址,因此,可以判断是读指令的时候发生的异常。我们来看看栈被破坏的大概范围,使用的是dds指令,这个指令将地址范围内的符号进行解析:
0:000:x86> dds 39f568 39ffff
0039f568 0000000b
0039f56c 0000000c
0039f570 0000000d
0039f574 0000000e
0039f578 0000000f
0039f57c 00000010
0039f580 00000011
0039f584 00000012
0039f588 00000013
0039f58c 00000014
0039f590 00000015
0039f594 00000016
0039f598 00000017
0039f59c 00000018
0039f5a0 00000019
0039f5a4 0000001a
0039f5a8 0000001b
0039f5ac 0000001c
0039f5b0 0000001d
0039f5b4 0000001e
0039f5b8 0000001f
0039f5bc 00000020
0039f5c0 00000021
0039f5c4 00000022
0039f5c8 00000023
0039f5cc 00000024
0039f5d0 00000025
0039f5d4 00000026
0039f5d8 00000027
0039f5dc 00000028
0039f5e0 00000029
0039f5e4 0000002a
0039f5e8 0000002b
0039f5ec 0000002c
0039f5f0 0000002d
0039f5f4 0000002e
0039f5f8 0000002f
0039f5fc 00000030
0039f600 00000031
0039f604 00000032
0039f608 00000033
0039f60c 00000034
0039f610 00000035
0039f614 00000036
0039f618 00000037
0039f61c 00000038
0039f620 00000039
0039f624 0000003a
0039f628 0000003b
0039f62c 0000003c
0039f630 0000003d
0039f634 0000003e
0039f638 0000003f
0039f63c 00000040
0039f640 00000041
0039f644 00000042
0039f648 00000043
0039f64c 00000044
0039f650 00000045
0039f654 00000046
0039f658 00000047
0039f65c 00000048
0039f660 00000049
0039f664 0000004a
0039f668 0000004b
0039f66c 0000004c
0039f670 0000004d
0039f674 0000004e
0039f678 0000004f
0039f67c 00000050
0039f680 00000051
0039f684 00000052
0039f688 00000053
0039f68c 00000054
0039f690 00000055
0039f694 00000056
0039f698 00000057
0039f69c 00000058
0039f6a0 00000059
0039f6a4 0000005a
0039f6a8 0000005b
0039f6ac 0000005c
0039f6b0 0000005d
0039f6b4 0000005e
0039f6b8 0000005f
0039f6bc 00000060
0039f6c0 00000061
0039f6c4 00000062
0039f6c8 00000063
0039f6cc 00000064
0039f6d0 00000065
0039f6d4 00000066
0039f6d8 00000067
0039f6dc 00000068
0039f6e0 00000069
0039f6e4 0000006a
0039f6e8 0000006b
0039f6ec 0000006c
0039f6f0 0000006d
0039f6f4 0000006e
0039f6f8 0000006f
0039f6fc 00000070
0039f700 00000071
0039f704 00000072
0039f708 00000073
0039f70c 00000074
0039f710 00000075
0039f714 00000076
0039f718 00000077
0039f71c 00000078
0039f720 00000079
0039f724 0000007a
0039f728 0000007b
0039f72c 0000007c
0039f730 0000007d
0039f734 0000007e
0039f738 0000007f
0039f73c 00000080
0039f740 00000081
0039f744 00000082
0039f748 00000083
0039f74c 00000084
0039f750 00000085
0039f754 00000086
0039f758 00000087
0039f75c 00000088
0039f760 00000089
0039f764 0000008a
0039f768 0000008b
0039f76c 0000008c
0039f770 0000008d
0039f774 0000008e
0039f778 0000008f
0039f77c 00000090
0039f780 00000091
0039f784 00000092
0039f788 00000093
0039f78c 00000094
0039f790 00000095
0039f794 00000096
0039f798 00000097
0039f79c 00000098
0039f7a0 00000099
0039f7a4 0000009a
0039f7a8 0000009b
0039f7ac 0000009c
0039f7b0 0000009d
0039f7b4 0000009e
0039f7b8 0000009f
0039f7bc 000000a0
0039f7c0 000000a1
0039f7c4 000000a2
0039f7c8 000000a3
0039f7cc 000000a4
0039f7d0 000000a5
0039f7d4 000000a6
0039f7d8 000000a7
0039f7dc 000000a8
0039f7e0 000000a9
0039f7e4 000000aa
0039f7e8 000000ab
0039f7ec 000000ac
0039f7f0 000000ad
0039f7f4 000000ae
0039f7f8 000000af
0039f7fc 000000b0
0039f800 000000b1
0039f804 000000b2
0039f808 000000b3
0039f80c 000000b4
0039f810 000000b5
0039f814 000000b6
0039f818 000000b7
0039f81c 000000b8
0039f820 000000b9
0039f824 000000ba
0039f828 000000bb
0039f82c 000000bc
0039f830 000000bd
0039f834 000000be
0039f838 000000bf
0039f83c 000000c0
0039f840 000000c1
0039f844 000000c2
0039f848 000000c3
0039f84c 000000c4
0039f850 000000c5
0039f854 000000c6
0039f858 000000c7
0039f85c 00b455f8
0039f860 00000001
0039f864 00590f20
0039f868 00000111
0039f86c 00bb9930
0039f870 00b99890
0039f874 00000000
0039f878 001005d2
0039f87c 00000f20
0039f880 0039f8a4
0039f884 75b096d5 user32!SendMessageW+0x7f
0039f888 00b455d0
0039f88c 00000000
0039f890 5b68855d mfc90+0x5855d
0039f894 000003e8
0039f898 000209e6
0039f89c 00000000
0039f8a0 00000001
0039f8a4 0039f8bc
0039f8a8 75b45feb user32!xxxButtonNotifyParent+0x66
0039f8ac 001005d2
0039f8b0 00000111
0039f8b4 000003e8
0039f8b8 000209e6
0039f8bc 0039f8e4
0039f8c0 75b4612c user32!xxxBNReleaseCapture+0x138
可以看到,在地址0039f884 之后又可以解析出来符号,所以,我们大概可以判断39f568-0039f884 之间的栈被破坏了。对于这个DUMP文件,我们能做的差不多就这些了。由于栈被破坏,我们无法再回溯函数调用,无法再进一步定位异常的位置。因此,DUMP文件由于是一种静止的快照,有点时候,只能作为一种辅助分析手段,要定位具体的原因,需要多个DUMP文件综合分析,还需要进行动态调试。