本文实验的例子来自《windows高级调试》第6.2.2节,参考书中的方法进行。
1.通过windbg分析堆块
(1)在命令行运行程序,输入参数(输入超过10个字符),在出现如下提示的时候,使用windbg attach到该进程。
(2)按任意键继续执行,执行完后,程序崩溃到windbg,使用kb命令查看堆栈如下:
(3)我们看到报的堆栈信息是在HeapFree函数中,其实我们知道原因是wcscpy函数溢出了,但崩溃点却不在溢出发生的地方,堆破坏的最大问题在于造成错误的代码无法在发生堆破坏时被发现,我们可以通过查看当前堆的一些情况进行分析。
(4)查看默认堆的详细情况,由于信息比较多,我仅仅把最后一部分打印出来。
(5) 我们看到最后一个堆块是有问题的,可以看下该堆的内容,我们从004e5188地址开始打印。
(6)我们看到了一系列的0x31字符,就是我们参数输入的“1”,我们可以用du命令再打印下:
(7)果然是我们输入的参数,而且参数越过了堆块004e51a8,我们把该堆块的头打印出来:
发现堆块的头全都被我们输入的参数覆盖了,这就导致了内存访问异常。
2.通过windbg+普通页堆分析
上面的程序很简单,很容易通过分析堆块来找出问题,然而通过分析堆块来查找堆溢出的问题并非总是可行的,还有一种方式可以让我们很快找到堆溢出的原因,就是页堆,我们可以通过Application Verifier工具开启页堆。
通过Add Application菜单,添加我们要分析的应用程序,然后再右边会有Heaps的选项。
右击Heaps 选择属性,在弹出框中,把full去掉(我们先分析普通页堆,之后再分析完全页堆)。
完成后我们按照原来的方式执行,程序崩溃后,打印堆栈如下。
上面给了我们堆的内存信息,要转储页堆数据的内容,我们需要将该地址减去32字节然后再转成_DPH_BLOCK_INFORMATION结构,如下:
上述结构体最重要的一项是StackTrace,可以查找堆栈的回溯,如下:
通过上述信息,我们很快就能找到有问题堆的分配栈回溯,通过分析代码很容易发现堆的溢出情况。
3.通过windbg+完全页堆分析
普通页堆还不是很直观的发现堆溢出的地方,但是开启了完全页堆就不一样了,完全页堆开启后,程序只要发生堆溢出就立马崩溃,这样就很好的发现问题所在了,开启完全页堆:
再次运行程序,崩溃后堆栈情况:
很快就找到问题所在了,我们可以把wcscpy拷贝的字符打印出来:
就是我们传入的参数导致的溢出。