51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1340|回复: 0
打印 上一主题 下一主题

[原创] 论软件测试的重要性,记一次.NET程序崩溃分析

[复制链接]
  • TA的每日心情
    无聊
    前天 09:05
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-10-28 14:47:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    一、背景
      1.讲故事
      前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一管血出来,看看崩溃点是什么,其实我的系列文章中,关于崩溃类的dump比较少,刚好补一篇上来,话不多说,上 windbg 。
      二、WinDbg 分析
      1. 崩溃点在哪里
      在 windbg 中有一个 !analyze -v 命令可以自动化分析,输出信息如下:
    1. <font size="3"> 0:120> !analyze -v

    2.   *******************************************************************************

    3.   *                                                                             *

    4.   *                        Exception Analysis                                   *

    5.   *                                                                             *

    6.   *******************************************************************************

    7.   CONTEXT:  (.ecxr)

    8.   rax=00000000032fed38 rbx=00000000c0000374 rcx=0000000000000000

    9.   rdx=0000000000000020 rsi=0000000000000001 rdi=00007ffbada727f0

    10.   rip=00007ffbada0a8f9 rsp=000000003103c8b0 rbp=0000000000c40000

    11.    r8=00007ffb779bdab7  r9=00007ffb782e94c0 r10=0000000000002000

    12.   r11=000000002c4aa498 r12=0000000000000000 r13=000000003103eb60

    13.   r14=0000000000000000 r15=000000002c873720

    14.   iopl=0         nv up ei pl nz na pe nc

    15.   cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

    16.   ntdll!RtlReportFatalFailure+0x9:

    17.   00007ffb`ada0a8f9 eb00            jmp     ntdll!RtlReportFatalFailure+0xb (00007ffb`ada0a8fb)

    18.   Resetting default scope

    19.   EXCEPTION_RECORD:  (.exr -1)

    20.   ExceptionAddress: 00007ffbada0a8f9 (ntdll!RtlReportFatalFailure+0x0000000000000009)

    21.      ExceptionCode: c0000374

    22.     ExceptionFlags: 00000001

    23.   NumberParameters: 1

    24.      Parameter[0]: 00007ffbada727f0

    25.   ...</font>
    复制代码
    从代码中的 ExceptionCode: c0000374 异常码来看,表示当前 nt堆损坏?,这就尴尬了,一个C#程序咋会把 windows nt 堆给弄坏了,可能是引入了第三方的 C++ 代码。
      由于异常分异常前和异常后,所以需要用 .ecxr? 将当前线程切到异常前的崩溃点,然后使用 k 观察当前的线程栈。



    1. <font size="3"> 0:120> .ecxr ; k

    2.   rax=00000000032fed38 rbx=00000000c0000374 rcx=0000000000000000

    3.   rdx=0000000000000020 rsi=0000000000000001 rdi=00007ffbada727f0

    4.   rip=00007ffbada0a8f9 rsp=000000003103c8b0 rbp=0000000000c40000

    5.    r8=00007ffb779bdab7  r9=00007ffb782e94c0 r10=0000000000002000

    6.   r11=000000002c4aa498 r12=0000000000000000 r13=000000003103eb60

    7.   r14=0000000000000000 r15=000000002c873720

    8.   iopl=0         nv up ei pl nz na pe nc

    9.   cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

    10.   ntdll!RtlReportFatalFailure+0x9:

    11.   00007ffb`ada0a8f9 eb00            jmp     ntdll!RtlReportFatalFailure+0xb (00007ffb`ada0a8fb)

    12.     *** Stack trace for last set context - .thread/.cxr resets it

    13.    # Child-SP          RetAddr               Call Site

    14.   00 00000000`3103c8b0 00007ffb`ada0a8c3     ntdll!RtlReportFatalFailure+0x9

    15.   01 00000000`3103c900 00007ffb`ada1314e     ntdll!RtlReportCriticalFailure+0x97

    16.   02 00000000`3103c9f0 00007ffb`ada1345a     ntdll!RtlpHeapHandleError+0x12

    17.   03 00000000`3103ca20 00007ffb`ad9aef41     ntdll!RtlpHpHeapHandleError+0x7a

    18.   04 00000000`3103ca50 00007ffb`ad9be520     ntdll!RtlpLogHeapFailure+0x45

    19.   05 00000000`3103ca80 00007ffb`aa3882bf     ntdll!RtlFreeHeap+0x966e0

    20.   06 00000000`3103cb20 00007ffb`66fac78f     KERNELBASE!LocalFree+0x2f

    21.   07 00000000`3103cb60 00007ffb`66f273a4     mscorlib_ni+0x63c78f

    22.   08 00000000`3103cc10 00007ffb`185c4fde     mscorlib_ni!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x24 [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\marshal.cs @ 1212]

    23.   09 00000000`3103cc50 00007ffb`185c4fa1     0x00007ffb`185c4fde

    24.   0a 00000000`3103cca0 00007ffb`185edc82     0x00007ffb`185c4fa1

    25.   ...</font>
    复制代码
    从代码中的 KERNELBASE!LocalFree 方法可知,程序正在释放一个 堆块,在释放的过程中抛出了异常,那为什么会释放失败呢?原因就比较多了,比如:
      原因1:Free 一个已 Free 的堆块
      原因2:Free 了一个别人的堆块
      那到底是哪一种情况呢?有经验的朋友应该知道,ntheap 默认开启了 损坏退出? 机制,用 !heap -s 命令就能显示出这种损坏原因。



    1. <font size="3">0:120> !heap -s

    2.   ************************************************************************************************************************

    3.                                                 NT HEAP STATS BELOW

    4.   ************************************************************************************************************************

    5.   **************************************************************

    6.   *                                                            *

    7.   *                  HEAP ERROR DETECTED                       *

    8.   *                                                            *

    9.   **************************************************************

    10.   Details:

    11.   Heap address:  0000000000c40000

    12.   Error address: 000000002c873710

    13.   Error type: HEAP_FAILURE_BLOCK_NOT_BUSY

    14.   Details:    The caller performed an operation (such as a free

    15.               or a size check) that is illegal on a free block.

    16.   Follow-up:  Check the error's stack trace to find the culprit.

    17.   Stack trace:

    18.   Stack trace at 0x00007ffbada72848

    19.       00007ffbad9aef41: ntdll!RtlpLogHeapFailure+0x45

    20.       00007ffbad9be520: ntdll!RtlFreeHeap+0x966e0

    21.       00007ffbaa3882bf: KERNELBASE!LocalFree+0x2f

    22.       00007ffb66fac78f: mscorlib_ni+0x63c78f

    23.       00007ffb66f273a4: mscorlib_ni!System.Runtime.InteropServices.Marshal.FreeHGlobal+0x24

    24.       00007ffb185c4fde: +0x185c4fde

    25.   LFH Key                   : 0x1d4fd2a71d8b8280

    26.   Termination on corruption : ENABLED

    27.             Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast

    28.                               (k)     (k)    (k)     (k) length      blocks cont. heap

    29.   -------------------------------------------------------------------------------------

    30.   0000000000c40000 00000002   16756  13688  16364    220   140     5    2      0   LFH

    31.   ...</font>
    复制代码


    从代码中可以清晰的看到错误类型:Error type: HEAP_FAILURE_BLOCK_NOT_BUSY? ,这是经典的 Double Free,也就是上面的 原因1 ,接下来我们就要寻找代码源头了。。。
      2. 是谁的代码引发的
      从线程栈上看,底层的方法区都是十六进制,这表示当前是托管方法,这就好办了,我们用 !clrstack 看看托管代码是什么?



    1. <font size="3">0:120> !clrstack

    2.   OS Thread Id: 0x4d54 (120)

    3.           Child SP               IP Call Site

    4.   000000003103cb88 00007ffbad9b0544 [InlinedCallFrame: 000000003103cb88] Microsoft.Win32.Win32Native.LocalFree(IntPtr)

    5.   000000003103cb88 00007ffb66fac78f [InlinedCallFrame: 000000003103cb88] Microsoft.Win32.Win32Native.LocalFree(IntPtr)

    6.   000000003103cb60 00007ffb66fac78f DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr)

    7.   000000003103cc10 00007ffb66f273a4 System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr) [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\marshal.cs @ 1212]

    8.   000000003103cc50 00007ffb185c4fde xxxx.StructToBytes(System.Object)

    9.   000000003103ced0 00007ffb185ec6b1 xxx.SendDoseProject(System.String)

    10.   ...</font>
    复制代码


    从代码中可以清晰的看到是托管方法 StructToBytes() 引发的,接下来导出这个方法的源码,截图如下:





    从方法逻辑看,这位朋友用了 Marshal 做了互操作,为了能够进一步分析,需要找到 localResource 堆块句柄,使用 !clrstack -l 显示方法栈参数。


    1. <font size="3"> 0:120> !clrstack -l

    2.   OS Thread Id: 0x4d54 (120)

    3.   ...

    4.   000000003103cca0 00007ffb185c4fa1 xxx.StructToBytes(System.Object)

    5.       LOCALS:

    6.           0x000000003103cd0c = 0x000000000000018f

    7.           0x000000003103ccf8 = 0x0000000003084420

    8.           0x000000003103ccf0 = 0x0000000003084420

    9.           0x000000003103cce8 = 0x0000000000000000

    10.           0x000000003103cce0 = 0x0000000000000000

    11.   ...</font>
    复制代码
    经过对比,发现并没有显示 localResource 值,这就很尴尬了。。。一般在 dump 中 IntPtr 类型是显示不出来的,遇到好几次了,比较闹心。。。既然显示不出来堆块句柄值。。。那怎么办呢?天要绝人之路吗?
      3. 绝处逢生
      既然托管层找不到堆块句柄,那就到非托管层去找,比如这里的 KERNELBASE!LocalFree+0x2f 函数,msdn 上的定义如下:



    1. <font size="3"> HLOCAL LocalFree(

    2.     [in] _Frees_ptr_opt_ HLOCAL hMem

    3.   );</font>
    复制代码


    那如何找到这个 hMem 值呢?在 x86 程序中可以直接用 kb? 就能提取出来,但在 x64 下是无效的,因为它是用寄存器来传递方法参数,此时的寄存器值已经刷新到了 ntdll!NtWaitForMultipleObjects+0x14? 上,比如下面的 rcx 肯定不是 hMem 值。


    1. <font size="3">0:120> r

    2.   rax=000000000000005b rbx=0000000000005b08 rcx=0000000000000002

    3.   rdx=000000003103b690 rsi=0000000000000002 rdi=0000000000000000

    4.   rip=00007ffbad9b0544 rsp=000000003103b658 rbp=0000000000001da4

    5.    r8=0000000000001000  r9=0101010101010101 r10=0000000000000000

    6.   r11=0000000000000246 r12=0000000000000000 r13=000000003103c930

    7.   r14=0000000000001f98 r15=0000000000000000

    8.   iopl=0         nv up ei pl zr na po nc

    9.   cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

    10.   ntdll!NtWaitForMultipleObjects+0x14:

    11.   00007ffb`ad9b0544 c3              ret</font>
    复制代码
    怎么办呢?其实还有一条路,就是观察 KERNELBASE!LocalFree+0x2f 方法的汇编代码,看看它有没有将 rcx 临时性的存到 线程栈 上。


    1. <font size="3">0:120> u KERNELBASE!LocalFree

    2.   KERNELBASE!LocalFree:

    3.   00007ffb`aa388290 48895c2410      mov     qword ptr [rsp+10h],rbx

    4.   00007ffb`aa388295 4889742418      mov     qword ptr [rsp+18h],rsi

    5.   00007ffb`aa38829a 48894c2408      mov     qword ptr [rsp+8],rcx

    6.   00007ffb`aa38829f 57              push    rdi

    7.   00007ffb`aa3882a0 4883ec30        sub     rsp,30h

    8.   00007ffb`aa3882a4 488bd9          mov     rbx,rcx

    9.   00007ffb`aa3882a7 f6c308          test    bl,8

    10.   00007ffb`aa3882aa 753f            jne     KERNELBASE!LocalFree+0x5b (00007ffb`aa3882eb)</font>
    复制代码


    很开心的看到,当前的 rcx 存到了 rsp+8? 位置上,那如何拿到 rsp 呢?可以用 k 提取父函数 mscorlib_ni+0x63c78f? 中的 Child-SP 值。


    1. <font size="3">0:120> k

    2.    # Child-SP          RetAddr               Call Site

    3.    ...

    4.   0e 00000000`3103ca80 00007ffb`aa3882bf     ntdll!RtlFreeHeap+0x966e0

    5.   0f 00000000`3103cb20 00007ffb`66fac78f     KERNELBASE!LocalFree+0x2f

    6.   10 00000000`3103cb60 00007ffb`66f273a4     mscorlib_ni+0x63c78f

    7.   ...</font>
    复制代码


    因为这个 Child-SP是 call 之前的 sp, 汇编中的 sp 是 call 之后的,所以相差一个 retaddr? 指针单元,所以计算方法是:ChildSp- 0x8 + 0x8 就是 堆块句柄。


    1. <font size="3">0:120> dp 00000000`3103cb60-0x8+0x8 L1

    2.   00000000`3103cb60  00000000`2c873720</font>
    复制代码




    上面的 000000002c873720? 就是堆块句柄,接下来用命令 !heap -x 000000002c873720 观察堆块情况。


    1. <font size="3"> 0:120> !heap -x 000000002c873720

    2.   Entry             User              Heap              Segment               Size  PrevSize  Unused    Flags

    3.   -------------------------------------------------------------------------------------------------------------

    4.   000000002c873710  000000002c873720  0000000000c40000  000000002c8703c0        30      -            0  LFH;free </font>
    复制代码


    果不其然,这个堆块已经是 Free 状态了,再 Free 必然会报错,经典的 Double Free 哈。
      4. 回首再看源码
      仔细阅读源码,发现有两个问题。
      没有对 localResource 加锁处理,在并发的时候容易出现问题。
      localResource 是一个类级别变量,在多个方法中被使用。
      将信息反馈给朋友之后,建议朋友加锁并降低 localResource 作用域。
      三、总结
      这次偶发的生产崩溃事故,主要原因是朋友的代码在逻辑上出了点问题,没有合理的保护好 localResource 句柄资源,反复释放导致的 ntheap 破坏。
      这个 dump 虽然问题比较小白,但逆向分析找出原因,还是挺考验基本功的。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-23 10:33 , Processed in 0.064804 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表