-
-
[翻译]手把手教你修复被破坏的堆栈
-
发表于: 2019-9-27 22:16 9045
-
English: f34K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6K6i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0Q4x3X3g2A6L8q4)9J5c8Y4y4S2M7$3S2S2i4K6u0r3x3U0l9I4x3g2)9J5c8U0l9%4i4K6u0r3x3U0m8Q4x3V1k6E0j5h3&6#2j5h3I4Q4x3X3c8K6N6r3q4U0K9#2)9J5k6s2N6S2L8r3E0A6L8X3N6Q4x3V1j5`.
栈被破坏了可一点都不好玩儿!尤其是当你在分析crash dump或者程序发生异常的时候,我猜首先要做的事,可能就是先查看一下儿堆栈调用。
但是发现当前线程的栈被破坏了,你的主要分析工具也无法显示堆栈,这可咋办哩?
尽管如此,有时候也可以修复被破坏的堆栈。我已经出了一些关于.NET和C++调试的教程,但是大家的要求,我也会再展示一个例子
.net 调试:fe5K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4W2L8r3q4Q4x3X3g2U0L8#2)9J5k6h3W2D9i4K6u0r3M7%4W2D9i4K6u0r3M7%4W2D9L8r3q4T1N6i4y4Q4x3X3g2S2M7%4m8^5i4K6y4r3b7$3!0#2M7Y4y4W2b7$3!0V1k6g2)9K6c8p5c8z5c8r3g2T1N6h3M7`.
c++调试:f89K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4W2L8r3q4Q4x3X3g2U0L8#2)9J5k6h3W2D9i4K6u0r3M7%4W2D9i4K6u0r3M7%4W2D9L8r3q4T1N6i4y4Q4x3X3g2S2M7%4m8^5i4K6y4r3b7$3!0#2M7Y4y4W2b7$3!0V1k6g2)9K6c8p5y4b7f1p5c8T1k6H3`.`.
你可以从208K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4Q4x3X3g2K6j5i4y4Z5j5h3N6Q4x3X3g2F1k6i4c8Q4x3V1k6G2j5%4u0u0L8g2k6Q4c8e0S2Q4b7V1k6Q4z5e0W2Q4c8e0c8Q4b7U0S2Q4b7f1q4Q4c8e0N6Q4b7V1c8Q4z5e0q4Q4c8e0N6Q4z5f1u0Q4z5e0S2Q4c8e0c8Q4b7U0S2Q4b7f1c8Q4c8e0c8Q4b7U0S2Q4z5p5u0Q4c8e0S2Q4b7V1c8Q4b7V1c8Q4c8e0k6Q4z5f1y4Q4b7f1y4Q4c8e0k6Q4b7f1y4Q4b7e0q4Q4c8e0c8Q4b7V1g2Q4z5p5u0Q4c8e0g2Q4b7f1c8Q4z5e0m8Q4c8e0c8Q4b7U0S2Q4b7f1c8Q4c8e0W2Q4z5f1y4Q4z5o6m8Q4c8e0S2Q4b7e0k6Q4z5o6q4Q4c8e0N6Q4z5f1q4Q4z5o6c8V1N6h3#2H3i4@1f1$3i4K6V1$3i4K6R3%4i4@1f1@1i4@1u0n7i4@1t1$3i4@1f1K6i4K6R3H3i4K6R3I4i4@1f1%4i4@1q4o6i4@1p5$3i4@1f1#2i4K6S2r3i4@1t1%4i4@1f1$3i4K6V1$3i4K6R3%4i4@1f1@1i4@1u0n7i4@1t1$3i4@1f1@1i4@1u0n7i4@1p5#2i4@1f1#2i4K6S2r3i4K6S2m8i4@1f1%4i4@1p5^5i4K6S2n7i4@1f1#2i4@1u0m8i4K6S2r3i4@1f1$3i4@1u0m8i4K6V1H3i4@1f1%4i4@1p5H3i4K6R3I4i4@1f1$3i4K6V1$3i4K6R3%4i4@1f1@1i4@1u0n7i4@1t1$3i4@1g2r3i4@1u0o6i4K6R3I4
OK,我们使用WinDbg(32-bit)打开dump文件,获取如下信息:
看到当前指令的地址是0x00000000的时候就猜到一定不是什么好事儿,意味着指令指针寄存器(EIP)已经被破坏了。同时你也可以看到EBP也被破坏了,EBP的值也是0x00000000,
这也是为什么k命令无法显示任何堆栈信息的原因。
好在ESP寄存器的值看起来是个正常的值,也不能确定这个值一定正常的,但是我们可以尝试读取ESP指向的内存,我们我们能正常读取它指向的内存数据,那几乎100%确定,ESP仍指向的是栈地址。
因为这是一个mini-dump文件,只包含和栈相关的内存数据。
如果ESP确实指向原始栈地址,我们可以尝试手动查看栈上的数据,寻找和返回地址相似的数据。在返回地址前面,我们应该能看到一个之前PUSH到栈里面的EBP的值,这个EBP是上一个函数的EBP。
其实每一个返回地址前面的EBP都保存的是上一个函数的EBP地址,除非开启了栈帧优化(FPO),关于FPO我打算以后再写一篇文章专门来介绍。
关于FPO以及其历史的介绍,网上有一篇文章讲的很详细: 755K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4M7#2)9J5k6h3#2K6k6r3&6Q4x3X3g2E0K9h3y4J5L8%4y4G2k6Y4c8Q4x3X3g2U0L8$3#2Q4x3V1k6D9j5i4u0J5P5h3!0K6N6r3g2J5L8h3q4F1i4K6u0r3x3U0l9H3y4#2)9J5c8U0l9K6i4K6u0r3x3e0u0Q4x3V1k6X3M7r3!0Q4x3V1j5`.
如果你忘记了堆栈的内存布局,可以来复习一下Intel x86 函数调用约定: 24bK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4g2F1K9i4S2%4K9i4A6Q4x3X3g2F1k6i4c8Q4x3V1k6@1k6h3y4Z5N6r3W2H3M7#2)9J5c8Y4N6A6L8U0x3J5i4K6u0V1j5$3q4D9L8r3y4G2L8Y4k6Q4x3X3c8S2M7$3#2Q4x3X3g2Z5N6r3#2D9
下面是ESP包含的原始栈信息,这时可以设置好指向BatteryMeter.pdb文件的符号路径,方便使用dds命令时正确的显示符号信息:
首先很高兴能看到ESP指向的内存是dump文件中的有效内存,说明我们正在查看堆栈。这里有几个地址很有可能是返回地址,并且紧接在它们前面的地址就是上一个函数堆栈的EBP。
我们可以挨个检查这些保存在栈上的EBP值指向的内存,如果是在栈上,说明这个栈是可以被我们看到的。
我们可以一直这样遍历EBP,直到最后一层栈帧,根据目前已经获取的堆栈信息,我们发现在栈被破坏之前,有个名为RecurseDeep的函数,一直在调用自己,至少调用了4次。
WinDbg有一个命令可以帮我们来重建堆栈,我们只需要猜测一下ESP、EBP、EIP的值,它就会按照我们给定的值去构造一个合理的堆栈。
我们可以假设EBP就是我们刚才找到的第一个在栈上的EBP的值,EIP可以假设为刚才EBP后面的返回地址,而ESP可以和EBP的值一样,然后就可以看到Windbg输出以下信息:
此时,我们已经利用已知极少的信息,将一个被破坏堆栈恢复成了正常的调用堆栈的样子,并且帮助我们定位了导致堆栈被破坏的罪魁祸首!
查看BatteryMeter!RecurseDeep的源码,就可以找出导致堆栈破坏的根本原因:原来这个破坏堆栈的函数,并没有破坏自己的栈帧,而是返回到了之前函数的栈帧的地方,并用零覆盖了一小块内存区域