第五十八章-ExeCryptor v2.2.50.h 好,本章还剩下ExeCryptor的最后三个UnPackMe,这三个UnPackMe是本系列教程中最难脱的。我们来尝试对它们进行脱壳,如果没有脱壳成功的话,至少也会记录下脱壳思路,供大家学习。 我们双击运行UnPackMe H,可以看到它新增了如下保护: 我们可以看到入口点保护开启了。也就是说,入口点可能被隐藏起来了。 好,我们还是跟之前一样对代码段设置break-on-execute断点,运行起来,断到了这里。 貌似有点不对劲,我们看看之前脱壳过的版本。 我们可以看到入口点处的代码明显被隐藏了。 这是之前脱壳版本的截图: 这是现在UnPackMe H的截图: 对比着看,明显有差别。 我们来看看UnPackMe H此时堆栈的情况: 我们再来看看之前脱过壳的版本: 我们可以看到之前脱壳修复后的版本断在OEP处时,栈顶指针指向的地址是12FFC4(不同的机器这个地址可能会不同)。根据堆栈平衡的原理,对于大部分壳(PS:有少数壳可能会玩一些把戏,譬如说:ExeCryptor,它利用了TLS在入口点之前执行代码,所以此时的栈顶指针可能与OEP处时的栈顶指针不一致)来说,用OD加载断在入口点处时的栈顶指针应该和断在OEP处时的栈顶指针是保持一致的。 就我们当前这个例子来说,入口点处时栈顶指针ESP指向的地址是12FFC4。大家在平时脱壳的时候也要多多留意入口点处的栈顶指针指向了哪里。 成功脱壳后,OEP处的第一条指令应该是PUSH EBP。 下面我们来执行PUSH EBP这条指令。 我们可以看到EBP的值被压入到堆栈中了: 我们可以看到EBP的值被保存到12FFC0中了,这是原程序执行的第一条指令。下面我们来看看UnPackMe H,此时的栈顶指针指向的地址明显高于12FFC4,也不等于12FFC0。 我们继续观察堆栈: 正常来说,到达OEP处时,栈顶指针指向12FFC4才对,这里我们姑且算它执行了PUSH EBP,那么栈顶指针也应该指向的是12FFC0才对。 也就是说该UnPackMe模拟执行了PUSH EBP,并且中间掺杂了大量的垃圾指令。 这里我们对黄色标注出来的区域设置硬件执行断点或者BP断点。 从这里开始该UnPackMe就没有继续模拟执行指令了,跟原程序是一样的。我们来看看断在这里时的堆栈情况: 断在了这里,我们来看下堆栈: 我们可以看到此时12FFC0中已经存放了12FFF0(EBP寄存器的初始值),也就说该UnPackMe已经成功模拟执行了PUSH EBP指令,接着12FFBC中存放了FFFFFFFF,相当于模拟执行了PUSH -1指令。我们知道PUSH EBP与PUSH -1这两条指令之间应该有一条MOV EBP,ESP指令才对。 我们耐心看的话,就会发现4271CD前面的几条指令都被成功模拟执行了。 我们会发现在剥离了TLS以后,每次断在OEP处时,唯一会变化的就是EBX寄存器的值,但是无论怎么变,总是在7FFDB000~7FFDF000这个范围之内,反观其他寄存器的值都是固定不变的。 好,我们重启OD,再次断到ADD EBP,-58指令处。 004271CD 83C4 A8 ADD ESP,-58 我们将当前未脱壳和之前已脱壳的寄存器组情况进行对比: 已脱壳: 未脱壳: 我们可以看到在UnPackMe模拟执行指令的过程中连同堆栈以及除ECX,EDX以外的其他通用寄存器也都模拟了。也就是当已脱壳和未脱壳的版本处于同一个位置时,我们要留意EAX,EBX,ESP,EBP,ESI,EDI这几个寄存器的情况,ECX,EDX的话,我们并不关心。 好,现在我们回到该UnPackMe模拟执行指令的起始地址处。 我们可以看到在4271B5~4271CD之间掺杂了大量的垃圾指令-起到混淆的作用。实际上它们是要完成5条指令的功能。 我们按F7单步执行RET指令,到了这里。 首先是利用PUSHFD保存EFLAGS(标志寄存器)的值。 接下来我们对ExeCryptor壳所在区段设置内存写入断点,看看其在执行的过程中会不会保存什么值之类的东西,这里ExeCryptor有好几个区段,大家可以逐一尝试。 设置完内存写入断点以后,我们运行起来,看看会发生什么。 断在了这里。 这里我们可以看到将EAX的值保存到47AD0C这个内存单元中,但是此时EAX的值并不是某个寄存器的初始值,之前并没有见过这个值。虽然没弄明白这条指令实际的作用,但我们还是将什么数值被保存到哪里简单的记录一下吧,方便下面的分析。 EAX = 122601B0 保存到 47AD0C中 EAX = 5A731601 保存到 4815E0 中 EBX = 5A731601 保存到 4815E0 中 EAX = 0046C5DE 保存到 47A90C中 00496CBB 01 变成了 00 00496C9C 01 变成了 02 00496CAB 00 变成了 01 我们要时刻留意堆栈的情况:看看有没有出现12FFF0这个值,到目前为止还没有出现这个值,我们继续记录。 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0 这里是利用POP指令将12FFF0保存到47A0CC中 EDI = 7C920738 保存到47A4CC中。 这里 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI 这是保存EDI寄存器的值,我们要稍微留意一下,它是要模拟的寄存器之一。 ESI=0046C5DE 保存到 0047A8CC中 EDX=0047F3EF 保存到 0047BCF8中 ECX=0047F3EF 保存到 0047B8EC中 EAX=0047E97E 保存到 0047B4E4中 EBX=7FFDB000 保存到 00481198中 这里 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX EAX = 0012FFC0 保存到 0047B0D8中 这里我们可以看到是12FFC0,我们继续。 EAX=14F43E15 保存到 0047ACCC中 00496CAB 01 变成了 00 00496C9C 02 变成了 03 00496D9A 00 变成了 01 跟之前一样POP 利用POP指令将12FFF0保存到47A488中 EDI = 7C920738 保存到 0047A888中 这里我们应该欣喜才对,因为出现了12FFF0,因为要模拟执行PUSH EBP的话,EBP的值应该为12FFF0才对,但是12FFC0这个值还没有出现。 ESI = FFFFFFFF 保存到 0047AC88中 这里 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI EDX=0047F3EF 保存到 0047C0B4中 ECX=0047F3EF 保存到 0047BCA8中 EAX=0047E97E 保存到 0047B8A0 中 EBX=7FFDB000 保存到 00481554中 这里跟之前某条指令是一样的,保存EBX的值 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX 虽然我们已经执行了大量的垃圾指令,但是还是没有遇到第一条该程序真正要执行的指令,我们只能耐心的继续往下跟。 EAX = 0012FFC4 保存到 0047B494中 这里 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX 0012FFC4是ESP的初始值,我们要重点关注。 我们继续。 EAX=1E500000 保存到 0047B088中 0047B494 C4 FF 12 00 这里我们可以看到12FFC4-ESP寄存器的初始值,现在要将其减去4。 0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4 减去4以后 0047B494 C0 FF 12 00 现在变成了12FFC0。 00496D9A 01 变成了 00 00496C9C 03 变成了 04 00496D8A 00 变成了 01 又是POP指令 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0 再次将12FFF0保存到0047A448中 大家应该还记得吧-还没有执行该程序真正要执行的指令呢,嘿嘿 EDI=7C920738 保存到 0047A848中 再次 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738 ESI=FFFFFFFF 保存到 0047AC48中 这里又是跟之前一样保存ESI的值 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI 继续耐心往下跟踪 EDX=0047F3EF 保存到 0047C074中 ECX=0047F3EF 保存到 0047BC68中 EAX=0047E97E 保存到 0047B860中 EBX = 00478304 保存到 00481514中 这里 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX; UnPackMe.0047830 EAX=0012FFBC 保存到 0047B454中 这里我们可以看到是12FFBC-第二个值要被压入到这个地址中 EAX=192082C0 保存到 0047B048中 我们明显的看出这里实际上是一个循环,继续。 EAX=0048F082 保存到 0048191C中 EBX=0048F082 保存到 0048191C中 00496D8A 01 变成了 00 00496C9C 04 变成了 05 00496CFB 00 变成了 01 又是POP指令 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0 12FFF0保存到0047A20C中 EDI=7C920738 保存到 0047A60C中 这里又是跟之前一样保存EDI的值 ESI = FFFFFFFF 保存到 0047AA0C中 哎呀妈呀,仍然没有看到第一条真正要执行的指令。 EDX=0047F3EF 保存到 0047BE38中 ECX=0047F3EF 保存到 0047BA2C中 EAX=0047E97E 保存到 0047B624中 诶,我们看到了一点曙光 EBX=7FFDB000 保存到 004812D8中 再次到了这里 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX EAX=0012FFC0 保存到 0047B218中 大家留心点的话会发现这是最后一次循环,之前EAX的值为12FFC4,现在为12FFC0。 EAX=1B700602 保存到 0047AE0C中 EAX=FFFFFFFF 保存到 0047B624 7E E9 47 00中 这个FFFFFFFF也是值得我们关注的,它是要被压入到堆栈中的一个值。 00496CFB 01 变成了 00 00472B9C 00 变成了 01 00472B9C 01 变成了 00 00496C9C 05 变成了 06 00496CEB 00 变成了 01 又是POP指令 12FFF0保存到0047A1CC中 然后 EDI=7C920738 保存到 0047A5CC中 ESI=FFFFFFFF 保存到 0047A9CC中 EDX=00172CF0 保存到 0047BDF8中 ECX=00000012 保存到 0047B9EC中 EAX=00472B00 保存到 0047B5E4中 还是没有看到真正要执行的第一条指令,我的天。 EBX = 7FFDB000 保存到 00481298中 EAX = 0012FFC4 保存到 0047B1D8中 EAX = 18000500 保存到 0047ADCC中 我们对12FFC0设置一个硬件写入断点,看看哪里会调用PUSH EBP向12FFC0进行写入。 004923C8 871C24 XCHG DWORD PTR SS: [ESP], EBX 我们可以看到这一行,从47A1CC中读取12FFF0的值。 如果往前翻的话,会发现是通过POP指令将12FFF0保存到0047A1CC中的。 12FFF0这个值是我们重点关注的,它是EBP的初始值,这里又通过XCHG DWORD PTR SS:[EBP],EBX模拟PUSH EBP执行的结果。 接下来,我们会逐步发现真正要执行的指令,嘿嘿 下一条真正要执行的指令是: 004271B1 8BEC MOV EBP, ESP 此时ESP的值为12FFC0,因此执行了MOV EBP,ESP以后,EBP的值也会变成12FFC0。 这个12FFC0是通过减4得来的。 0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4 0047B1D8 C4 FF 12 00 我们可以看到47B1D8中的值变成了12FFC0,不出意外的话,应该是要保存到EBP中的,我猜测接下来可能要执行MOV EBP,ESP。 0047B1D8 C0 FF 12 00 下面我们对12FFBC设置硬件写入断点,来看看哪里执行了PUSH -1将FFFFFFFF压入到12FFBC中。 这里是脱壳修复后的情形。 好,我们继续,看看真正要执行的第二条指令在哪里。 00496CEB 01 变成了 00 00496C9C 06 变成了 07 00496CDB 00 变成了 01 又是POP指令 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFF0 将12FFF0保存到0047A18C中 我们继续 EDI=7C920738 保存到 0047A58C中 ESI=FFFFFFFF 保存到 0047A98C中 EDX=00172CF0 保存到 0047BDB8中 ECX=00000012 保存到 0047B9AC中 EAX=00472B00 保存到 0047B5A4中 继续 EBX=7FFDB000 保存到 00481258中 EAX=12FFc0 保存到 0047B198中 循环再次开始 EAX=1590F683 保存到 0047AD8C中 EAX=004820F6 保存到 00481660中 EBX=004820F6 保存到 00481660中 我们跟之前一样对堆栈设置硬件写入断点,到了这里 00478520 68 7F354700 PUSH 47357F 明显是垃圾指令,我们不理会它。 00496CDB 01 变成了 00 跳过位操作指令 00495F2D 6A FF PUSH-1 嘿嘿,这里是将FFFFFFFF保存到12FFBC中,这是真正要执行的第二条指令。 好,按照这个指令执行的顺序,接下来将是450E60保存到12FFB8中,4292C8保存到12FFB4中,所以我们接着对12FFB8设置硬件写入断点。 00496C9C 07 变成了 08 00496CCB 00 变成了 01 又是POP指令 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0 这里我每次POP都用蓝色标注出来,方便大家观察每次循环的周期。 EDI=7C920738 保存到 0047A54C中 这里又是保存EDI的值。 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738 每次循环都会保存EDI的值,现在也会保存ESI的值。 ESI=FFFFFFFF 保存到 0047A94C中 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI 这里面有是大量的跳转,入栈操作,还有混淆。 其实这些步骤我们完全可能编写脚本来完成,记录每次更新寄存器的指令,有助于我们理解程序的意图。 这里又是将ESI的值保存到空闲区域,存放的从上图来看非常的对称。 EDX=00172CF0 保存到 0047BD78中 ECX=00000012 保存到 0047B96C中 这里又是将寄存器的值保存到空白区域,这里保存的是12。 我晕,已经写了这么多页了。 EAX=00472B00 保存到 0047B564中 EBX=7FFDB000 保存到 00481218中 这次是保存EBX的值。 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX 排列的依然很整齐 EAX=12FFBC 保存到 0047B158中 这里 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX 这些指令执行过程中,ESP的值一直没有变过,我们来看看它什么时候会变化。 这里我们可以看到当前的值12FFBC以及之前的值12FFC0。 我们继续,又是一轮循环。 EAX=1EF00200 保存到 0047AD4C中 现在我们又看到了SUB指令了,减去4,接下来应该是保存ESP的值,然后PUSH,嘿嘿。 0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4 这里减去4。 变成了 这里ESP的值更新了,那么接下来肯定是模拟PUSH指令向堆栈中压入相应的值。 00496CCB 01 变成了 00 00496C9C 08 变成了 09 00496D3B 00 变成了 01 又是POP指令 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0 12FFc0 保存到 0047A30C中 我们继续 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; UnPackMe.0049B8C7 EDI=0049B8C7 保存到 0047A70C中 继续 ESI=FFFFFFFF 保存到 0047AB0C中 EDX=00172CF0 保存到 0047BF38中 ECX=00000012 变成了00000012 EAX=00472B00 变成了 0047B724 保存EBX的值 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX EBX=7FFDB000 保存到 004813D8中 这里我们可以看到ESP的值。 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX EAX=12ffb4 保存到 0047B318中 继续 EAX=12A43F15 保存到 0047AF0C中 貌似又一轮循环开始了 00496D3B 01 变成了 00 00496C9C 09 变成了 0a 00496D2B 00 变成了 01 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0 又是POP指令 0047A2CC C0 FF 12 00 继续变成了12FFC0 跟之前一样保存EDI的值。 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI; ntdll.7C920738 EDI=7C920738 保存到 0047A6CC 中 ESI=FFFFFFFF 保存到 0047AACC中 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI EDX=00172CF0 保存到 0047BEF8中 ECX=0047BEF8 保存到 0047BAEC中 这里有可能连EDX,ECX也模拟了。 00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX 0049125А 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX 其实这两个寄存器的值无关紧要,既然我们发现了,还是来看看吧。 EAX=00472B00 保存到 0047B6E4中 保存EBX的值 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX EBX=7FFDB000 保存到 00481398中 保存ESP的值 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX EAX=0012FFB8 保存到 0047B2D8中 又一轮循环 EAX=09AFCDC0 保存到 0047AECC中 EAX=00493FCD 保存到 004817A0中 EBX=00493FCD 保存 004817A0中 004965A4 8BB5 C0A84700 MOV ESI, DWORD PTR SS: [EBP+47A8C0] 这里向ESI中写入内容 0047AACC FF FF FF FF 00496D2B 01 变成了 00 00496C9C 0a 变成了 0b 00496D1B 00 变成了 01 再次利用POP指令更新EBP的值 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0]; 0012FFC0 这里将EDI,ESI恢复为正确的值。 00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX 0049125A 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX 00491260 8985 D8B44700 MOV DWORD PTR SS: [EBP+47B4D8], EAX 更新EDX,ECX,EAX 接着更新EBX = 7FFDE000 00498D03 899D 8C114800 MOV DWORD PTR SS: [EBP+48118C], EBX ESP 变成了 0012FFB8 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX ESP减去4 0047711C 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4 0047B418 B4 FF 12 00 接下来将是PUSH 450E60 00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX 0049125A 898D E0B84700 MOV DWORD PTR SS: [EBP+47B8E0], ECX 00491260 8985 D8B44700 MOV DWORD PTR SS: [EBP+47B4D8], EAX 保存到了这里 0048D299 870C24 XCHG DWORD PTR SS: [ESP], ECX 通过XCHG指令实现了PUSH 450E60的效果,在大量的垃圾中定位到它真心困难。 我们继续,关注重点的地方,其他的略过就行了。 接下来的原始指令应该是PUSH 4292C8,我们继续: 我们可以看到ESP的值保存在EAX中了。 从上图中我们可以看到ESP的值在变化,但是变化的很小。之前是12FFB8,现在变为了12FFAC。 这里ESP将加4。 然后会再减去4。 00471718 83AD CCB04700 0> SUB DWORD PTR SS: [EBP+47B0CC], 4 这里保存的ESI的值不正确,然后是EDI,接着又会恢复。 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI ESI=CB4A9B05 好,这里 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX EAX实际上就是ESP的值为12FFB0。 ESI的值继续变化,而且都是不正确的值,很显然是被混淆过的。 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI ESI=F2AFFFFC 这里我们可以看到不仅仅使用XCHG指令模拟PUSH了,下面还夹杂着原始指令 004820D9 873424 XCHG DWORD PTR SS: [ESP], ESI 004820DC 64:A1 00000000 MOV EAX, DWORD PTR FS: [0] 我们继续跟踪,会发现来到了代码段。 我们继续 我们可以看到已经到了OEP附近了。 下面我们来尝试设置条件断点,观察各个寄存器的值是如何保存的。 00470F82 89B5 C0A84700 MOV DWORD PTR SS: [EBP+47A8C0], ESI 这里保存的是ESI的值 接着的条件断点。 接下来是看看哪里保存EDI的值。 0047949B 89BD C0A44700 MOV DWORD PTR SS: [EBP+47A4C0], EDI 照例 接着是EBP 0048E2C2 8F85 C0A04700 POP DWORD PTR SS: [EBP+47A0C0] 接下来是ESP 00498D0B 8985 CCB04700 MOV DWORD PTR SS: [EBP+47B0CC], EAX 继续调整条件断点 0049125A这里更新ECX。 这里虽然我们不关心EDX的值,还是给它设置条件断点。 00491254 8995 ECBC4700 MOV DWORD PTR SS: [EBP+47BCEC], EDX 接着EBX 00498D03 899D 8C114800 MOV DWORD PTR SS:[EBP+48118C],EBX 我们可以看到 这里我们可以看到EBP,ECX,EDX,EBX,EDI这些值是相同的,ESP,EAX,ESI的值有些差别。 好,那么我们将条件断点的条件换一换,换成ESI==FFFFFFFF试试看。 到了这里 此时ESI的值为FFFFFFFF。 将针对ESI的断点禁用掉。 我们继续跟踪到了47CE1E这里。 恢复针对ESI的断点,条件依然设置为ESI == FFFFFFFF。 这里已经获取到了ESI正确的值。 好了,本章结束。 这个系列也结束了。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人)
感谢热心翻译的朋友: 1~3章译者:BGCoder 4~58章译者:安于此生
全集配套程序下载地址:
|