-
-
[翻译]手把手教你修复被破坏的堆栈
-
2019-9-27 22:16 8090
-
English: http://blogs.microsoft.co.il/sasha/2011/07/20/manual-stack-walking/
栈被破坏了可一点都不好玩儿!尤其是当你在分析crash dump或者程序发生异常的时候,我猜首先要做的事,可能就是先查看一下儿堆栈调用。
但是发现当前线程的栈被破坏了,你的主要分析工具也无法显示堆栈,这可咋办哩?
尽管如此,有时候也可以修复被破坏的堆栈。我已经出了一些关于.NET和C++调试的教程,但是大家的要求,我也会再展示一个例子
.net 调试:http://sela.co.il/syl/syllabus.aspx?CourseCode=DNDebug
c++调试:http://sela.co.il/syl/syllabus.aspx?CourseCode=CPPDbg
你可以从http://s.sashag.net/ocrImV这个网盘中下载本次例子中需要的dump文件、符号文件以及程序源码文件!
OK,我们使用WinDbg(32-bit)打开dump文件,获取如下信息:
User Mini Dump File: Only registers, stack and portions of memory are available . . . This dump file has an exception of interest stored in it. The stored exception information can be accessed via .ecxr. (1ed0.870): Access violation – code c0000005 (first/second chance not available) eax=00000000 ebx=00000001 ecx=73536122 edx=00000000 esi=002af37c edi=0000004e eip=00000000 esp=002af1a8 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 00000000 ?? ??? 0:000> k ChildEBP RetAddr WARNING: Frame IP not in any known module. Following frames may be wrong. 002af1a4 00000000 0x0
看到当前指令的地址是0x00000000的时候就猜到一定不是什么好事儿,意味着指令指针寄存器(EIP)已经被破坏了。同时你也可以看到EBP也被破坏了,EBP的值也是0x00000000,
这也是为什么k命令无法显示任何堆栈信息的原因。
好在ESP寄存器的值看起来是个正常的值,也不能确定这个值一定正常的,但是我们可以尝试读取ESP指向的内存,我们我们能正常读取它指向的内存数据,那几乎100%确定,ESP仍指向的是栈地址。
因为这是一个mini-dump文件,只包含和栈相关的内存数据。
如果ESP确实指向原始栈地址,我们可以尝试手动查看栈上的数据,寻找和返回地址相似的数据。在返回地址前面,我们应该能看到一个之前PUSH到栈里面的EBP的值,这个EBP是上一个函数的EBP。
其实每一个返回地址前面的EBP都保存的是上一个函数的EBP地址,除非开启了栈帧优化(FPO),关于FPO我打算以后再写一篇文章专门来介绍。
关于FPO以及其历史的介绍,网上有一篇文章讲的很详细: https://blogs.msdn.microsoft.com/larryosterman/2007/03/12/fpo/
如果你忘记了堆栈的内存布局,可以来复习一下Intel x86 函数调用约定: http://www.unixwiz.net/techtips/win32-callconv-asm.html
下面是ESP包含的原始栈信息,这时可以设置好指向BatteryMeter.pdb文件的符号路径,方便使用dds命令时正确的显示符号信息:
0:000> dds ESP 002af1a8 00000000 002af1ac 002af120 002af1b0 00000000 002af1b4 014cfe90 002af1b8 002af0fc 002af1bc 742fd594 uxtheme!StreamInit+0x36 002af1c0 002af180 002af1c4 01850815 002af1c8 0000029e 002af1cc 00000000 002af1d0 00000000 002af1d4 737990fa 002af1d8 002af210 002af1dc 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135] 002af1e0 00000004 002af1e4 77dbc290 mfc100u!AfxDlgProc […\dlgcore.cpp @ 22] 002af1e8 00000000 002af1ec 002af284 002af1f0 00000001 002af1f4 00a24a74 002af1f8 00a5ec90 002af1fc 00a24cf0 002af200 002af228 002af204 002af198 002af208 002af234 002af20c 73799332 002af210 002af248 002af214 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135]
首先很高兴能看到ESP指向的内存是dump文件中的有效内存,说明我们正在查看堆栈。这里有几个地址很有可能是返回地址,并且紧接在它们前面的地址就是上一个函数堆栈的EBP。
我们可以挨个检查这些保存在栈上的EBP值指向的内存,如果是在栈上,说明这个栈是可以被我们看到的。
0:000> dd 002af0fc L1 002af0fc ???????? 0:000> dd 002af210 L1 002af210 002af248
0:000> dds 002af210 L2 002af210 002af248 002af214 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135] 0:000> dds 002af248 L2 002af248 002af280 002af24c 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135] 0:000> dds 002af280 L2 002af280 002af2b8 002af284 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135] 0:000> dds 002af2b8 L2 002af2b8 002af2f0 002af2bc 013719be BatteryMeter!RecurseDeep+0x4e […\batterymeterdlg.cpp @ 135] 0:000> dds 002af2f0 L2 002af2f0 002af304 002af2f4 013719f7 BatteryMeter!CBatteryMeterDlg::OnCPUSelectorChanged+0x27 […\batterymeterdlg.cpp @ 142] 0:000> dds 002af304 L2 002af304 002af318 002af308 77d92c8c mfc100u!_AfxDispatchCmdMsg+0x58 […\cmdtarg.cpp @ 112]
我们可以一直这样遍历EBP,直到最后一层栈帧,根据目前已经获取的堆栈信息,我们发现在栈被破坏之前,有个名为RecurseDeep的函数,一直在调用自己,至少调用了4次。
WinDbg有一个命令可以帮我们来重建堆栈,我们只需要猜测一下ESP、EBP、EIP的值,它就会按照我们给定的值去构造一个合理的堆栈。
我们可以假设EBP就是我们刚才找到的第一个在栈上的EBP的值,EIP可以假设为刚才EBP后面的返回地址,而ESP可以和EBP的值一样,然后就可以看到Windbg输出以下信息:
0:000> k = 002af210 002af210 013719be ChildEBP RetAddr 002af210 013719be BatteryMeter!RecurseDeep+0x4e 002af248 013719be BatteryMeter!RecurseDeep+0x4e 002af280 013719be BatteryMeter!RecurseDeep+0x4e 002af2b8 013719be BatteryMeter!RecurseDeep+0x4e 002af2f0 013719f7 BatteryMeter!RecurseDeep+0x4e 002af304 77d92c8c BatteryMeter!CBatteryMeterDlg::OnCPUSelectorChanged+0x27 002af318 77d92e51 mfc100u!_AfxDispatchCmdMsg+0x58 002af334 77dc6d36 mfc100u!CCmdTarget::OnCmdMsg+0x124 002af358 77e1c4cb mfc100u!CPropertySheet::OnCmdMsg+0x1d 002af388 77e1bc7f mfc100u!CWnd::OnNotify+0x7b 002af454 002af478 mfc100u!CWnd::OnWndMsg+0x9e … source information and the rest of the stack snipped for brevity
此时,我们已经利用已知极少的信息,将一个被破坏堆栈恢复成了正常的调用堆栈的样子,并且帮助我们定位了导致堆栈被破坏的罪魁祸首!
查看BatteryMeter!RecurseDeep的源码,就可以找出导致堆栈破坏的根本原因:原来这个破坏堆栈的函数,并没有破坏自己的栈帧,而是返回到了之前函数的栈帧的地方,并用零覆盖了一小块内存区域
参考资料:
- https://blogs.msdn.microsoft.com/ntdebugging/2010/05/12/x64-manual-stack-reconstruction-and-stack-walking/
- https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/manually-walking-a-stack
- http://www.hexblog.com/?p=104
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课