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 8 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;

节选自WinBa和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链进行异常的分发,暂且按下不表。

节选自exc #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分别往这个链表里增删项。

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进行自杀的动作;最后通过手动重构,恢复出程序执行到死亡的整个执行流程;

本文由void *原创发布
转载,请参考转载声明,注明出处:

我是安仔,一名刚入职网络安全圈的网安萌新,欢迎关注我,跟我一起成长; 欢迎大家私信回复【入群】,加入安界网大咖交流群,跟我一起交流讨论。

小白入行网络安全、混迹安全行业找大咖,以及更多成长干货资料,欢迎关注#安界网人才培养计划#、#网络安全在我身边#、@安界人才培养计划

1.《0x000050,干货看这篇!基于异常的狩猎行为——自我保护触发自杀》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《0x000050,干货看这篇!基于异常的狩猎行为——自我保护触发自杀》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/keji/1956742.html