前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一管血出来,看看崩溃点是什么,其实我的系列文章中,关于崩溃类的dump比较少,刚好补一篇上来,话不多说,上 windbg 。,在 windbg 中有一个 !analyze -v 命令可以自动化分析,输出信息如下:,从卦中的 ExceptionCode: c0000374 异常码来看,表示当前 nt堆损坏,这就尴尬了,一个C#程序咋会把 windows nt 堆给弄坏了,可能是引入了第三方的 C++ 代码。,由于异常分异常前和异常后,所以需要用 .ecxr 将当前线程切到异常前的崩溃点,然后使用 k 观察当前的线程栈。,从卦中的 KERNELBASE!LocalFree 方法可知,程序正在释放一个 堆块,在释放的过程中抛出了异常,那为什么会释放失败呢?原因就比较多了,比如:,原因1:Free 一个已 Free 的堆块,原因2:Free 了一个别人的堆块,那到底是哪一种情况呢?有经验的朋友应该知道,ntheap 默认开启了 损坏退出 机制,用 !heap -s 命令就能显示出这种损坏原因。,从卦中可以清晰的看到错误类型:Error type: HEAP_FAILURE_BLOCK_NOT_BUSY ,这是经典的 Double Free,也就是上面的 原因1 ,接下来我们就要寻找代码源头了。。。,从线程栈上看,底层的方法区都是十六进制,这表示当前是托管方法,这就好办了,我们用 !clrstack 看看托管代码是什么?,从卦中可以清晰的看到是托管方法 StructToBytes() 引发的,接下来导出这个方法的源码,截图如下:,
,从方法逻辑看,这位朋友用了 Marshal 做了互操作,为了能够进一步分析,需要找到 localResource 堆块句柄,使用 !clrstack -l 显示方法栈参数。,经过对比,发现并没有显示 localResource 值,这就很尴尬了。。。一般在 dump 中 IntPtr 类型是显示不出来的,遇到好几次了,比较闹心。。。既然显示不出来堆块句柄值。。。那怎么办呢?天要绝人之路吗?,既然托管层找不到堆块句柄,那就到非托管层去找,比如这里的 KERNELBASE!LocalFree+0x2f 函数,msdn 上的定义如下:,那如何找到这个 hMem 值呢?在 x86 程序中可以直接用 kb 就能提取出来,但在 x64 下是无效的,因为它是用寄存器来传递方法参数,此时的寄存器值已经刷新到了 ntdll!NtWaitForMultipleObjects+0x14 上,比如下面的 rcx 肯定不是 hMem 值。,怎么办呢?其实还有一条路,就是观察 KERNELBASE!LocalFree+0x2f 方法的汇编代码,看看它有没有将 rcx 临时性的存到 线程栈 上。,很开心的看到,当前的 rcx 存到了 rsp+8 位置上,那如何拿到 rsp 呢?可以用 k 提取父函数 mscorlib_ni+0x63c78f 中的 Child-SP 值。,因为这个 Child-SP 是 call 之前的 sp, 汇编中的 sp 是 call 之后的,所以相差一个 retaddr 指针单元,所以计算方法是:ChildSp- 0x8 + 0x8 就是 堆块句柄。,上面的 000000002c873720 就是堆块句柄,接下来用命令 !heap -x 000000002c873720 观察堆块情况。,果不其然,这个堆块已经是 Free 状态了,再 Free 必然会报错,经典的 Double Free 哈。,仔细阅读源码,发现有两个问题。,没有对 localResource 加锁处理,在并发的时候容易出现问题。,localResource 是一个类级别变量,在多个方法中被使用。,将信息反馈给朋友之后,建议朋友加锁并降低 localResource 作用域。,这次偶发的生产崩溃事故,主要原因是朋友的代码在逻辑上出了点问题,没有合理的保护好 localResource 句柄资源,反复释放导致的 ntheap 破坏。,这个 dump 虽然问题比较小白,但逆向分析找出原因,还是挺考验基本功的。
© 版权声明
文章版权归作者所有,未经允许请勿转载。