首页
社区
课程
招聘
[翻译]手把手教你修复被破坏的堆栈
2019-9-27 22:16 8090

[翻译]手把手教你修复被破坏的堆栈

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

尝试第一个EBP失败了,但是第二个成功了,当我们拿到了一个有效的保存在栈上的EBP的时候,我们就可以开始手动修复堆栈了,这个EBP指向的是前一个栈帧的EBP,正常的EBP就这样一层一层链起来的:

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直播授课

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回