基于异常的猎杀行动——自保护触发自杀

阅读量    299138 | 评论 1

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0、引言

这次分析的问题 ,非常有意思,一款软件的自保护机制触发了,抛出了一次异常,在异常链的处理中做了点简单的检测,命中则再次触发一次异常,即异常中嵌套异常,然而最具有挑战意思的事情是如何找到案发第一现场,屋漏偏逢连夜雨,Windbg提供的常用命令集体罢工,增添了分析的趣味性。做一次侦探,从dmp的蛛丝马迹中找到案发第一现场,分析问题的根本原因,便是此文的来由。

涉及到以下知识点:

1、命令失效时,如何手动在dmp中找到关键调用,关键数据;

2、如何手动重构栈帧,复原调用栈;

3、用户态异常分发的优先级及路径;

4、32位下,OS从内核分发至用户态时做的关键动作,64位下又有何区别;

5、嵌套的异常如何找到案发第一现场;

 

1、背景

周末闲居在家,老友发来求助信息,说是玩游戏玩的好好的,突然崩溃了,作为软件开发的他自然想探寻下crash的原因罗,通过Everything搜索了下电脑上的*.dmp,找到了本次游戏崩溃产生的dmp文件。题外话,游戏一般都会在crash时保存下dmp,并发送至后台处理,这里能找到也就不奇怪了。然后便祭出神器——Windbg开始分析,但由于各种原因诸如MiniDump,没有PDB,栈回溯失败,Windbg的很多自动化命令失效等等原因,没有分析各所以然出来,遂抛给了我,请我助其一臂之力,看看到底是啥子原因。分析之余,觉得挺有意思,撰文以分享之。

 

2、分析过程

2.1 step1:看一下异常记录,如下,为了规避哪款游戏,这里隐藏掉与游戏相关的信息,包括游戏的各个模块名,代之以GameModule,GameExe这样的名字;

0:023> .exr -1
ExceptionAddress: 013e50c1 (GameExe+0x000050c1)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 00000000
Attempt to write to address 00000000

常规的异常,往空指针里写数据了;

2.2 step2:再看下异常上下文,如下:

0:023> .ecxr
eax=000001e7 ebx=7755d418 ecx=7f1d0720 edx=00000000 esi=00000000 edi=0b369ee8
eip=013e50c1 esp=0ea8e600 ebp=0ea8e644 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010216
GameExe+0x50c1:
013e50c1 a300000000      mov     dword ptr ds:[00000000h],eax ds:002b:00000000=????????

与上边异常记录给的信息也是吻合的,但这条反汇编代码非常的奇怪,居然是硬编码好了的往0x00000000地址中写数据,这到底是在干嘛?看下附近的反汇编代码吧,看看在干什么,如下:

0:023> u 013e5090  l20
GameExe+0x5090:
013e5090 8b442404        mov     eax,dword ptr [esp+4]
013e5094 8b00            mov     eax,dword ptr [eax]
013e5096 813803000080    cmp     dword ptr [eax],80000003h
013e509c 752a            jne     GameExe+0x50c8 (013e50c8)
013e509e 68982d4e01      push    offset GameExe+0x102d98 (014e2d98)
013e50a3 6a00            push    0
013e50a5 68982d4e01      push    offset GameExe+0x102d98 (014e2d98)
013e50aa 68982d4e01      push    offset GameExe+0x102d98 (014e2d98)
013e50af 688c2e4e01      push    offset GameExe+0x102e8c (014e2e8c)
013e50b4 e8e7e00300      call    GameExe+0x431a0 (014231a0)
013e50b9 83c414          add     esp,14h
013e50bc e8cfe00300      call    GameExe+0x43190 (01423190)
013e50c1 a300000000      mov     dword ptr ds:[00000000h],eax
013e50c6 ebfe            jmp     GameExe+0x50c6 (013e50c6)
013e50c8 33c0            xor     eax,eax
013e50ca c20400          ret     4

从上边反汇编出来的代码来看,有意思的还不仅仅是这一行代码,紧接着的下一行代码也是非常奇怪,死循环,在原地打转。越来越有趣了,简单分析下这段代码的功能:

首先,这个函数没有自己独立的栈帧,[esp+4]取出的便是第一个参数,而这个函数也只有一个参数,ret 4可以说明;当然这里边没有用到ecx,edx之类的寄存器,所以就排除了寄存器传参的可能性;

接着,80000003h这个数字有着特殊的含义的,正如C0000005h代表的是 EXCEPTION_ACCESS_VIOLATION,80000003h代表的是 EXCEPTION_BREAKPOINT;

节选自WinBase.h和WinNT.h
#define STATUS_ACCESS_VIOLATION          ((DWORD)0xC0000005L)
#define STATUS_BREAKPOINT                ((DWORD)0x80000003L)  
#define EXCEPTION_ACCESS_VIOLATION       STATUS_ACCESS_VIOLATION
#define EXCEPTION_BREAKPOINT             STATUS_BREAKPOINT

这就更增加了问题的有趣性,显然这里是在判断当前异常是不是EXCEPTION_BREAKPOINT,不是的话直接返回0,什么也不干;否则的话则调用其他函数处理,然后再次触发写0x00000000地址,再次嵌套触发异常,触发自杀;我的第一反应是:游戏在做反调试的事情吗?但立马又否定了这个想法,原因很简单——如果当前进程处于调试状态,EXCEPTION_BREAKPOINT异常压根不会派遣给进程,调试器就给处理掉了。当然,我们也可以看下,进程是否被调试,如下:

0:023> !peb
PEB at 00889000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         013e0000
    Ldr                       7755fbe0

+0x068 NtGlobalFlag     : 0

这些证据都或多或少的证实着,当前的游戏进程没有被调试。[后边会专门写文章讲解异常是如何被CPU发现并转交给OS,OS又是如何确认是内核异常还是用户态异常,如果是用户态异常,OS又是如何分发给用户态进程的。]。先不着急回答这些问题,看下栈回溯,看看程序的执行路径;

2.3 step3:栈回溯,如下:

0:023> k
# ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0ea8e644 774af15a GameExe+0x50c1
01 0ea8e6d8 774c088f ntdll!RtlDispatchException+0x7c
02 0ea8e6d8 0f688051 ntdll!KiUserExceptionDispatcher+0xf
03 0ea8eb9c 0f652ee8 libcef+0x198051
04 0ea8f0e8 116521a8 libcef+0x162ee8
05 0ea8f264 116529ba libcef+0x21621a8
06 0ea8f2bc 1167c36c libcef+0x21629ba
07 0ea8f2c8 11636600 libcef+0x218c36c
08 0ea8f4e0 11650bb1 libcef+0x2146600
09 0ea8f524 11b4b196 libcef+0x2160bb1
0a 0ea8f538 0f692ab5 libcef+0x265b196
0b 0ea8f594 0f65f0ef libcef+0x1a2ab5
0c 0ea8f6d8 0f65eae3 libcef+0x16f0ef
0d 0ea8f758 0f6943b7 libcef+0x16eae3
0e 0ea8f77c 0f65ee20 libcef+0x1a43b7
0f 0ea8f7ac 0f65eddd libcef+0x16ee20
10 0ea8f7d4 0f67a94b libcef+0x16eddd
11 0ea8f7dc 0f67ad9a libcef+0x18a94b
12 0ea8f814 0f65d2e2 libcef+0x18ad9a
13 0ea8f830 76da62c4 libcef+0x16d2e2
14 0ea8f844 774b0f79 kernel32!BaseThreadInitThunk+0x24
15 0ea8f88c 774b0f44 ntdll!__RtlUserThreadStart+0x2f
16 0ea8f89c 00000000 ntdll!_RtlUserThreadStart+0x1b

由于没有符号,当然也肯定不会有罗,所以回溯出来的栈可读性差,但不要紧,做逆向分析,不也这样的嘛。但这个栈不正常,这里还没有进入SEH的分发,而是在VEH就被拦截掉了;VHE的全称是Vectored Exception Handling,向量化异常处理,这个与SEH不太一样,且只能用在Ring3,是进程级别的,不像SEH是线程级别的,异常在分发时,先遍历VEH链,处理了则不会继续往后传递,没处理则继续后遍历,分发异常;我为什么说当前处于VEH呢?很简单,因为没有看见SEH的特征处理函数,哪怕是相关联的一点点函数调用的影子都没有,为了打消你的疑虑,我们来逆向分析下ntdll!RtlDispatchException的关键部分,这个函数很大,我们就看看774af15a返回地址附近的代码。

2.4 step4:逆向分析关键API的关键部分,如下:

0:023> u 774af13f
ntdll!RtlDispatchException+0x61:
774af13f 7411            je      ntdll!RtlDispatchException+0x74 (774af152)
774af141 e89137fcff      call    ntdll!RtlGuardIsValidStackPointer (774728d7)
774af146 85c0            test    eax,eax
774af148 0f8468010000    je      ntdll!RtlDispatchException+0x1d8 (774af2b6)
774af14e 8b542410        mov     edx,dword ptr [esp+10h]
774af152 53              push    ebx
774af153 8bce            mov     ecx,esi
774af155 e8f7610000      call    ntdll!RtlpCallVectoredHandlers (774b5351)
774af15a 84c0            test    al,al
774af15c 0f851c010000    jne     ntdll!RtlDispatchException+0x1a0 (774af27e)
...省略
774af22a e8b13a0200      call    ntdll!RtlpExecuteHandlerForException (774d2ce0)

会判断RtlpCallVectoredHandlers()的返回值,如果是0的话,则调用RtlpExecuteHandlerForException(),那么0是啥意思呢?且看下边的定义,返回0意味着继续分发异常,也就是RtlpExecuteHandlerForException()中做的勒,即遍历SEH链进行异常的分发,暂且按下不表。

节选自excpt.h
#define EXCEPTION_EXECUTE_HANDLER       1
#define EXCEPTION_CONTINUE_SEARCH       0
#define EXCEPTION_CONTINUE_EXECUTION    -1

回过头来看看上边游戏做的事情,如果不是EXCEPTION_BREAKPOINT则让异常继续分发,不做任何特殊处理,如果是EXCEPTION_BREAKPOINT的话,则没有接下来的事情了。简单分析下RtlpCallVectoredHandlers()即可知道OS是如何管理VEH的了,如下:

0:023> u ntdll!RtlpCallVectoredHandlers l30
ntdll!RtlpCallVectoredHandlers:
774b5351 8bff            mov     edi,edi
774b5353 55              push    ebp
774b5354 8bec            mov     ebp,esp
774b5356 83ec30          sub     esp,30h
774b5359 a1d4425677      mov     eax,dword ptr [ntdll!__security_cookie (775642d4)]
774b535e 33c5            xor     eax,ebp
774b5360 8945fc          mov     dword ptr [ebp-4],eax
774b5363 8b4508          mov     eax,dword ptr [ebp+8]
774b5366 53              push    ebx
774b5367 56              push    esi
774b5368 8bf1            mov     esi,ecx
774b536a 6bd80c          imul    ebx,eax,0Ch
774b536d 648b0d30000000  mov     ecx,dword ptr fs:[30h]
774b5374 894df0          mov     dword ptr [ebp-10h],ecx
774b5377 8d4802          lea     ecx,[eax+2]
774b537a 33c0            xor     eax,eax
774b537c 8955ec          mov     dword ptr [ebp-14h],edx
774b537f 8b55f0          mov     edx,dword ptr [ebp-10h]
774b5382 40              inc     eax
774b5383 d3e0            shl     eax,cl
774b5385 81c318d45577    add     ebx,offset ntdll!LdrpVectorHandlerList (7755d418)
774b538b 57              push    edi
774b538c 8975e0          mov     dword ptr [ebp-20h],esi
774b538f 854228          test    dword ptr [edx+28h],eax
774b5392 8b55ec          mov     edx,dword ptr [ebp-14h]
774b5395 c645fb00        mov     byte ptr [ebp-5],0
774b5399 894dd8          mov     dword ptr [ebp-28h],ecx
774b539c 0f85cbaf0300    jne     ntdll!RtlpCallVectoredHandlers+0x3b01c (774f036d)
774b53a2 8b4dfc          mov     ecx,dword ptr [ebp-4]
774b53a5 8a45fb          mov     al,byte ptr [ebp-5]
774b53a8 33cd            xor     ecx,ebp
774b53aa 5f              pop     edi
774b53ab 5e              pop     esi
774b53ac 5b              pop     ebx
774b53ad e88eb10000      call    ntdll!__security_check_cookie (774c0540)
774b53b2 8be5            mov     esp,ebp
774b53b4 5d              pop     ebp
774b53b5 c20400          ret     4

即通过ntdll!LdrpVectorHandlerList这个链表来管理每个Handler, AddVectoredExceptionHandler、 RemoveVectoredExceptionHandler分别往这个链表里增删项。
https://docs.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling

2.5 step5:寻找案发第一现场——分析起因

到目前为止,我们看见的所有的异常上下文,包括栈回溯,都是第二案发现场了,是”mov dword ptr ds:[00000000h],eax”这条指令触发的,它并不是最直接导致这次crash的罪魁祸首,顶多算个背锅的,自杀的罪名被他坐实了。按理说,如果嵌套了一次异常,那.cxr后执行k进行回溯的话,栈上应该有两个ntdll!KiUserExceptionDispatcher才对,我们看下现实的情况是怎样的:

0:023> .cxr;k
# ChildEBP RetAddr  
00 0ea8cd98 76f41d80 ntdll!NtWaitForMultipleObjects+0xc
01 0ea8cf2c 76f41c78 kernelbase!WaitForMultipleObjectsEx+0xf0
02 0ea8cf48 71021997 kernelbase!WaitForMultipleObjects+0x18
WARNING: Stack unwind information not available. Following frames may be wrong.
03 0ea8dfdc 71021179 GameCrashdmp+0x1997
04 0ea8dfe4 774edff0 GameCrashdmp+0x1179
05 0ea8f88c 774b0f44 ntdll!__RtlUserThreadStart+0x3d0a6
06 0ea8f89c 00000000 ntdll!_RtlUserThreadStart+0x1b

what???这是啥,居然一个ntdll!KiUserExceptionDispatcher都没有,刚刚上边.ecxr之后的k看栈回溯不是还有一个ntdll!KiUserExceptionDispatcher的吗?怎么现在一个都没有了?这当然是Windbg在栈回溯时除了问题了,而且也经常会出问题,这也怪不得他,原因有很多,我们没有符号,dmp也是Minidump类型的,有的也是FPO的,它回溯起来肯定会有问题的。现在就有两个问题需要解决了,第一:上边出现的这个ntdll!KiUserExceptionDispatcher是第一案发现场还是。。。

第二:如果是第一案发现场,那第二案发现场的ntdll!KiUserExceptionDispatcher如何找出来;

我们再用下Windbg提供的其他两个很厉害的命令来找ntdll!KiUserExceptionDispatcher,看看能不能揪出来,如下:

0:023> !ddstack
Range: 0ea89000->0ea90000
0x0ea8cd90    0x664c17e5    nvwgf2um+005817e5
0x0ea8ce1c    0x76f4627c    kernelbase!CreateProcessW+0000002c
0x0ea8e004    0x774c2330    ntdll!_except_handler4_common+00000080
0x0ea8e1c8    0x01426886    GameExe+00046886
0x0ea8e244    0x7755d418    ntdll!LdrpVectorHandlerList+00000000
0x0ea8e274    0x01426886    GameExe+00046886
0x0ea8e400    0x013e50c1    GameExe+000050c1
0x0ea8e490    0x013e50c1    GameExe+000050c1
0x0ea8e5dc    0x014d5818    GameExe+000f5818
0x0ea8e5f4    0x014e2d98    GameExe+00102d98
0x0ea8e638    0x013e5090    GameExe+00005090
0x0ea8e648    0x774af15a    ntdll!RtlDispatchException+0000007c
0x0ea8e768    0x1165331a    libcef+0216331a
0x0ea8e814    0x1165331a    libcef+0216331a
0x0ea8e980    0x0f688051    libcef+00198051
0x0ea8eb04    0x77185bd9    ucrtbase!<lambda_54dcfcba6f8e0c549fa430f4d53fb7dd>::operator()+00000033
0x0ea8eb64    0x0f65390b    libcef+0016390b
0x0ea8ec54    0x5deb11c8    AudioSes+000011c8
0x0ea8f0f8    0x0f653f19    libcef+00163f19
0x0ea8f158    0x11636410    libcef+02146410
0x0ea8f2e8    0x116362c5    libcef+021462c5
0x0ea8f340    0x10cc84df    libcef+017d84df
0x0ea8f3b0    0x10cc9c91    libcef+017d9c91
0x0ea8f528    0x11b4b196    libcef+0265b196
0x0ea8f54c    0x0f3e3702    ffmpeg+00223702
0x0ea8f564    0x0f692aca    libcef+001a2aca
0x0ea8f574    0x0f3e3702    ffmpeg+00223702
0x0ea8f57c    0x1165095c    libcef+0216095c
0x0ea8f5b4    0x0f3e1bd3    ffmpeg+00221bd3
0x0ea8f724    0x0f3e3702    ffmpeg+00223702
0x0ea8f794    0x0f363c33    ffmpeg+001a3c33
0x0ea8f850    0x664c22d5    nvwgf2um+005822d5
0x0ea8f898    0x774d2ec5    ntdll!FinalExceptionHandlerPad37+00000000

0:023> !findstack ntdll!KiUserExceptionDispatc*r
Scanning thread 004

很遗憾,这些命令集体哑火,啥帮助也没有,我们要开始靠自己的双手来掘金了,使用dps来做,输出的太多了,简单整理下如下所示:

果然不出所料,找出来了。根据栈的递减原理,我们可以推断,第一案发现场的ntdll!KiUserExceptionDispatcher应该是0x0ea8e6dc这个,下一步就是还原到案发第一现场了,如下操作:

KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )

0:023> dd 0ea8e6dc
0ea8e6dc  774c088f 0ea8e6f0 0ea8e740 0ea8e6f0
0ea8e6ec  0ea8e740 80000003 00000000 00000000
0ea8e6fc  0f688051 00000001 00000000 00000000
0ea8e70c  00000000 00000000 00000000 00000000
0ea8e71c  00000000 00000000 00000000 00000000
0ea8e72c  00000000 00000000 00000000 00000000
0ea8e73c  00000000 0001007f 00000000 00000000
0ea8e74c  00000000 00000000 00000000 00000000

这里需要说明下,32位的程序,OS在从内核将异常分发至用户态时,会伪造两个参数,并且通过用户态栈传递,而对于64位的程序,则有差别,是通过寄存器传递的参数,而非通过栈,这个后边分析dmp时详解;好了,有了KiUserExceptionDispatcher的原型,又有了传递给他的两个参数,那么下一步就开始复原案发现场吧。

0:023> .exr 0ea8e6f0
ExceptionAddress: 0f688051 (libcef+0x00198051)
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000
NumberParameters: 1
   Parameter[0]: 00000000

0:023> .cxr 0ea8e740
eax=0ea8ec00 ebx=0ea8f1a8 ecx=00000000 edx=000003d1 esi=0ea8f1b4 edi=000003d1
eip=0f688051 esp=0ea8eba0 ebp=0ea8f0e8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
libcef+0x198051:
0f688051 ??              ???

看到这些数据,我悬着的心落下来了,毕竟看到这个就已然证明了之前的推测都是正确的。简单分析下,异常记录中记录下来的异常Code是80000003,以为了游戏自身触发了一个int 3的断点,这于之前那里分析的逻辑也是对的上的,异常上下文则直接恢复出了游戏执行int 3时的执行状态;可以的是,dmp时这块内存没有被保存下来,导致现在看不了反汇编,不过也不要紧了,这里的指令一定是int 3;

这里的指令看不了,但还是可以看下程序执行到这里的执行路径,看下调用栈吧,如下:

0:023> kf
#   Memory  ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00           0ea8eb9c 0f652ee8 libcef+0x198051
01       54c 0ea8f0e8 116521a8 libcef+0x162ee8
02       17c 0ea8f264 116529ba libcef+0x21621a8
03        58 0ea8f2bc 1167c36c libcef+0x21629ba
04         c 0ea8f2c8 11636600 libcef+0x218c36c
05       218 0ea8f4e0 11650bb1 libcef+0x2146600
06        44 0ea8f524 11b4b196 libcef+0x2160bb1
07        14 0ea8f538 0f692ab5 libcef+0x265b196
08        5c 0ea8f594 0f65f0ef libcef+0x1a2ab5
09       144 0ea8f6d8 0f65eae3 libcef+0x16f0ef
0a        80 0ea8f758 0f6943b7 libcef+0x16eae3
0b        24 0ea8f77c 0f65ee20 libcef+0x1a43b7
0c        30 0ea8f7ac 0f65eddd libcef+0x16ee20
0d        28 0ea8f7d4 0f67a94b libcef+0x16eddd
0e         8 0ea8f7dc 0f67ad9a libcef+0x18a94b
0f        38 0ea8f814 0f65d2e2 libcef+0x18ad9a
10        1c 0ea8f830 76da62c4 libcef+0x16d2e2
11        14 0ea8f844 774b0f79 kernel32!BaseThreadInitThunk+0x24
12        48 0ea8f88c 774b0f44 ntdll!__RtlUserThreadStart+0x2f
13        10 0ea8f89c 00000000 ntdll!_RtlUserThreadStart+0x1b

0:023> ?54c
Evaluate expression: 1356 = 0000054c

栈有些不太对劲,表现为01#栈帧太大了;用了这么多字节,先来看看这里存储的数据有没有什么特别的;找到的字符串,如下所示:

0:023> da 0ea8ecdc+8 l10000
0ea8ece4  "[0812/210050:FATAL:core_audio_ut"
0ea8ed04  "il_win.cc(292)] Check failed: de"
0ea8ed24  "vice_enumerator. .Backtrace:..ce"
0ea8ed44  "f_time_to_timet [0x0F6889A7+8270"
0ea8ed64  "31]..cef_time_to_timet [0x0F652D"
0ea8ed84  "17+606727]..TerminateProcessWith"
0ea8eda4  "outDump [0x116521A8+10745848]..T"
0ea8edc4  "erminateProcessWithoutDump [0x11"
0ea8ede4  "6529BA+10747914]..TerminateProce"
0ea8ee04  "ssWithoutDump [0x1167C36C+109183"
0ea8ee24  "32]..TerminateProcessWithoutDump"
0ea8ee44  " [0x11636600+10632272]..Terminat"
0ea8ee64  "eProcessWithoutDump [0x11650BB1+"
0ea8ee84  "10740225]..TerminateProcessWitho"
0ea8eea4  "utDump [0x11B4B196+15960038]..Ge"
0ea8eec4  "tHandleVerifier [0x0F692AB5+469]"
0ea8eee4  "..cef_time_to_timet [0x0F65F0EF+"
0ea8ef04  "656863]..cef_time_to_timet [0x0F"
0ea8ef24  "65EAE3+655315]..GetHandleVerifie"
0ea8ef44  "r [0x0F6943B7+6871]..cef_time_to"
0ea8ef64  "_timet [0x0F65EE20+656144]..cef_"
0ea8ef84  "time_to_timet [0x0F65EDDD+656077"
0ea8efa4  "]..cef_time_to_timet [0x0F67A94B"
0ea8efc4  "+769595]..cef_time_to_timet [0x0"
0ea8efe4  "F67AD9A+770698]..cef_time_to_tim"
0ea8f004  "et [0x0F65D2E2+649170]..BaseThre"
0ea8f024  "adInitThunk [0x76DA62C4+36]..Rtl"
0ea8f044  "SubscribeWnfStateChangeNotificat"
0ea8f064  "ion [0x774B0F79+1081]..RtlSubscr"
0ea8f084  "ibeWnfStateChangeNotification [0"
0ea8f0a4  "x774B0F44+1028].."

整理之后如下所示:

原理是一大推字符串占据可这块内存,但这着实不是什么好习惯;现在来解释下,为何游戏进程自己就执行了一个int 3;根据提示的字符串文本可推测:

libcef.dll中检测到一个FATAL错误,具体的错误就是跟audio相关的校验失败了,由于Check failed,且libcef还认为这是个FATAL——致命类型的错误,就直接执行int 3 触发断点了;话又说回来,这种死法确实不优雅也不高明。libcef的具体信息看下:

0:023> lmvm libcef
Browse full module list
start    end        module name
0f4f0000 129bd000   libcef   T (no symbols)           
    Loaded symbol image file: libcef.dll
    Image path: c:xxxxlibcef.dll
    Image name: libcef.dll
    Browse all global symbols  functions  data
    Timestamp:        Wed Jan 30 12:17:12 2019 (5c512548)
    CheckSum:         0348AAFD
    ImageSize:        034CD000
    File version:     2.1432.2186.0
    Product version:  2.1432.2186.0
    File flags:       0 (Mask 17)
    File OS:          4 Unknown Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4
    Information from resource tables:

没啥有用的信息,我有看了看我本地有没有这个dll,结果还真有,印象笔记里就有用他;原来是大佬google家的,失敬失敬。

2.6 step6:最后的战役——手动重构栈;

现在想看下整个程序从第一次异常触发到异常的分发过程再到程序的最后一条指令的整个栈回溯,该怎么办呢?当然是栈重构了。 重构的思路是什么?需要怎么做呢?重构做要做的核心工作便是修复受损的栈帧,那如何找到那些栈帧是受损的呢?靠猜测,当然不是瞎猜,综合现有的证据。我们回到当前线程的初始上下文环境,然后栈回溯下看看;

0:023> .cxr;kf
Resetting default scope
#   Memory  ChildEBP RetAddr  
00           0ea8cd98 76f41d80 ntdll!NtWaitForMultipleObjects+0xc
01       194 0ea8cf2c 76f41c78 kernelbase!WaitForMultipleObjectsEx+0xf0
02        1c 0ea8cf48 71021997 kernelbase!WaitForMultipleObjects+0x18
WARNING: Stack unwind information not available. Following frames may be wrong.
03      1094 0ea8dfdc 71021179 GameCrashDmp+0x1997
04         8 0ea8dfe4 774edff0 GameCrashDmp+0x1179
05      18a8 0ea8f88c 774b0f44 ntdll!__RtlUserThreadStart+0x3d0a6
06        10 0ea8f89c 00000000 ntdll!_RtlUserThreadStart+0x1b

根据上边的信息可知,03#和05#栈帧有问题,我们逐一进行查看;

先看03#的数据:

再看05#帧的数据:

0:023> dd 0ea8dfe4
0ea8dfe4  0ea8f88c 774edff0 0ea8e014 774c2a20
0ea8dff4  0ea8f88c 00000000 fffffffe 0ea8e02c
0ea8e004  774c2330 00000000 00000000 00000000
0ea8e014  0ea8e150 0ea8e1a0 77548760 00000001
0ea8e024  77548750 00000047 0ea8e04c 774c6770
0ea8e034  775642d4 774c0540 0ea8e150 0ea8f87c
0ea8e044  0ea8e1a0 0ea8e0dc 0ea8e070 774d2d42
0ea8e054  0ea8e150 0ea8f87c 0ea8e1a0 0ea8e0dc

如上所示,这个栈帧明显有问题了,我们试着这样改一下,看看效果如何:

0:023> ed 0ea8dfe4  0ea8dfe4+8

0:023> kf
#   Memory  ChildEBP RetAddr  
00           0ea8cd98 76f41d80 ntdll!NtWaitForMultipleObjects+0xc
01       194 0ea8cf2c 76f41c78 kernelbase!WaitForMultipleObjectsEx+0xf0
02        1c 0ea8cf48 71021997 kernelbase!WaitForMultipleObjects+0x18
03      1094 0ea8dfdc 71021179 GameCrashdmp+0x1997
04         8 0ea8dfe4 774edff0 GameCrashdmp+0x1179
05         8 0ea8dfec 774c2a20 ntdll!__RtlUserThreadStart+0x3d0a6
06        14 0ea8e000 774c2330 ntdll!_EH4_CallFilterFunc+0x12
07        2c 0ea8e02c 774c6770 ntdll!_except_handler4_common+0x80
08        20 0ea8e04c 774d2d42 ntdll!_except_handler4+0x20
09        24 0ea8e070 774d2d14 ntdll!ExecuteHandler2+0x26
0a        c8 0ea8e138 774c088f ntdll!ExecuteHandler+0x24
0b         0 0ea8e138 013e50c1 ntdll!KiUserExceptionDispatcher+0xf        ;第二次异常分发
0c       50c 0ea8e644 774af15a GameExe+0x50c1                             ;案发第二现场
0d        94 0ea8e6d8 774c088f ntdll!RtlDispatchException+0x7c
0e         0 0ea8e6d8 0f688051 ntdll!KiUserExceptionDispatcher+0xf        ;第一次异常分发
0f       4c4 0ea8eb9c 0f652ee8 libcef+0x198051                            ;案发第一现场
10       54c 0ea8f0e8 116521a8 libcef+0x162ee8
11       17c 0ea8f264 116529ba libcef+0x21621a8
12        58 0ea8f2bc 1167c36c libcef+0x21629ba
13         c 0ea8f2c8 11636600 libcef+0x218c36c
14       218 0ea8f4e0 11650bb1 libcef+0x2146600
15        44 0ea8f524 11b4b196 libcef+0x2160bb1
16        14 0ea8f538 0f692ab5 libcef+0x265b196
17        5c 0ea8f594 0f65f0ef libcef+0x1a2ab5
18       144 0ea8f6d8 0f65eae3 libcef+0x16f0ef
19        80 0ea8f758 0f6943b7 libcef+0x16eae3
1a        24 0ea8f77c 0f65ee20 libcef+0x1a43b7
1b        30 0ea8f7ac 0f65eddd libcef+0x16ee20
1c        28 0ea8f7d4 0f67a94b libcef+0x16eddd
1d         8 0ea8f7dc 0f67ad9a libcef+0x18a94b
1e        38 0ea8f814 0f65d2e2 libcef+0x18ad9a
1f        1c 0ea8f830 76da62c4 libcef+0x16d2e2
20        14 0ea8f844 774b0f79 kernel32!BaseThreadInitThunk+0x24
21        48 0ea8f88c 774b0f44 ntdll!__RtlUserThreadStart+0x2f
22        10 0ea8f89c 00000000 ntdll!_RtlUserThreadStart+0x1b

完美,这个栈回溯要好得多,程序的执行脉络很清晰了;

 

3、总结

本文从异常入手,通过各种分析,辨识出第一次案发现场,第二次案发现场,并逆向分析了关键的异常分发函数,简要介绍了VEH,接着根据搜索到的数据猜测除了程序为何触发int 3进行自杀的动作;最后通过手动重构,恢复出程序执行到死亡的整个执行流程;

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多