使用OllyDbg从零开始Cracking 第五十五章-EXECryptor v2.2.50.a脱壳-Part2
第五十五章-EXECryptor v2.2.50.a脱壳-Part2本章我们将编写一个脚本来修复ExeCryptor的IAT。可能很多童鞋一听到脚本这个词就有一种头皮发麻的感觉。(嘿嘿),因为一般来说人家编写好的脚本通常都比较长,看起来非常复杂的样子,当然会感觉到头皮发麻撒。但是我这里换一种方法,我不是直接拿一个现成的脚本给大家,而是带着大家从零开始编写这个脚本,逐步添砖加瓦,这样大家接受起来就容易的多。我的想法很简单,就是遍历IAT,看哪一个IAT项被重定向了,如果被重定向了的话,就修复之,我们首先来构建这个脚本的基本框架。首先我们要定义一个变量,用它来作为IAT项的指针。var table首先我们将IAT的起始地址保存到该table变量中mov table,460818这里我们就将table这个变量初始化完毕了,接下来我们需要遍历整个IAT,判断其中哪些项被重定向了,如果被重定向了,就修复之。如果没有被重定向,就继续遍历下一项。脚本的基本框架就是这样的:start:cmp ,50000000ja ToSkip log tableend ToSkip:add table,4jmp start这是一个基本框架-遍历IAT中的每一项,依次判断IAT项中的值是否大于50000000(一般来说大于50000000的话,就属于是DLL中的地址,即为正常的API函数地址,没有被重定向)如果大于50000000的话,就直接跳过该项,继续遍历下一项。如果是小于等于50000000的话,说明是被重定向过的,则进行下面的操作:log tableend这里的话我们就需要对重定向的项进行修复了,要对其修复的话,首先我们要知道其实际要调用的API函数是什么,这里为了简单起见,我们只测试第一个待修复的项,所以在定位到实际要调用的API函数以后,直接退出循环。我们首先断到OEP处(如何断在OEP处,上一章已经给大家介绍过了),然后执行该脚本看看效果。我们可以看到提示第7行有一个不识别的end命令,呵呵,写错了,应该是ret命令才对,我们将它改过来。----------------------------------------------------------------------------------------------------------------------------------------------------------------------var table mov table,460818start: cmp ,50000000 ja ToSkip log table retToSkip: add table,4 jmp start这里我们已经改过来了,再次执行该脚本看看效果。我们可以看到这次没有报错,并且第一个重定向的IAT项的地址成功被记录到日志中了。现在我们需要获取重定向的IAT项的值了,所以这里我们再定义content变量用于临时保存重定向的IAT项的值。----------------------------------------------------------------------------------------------------------------------------------------------------------------------var tablevar contentmov table,460818start: cmp ,50000000 ja ToSkip: log table mov content, log content retToSkip: add table,4 jmp start这里添加了content这个变量以后,我们就可以把重定向过的IAT项的地址以及值都记录到日志中了。我们可以看到执行完该脚本以后,日志中记录了460818这个IAT项中的值为47FCA8,接下来我们需要将ret命令去掉,循环遍历所有项。不知道大家知不知道其实ODbgScript这个插件是可以对脚本进行单步跟踪的,以便脚本出错的时候方便我们调试。我们来看一看这个功能如何使用:我们选中菜单项Plugins-ODbgScript-Script Window,打开脚本跟踪窗口。这里我们可以看到单步跟踪的快捷键为S,给指定行设置断点的快捷键为F2,有了这两个命令就足够我们对脚本进行跟踪排错了。下面我们按S键单步跟踪看看效果如何。这里我们可以看到EIP这一列会显示当前行EIP的值为多少,Values <---这一列的话,如果当前行有取值操作的话,就会以 值《内存地址 的方式显示出来,例如:这里显示的是47FCAB << 460818。我们也可以通过给指定行设置断点,然后单击Resume菜单项来执行多条命令,然后停在断点处。好了,现在我们关闭这个脚本窗口,继续给我们的脚本添加新的内容。现在我们需要给它添加一个条件,当满足该条件时就停止执行该脚本并退出,这里该条件应该是遍历完整个IAT,即超出了IAT的范围,table指针大于460F28时就退出。----------------------------------------------------------------------------------------------------------------------------------------------------------------------var tablevar content mov table,460818start: cmp table,460F28 ja final cmp ,50000000 ja ToSkip log table mov content, log content retToSkip: add table,4 jmp startfinal: ret这里判断的条件我们就添加好了,当遍历到IAT尾部的时候,我们就跳转到final标签处,结束脚本的执行。下面我们就可以来修复重定向的IAT项了,前面我们在定位到第一个重定向的IAT项时,是将其地址和值都记录到日志中,然后ret退出脚本,这里我们将这个ret命令去掉。var tablevar content mov table,460818start: cmp table,460F28 ja final cmp ,50000000 ja ToSkip log table mov content, log contentToSkip: add table,4 jmp startfinal: ret这里我们就将log content后面ret命令去掉了,这样处理之后,在记录完重定向IAT项的地址以及值以后,会到达ToSkip标签处继续遍历后面的IAT项,重复上面的过程,直到遍历完整个IAT为止,我们来看看该脚本执行的效果。我们来看看日志信息。这里我们会发现有一些IAT项的值为零,我们知道,这些零是分隔符,并没有实质性的作用,所以我们还需要添加相应的命令跳过这些分隔符。 var tablevar content mov table,460818start: cmp table,460F28 ja final cmp ,50000000 ja ToSkip mov content, cmp content,0 je ToSkip log content log tableToSkip: add table,4 jmp startfinal: ret这里我们添加了两条命令跳过值为零的IAT项,我们再次执行该脚本看看效果。我们可以看到所有的待修复的IAT项的地址以及内容都被打印出来了,下面我们来尝试修复这些项。接下来我们要做的就是定位到重定向的IAT项实际要调用的API,我们可以利用内存写入断点来定位。当断下来的时候我们将API函数的地址填充到对应的IAT项中,接下来我们不让其去调用实际要调用的API函数,而是让其继续遍历IAT中的下一项。脚本中设置内存写入断点用到的是BPWM这个命令,具体用法如下:BPWM 地址,大小在指定地址处,设置一个内存写入断点。”大小”是指内存中的字节大小。例子:BPWM 401000,FF通过这个命令我们就可以对指定内存单元设置内存写入断点了。mov eip,contentbpwm table,4这里我们首先将EIP指向重定向后函数的入口点,接着对待修复的IAT项设置内存写入断点。接下来通过COB命令继续往下执行。COB命令的具体用法如下:COB---发生中断后,让脚本继续执行(移除EOB指令)例子:COB mov eip,contentbpwm table,4cob ToRepairrunToRepair:ret这段脚本的意思就是说对待修复的IAT项设置内存写入断点,接着运行起来,当发生中断的时候就跳转ToRepair标签处。这里ToRepair标签处,我只添加了一个ret命令,只是为了测试是否能成功断在第一个待修复的IAT项被写入的地方。我们可以看到执行完该脚本以后,正好断在了向第一个待修复的IAT项写入API函数地址的指令处,下面我们可以通过STI命令单步执行这一行,看看第一个IAT项被修复后的效果。STL命令的用法如下:STI---相当于在OD中按F7,单步步入例子:sti----------------------------------------------------------------------------------------------------------------------------------------------------------------------mov eip,contentbpwm table,4cob ToRepairrunToRepairstiret好,我们执行该修改后的脚本,执行完毕以后,可以看到第一个重定向的IAT项的值成功被修复了。我们可以看到第一个待修复的IAT项已经被修复了,下面我们要做的就是继续定位下一个待修复的IAT项,并且确保不执行当前IAT项实际要调用的API函数,以免出错。这里我们可以看到在脚本执行的过程中,产生了异常,出错了。我们在编写脚本的过程中应该充分考虑到这一点-哪些地方会产生异常,我们需要检查当前是否在处于我们预想的位置,这里我们不修改任何东西,直接用ODbgScript插件调试看看哪里出错了。我们在第19行处按F2键设置一个断点,执行这个sti命令以后,第一个待修复的IAT项就会被修复,我们开始执行该脚本看看效果。我们单击鼠标右键选择Resume,恢复脚本的执行,就可以看到断在sti这条命令处了。虽然在调试脚本过程中我们可以精确看到EIP的值,但是在脚本中我们还是要对其进行检查。我们按S键执行该sti命令的话,第一个待修复的IAT项就会被修复。按道理来说,接下来将继续修复第二个待修复的IAT项,我们按S键继续跟踪,看看会发生什么。这里我们可以看到table这个变量(该变量实际上是IAT的指针)已经指向IAT中的下一项了,我们继续跟踪。接下来是修复第二个待修复的IAT项。我们跟踪到了这里,这里将对第二个待修复的IAT项设置内存写入断点,我们继续跟踪,我们可以看到当我们执行run命令的时候出错了。我们来看一看是哪里导致的错误。这里我们再次跟踪到了第17行的run命令处,我们单击鼠标右键选择Abort菜单项,终止脚本的执行,下面我们在OD中继续往下跟踪,看看是哪里导致的错误。这里我们处于第二个重定向IAT项所指向函数的入口处,我们继续跟踪。这里是将栈顶指针指向的内容与EAX的内容进行交换。这里是将480094这个内存单元的内容保存到EAX中,此时480094这个内存单元中内容为零,也就是说这条指令其实就是将EAX置零。这里好像看不出什么端倪来。既然是脚本在修复第二个IAT项的时候抛出的异常,那我们就来看看第二个IAT的项的调用处吧。这里我们通过单击鼠标右键选择New origin here将EIP指向该CALL处,接着手动给该IAT项设置一个内存写入断点,然后给返回地址处设置一个BP断点,执行以后我们会发现第二个IAT项的值并没有被修复,这也就是为什么会抛出异常的原因了。也就说我们的脚本逻辑上存在问题,第一个IAT项触发内存写入断点时断在了写入正确的API函数地址的指令处,但是第二项却没有。好,这里我想到一个解决办法,虽然不是很正规,但是确实能解决这个问题。我们对第一个待修复的IAT项即460818这一项设置内存写入断点,接着从第一个重定向后的函数入口处即47FCA8处开始跟踪(利用OD的自动跟踪功能跟踪),OD自动跟踪大约要花5分钟左右的时间。部分跟踪指令序列如下:00483C91 Main MOV AL,1 ; EAX=77DA6C0100483C93 Main JMP 00483C7800483C78 Main MOV BYTE PTR SS:,AL00483C7B Main MOV AL,BYTE PTR SS:00483C7E Main POP ECX ; ECX=01000001, ESP=0012FE6C00483C7F Main POP ECX ; ECX=77DA6C75, ESP=0012FE7000483C80 Main POP EBP ; ESP=0012FE74, EBP=0012FE8000483C81 Main RETN ; ESP=0012FE78004833D5 Main TEST AL,AL ; FL=0004833D7 Main JNZ 0047CC500047CC50 Main POP ECX ; ECX=00000001, ESP=0012FE7C0047CC51 Main POP ECX ; ECX=77DA6C75, ESP=0012FE800047CC52 Main POP EBP ; ESP=0012FE84, EBP=0012FFB00047CC53 Main RETN ; ESP=0012FE880047691C Main MOV EAX,DWORD PTR SS: ; EAX=77DA6BF00047691F Main MOV ESP,EBP ; ESP=0012FFB000476921 Main JMP 004765DC004765DC Main JMP 0047F15C0047F15C Main PUSH 47C9B5 ; ESP=0012FFAC0047F161 Main JMP 00491A5F00491A5F Main JMP 004737D4004737D4 Main RETN ; ESP=0012FFB00047C9B5 Main POP EBP ; ESP=0012FFB4, EBP=0012FFF00047C9B6 Main RETN ; ESP=0012FFB80046E81D Main RETN ; ESP=0012FFBCMemory breakpoint when writing to 以上是触发460818处的内存写入断点之前执行的部分指令,我们需要在上面这些指令中找一条所有待修复的IAT项都会执行的指令。0047691C Main MOV EAX,DWORD PTR SS: ; EAX=77DA6BF0就选着47691C这条指令吧,当将要执行这个指令的时候,此时EAX中正好保存正确的API函数地址,我们对这条指令设置一个硬件执行断点,看看遍历到第二个IAT项时,断在这里是什么情况。这里我们可以看到遍历到第二个IAT项时,EAX中保存的的确是正确的API函数地址,我们继续跟踪。我们继续往下跟踪,会发现从46E81D地址处开始各个IAT项的执行流程就不同了。0046E81D C3 RETN第一个IAT项将跳转到修复IAT项的指令处,而第二个IAT项将跳到这里。好了,不管怎么说,我们这里还是可以利用所有IAT项都会执行的这一条指令-即47691C这条指令。这里正确的IAT函数地址将被保存到EAX中,所以下面我们来修改脚本,让其断在47691C这条指令处,执行完这条指令后,EAX就保存了正确的API函数地址,接着我们将其填充到对应的IAT项中。----------------------------------------------------------------------------------------------------------------------------------------------------------------------var tablevar contentmov table,460818start:cmp table,460F28ja finalcmp ,50000000ja ToSkipmov content,cmp content,0je ToSkiplog contentlog tablemov eip,contentbphws 47691F,”x”cob ToRepairToRepair:log eaxmov ,eaxToSkip: add table,4 jmp startfinal: ret这里我们给47691F这一行设置一个硬件执行断点,此时EAX中已经保存了正确的API函数地址了,接着我们将其填充到对应的IAT项中,循环往复直到遍历完整个IAT为止,我们来执行该脚本看看效果。这里我们可以看到修复了一会儿后,就报错了。确切的说是在尝试修复460988这一项的时候发生异常了。看来我们的脚本逻辑上还是有问题,我们重启OD。再次定位到OEP处,接着定位到460988这一个IAT项,看看它有没有参考引用处。我们跟到这个函数里面看看。我们在47691F处设置一个硬件执行断点看看,运行起来,看看会发生什么。这里我们可以看到此时EAX中保存的依然是正确的API函数地址,那么刚刚我们脚本修复IAT的过程中报错有可能是因为该壳会检测是不是在同一个时间段内IAT中的多项同时被修复,如果是的话就报错。好,那么现在我们将table指针初始化为460988,然后执行脚本,看看还会不会报错。var tablevar contentmov table,460988start:cmp table,460F28ja finalcmp ,50000000ja ToSkipmov content,cmp content,0je ToSkiplog contentlog tablemov eip,contentbphws 47691F,”x”cob ToRepairToRepair:
log eaxmov ,eaxToSkip: add table,4 jmp startfinal: ret我们可以清楚的看到发生了什么。我们可以看到大约修复了5到6个IAT项后又报错了,嘿嘿,说明该壳会检测修复5到6个左右IAT项所用的时间,如果修复IAT项的时间过于连续的话,就会抛出异常。导致我们无法继续修复,下面大家来看看我的解决方案,嘿嘿。我的做法是监视ZwTerminateProcess(调用了该函数程序就直接退出了)这个函数,我们要做的就是当将要执行该函数的时候,我们让其继续执行脚本而不是直接退出程序。ToRepair:cmp eip,7C91E88Eje ToSkiplog eaxmov ,eaxrun我的机器上ZwTerminateProcess这个API函数的地址为7C91E88E(不同的机器上这个地址可能会不同,大家根据自己机器的实际情况来决定)。当脚本执行的过程中触发了异常的话,我们判断该程序是不是在尝试调用ZwTerminateProcess这个函数来结束进程,如果是的话,我们就直接跳到ToSkip标签处去遍历下一个IAT项。还有一种情况我们需要考虑就是:在修复了一个IAT项之后,继续执行后面的代码过程中发生了异常怎么办?即执行完了47691C这条指令之后,接着在执行后面的代码的过程中发生了异常,进而转入ZwTerminateProcess。按照现在这个逻辑就是跳转到ZwTerminateProcess处,好,跳转ZwTerminateProcess处这个操作被我们的脚本捕获到了,我们脚本的处理是跳到下一个IAT项处,但是我们当前这个IAT项并没有修复啊,所以说这个逻辑还是有问题的。我们可以这样做,就是我们人为的在47691F地址处造一个异常,让其去调用ZwTerminateProcess这个函数。经修改后,我们的脚本就变成了这个样子:start:cmp table,460F28ja finalcmp ,50000000ja ToSkipmov content,cmp content,0je ToSkiplog contentlog tablemov eip,contentbphws 47691F,”x”mov ,0mov ,0cob ToRepairrunToRepair:cmp eip,7C91E88Eje ToSkiplog eaxmov ,eaxrunToSkip:add table,4jmp startfinal:ret这里我们把忽略内存访问异常这个选项的对勾去掉,执行脚本。 我们可以看到IAT项都被修复了,下面我来进行dump。打开IMP REC。现在我们来修复dump文件,然后将TLS Table的指针和大小都设置为零。现在入口点就是4271B0了。我们运行修复后的dump文件,可以看到完美运行。好了本章到此结束。(PS:这里执行了最终的这个脚本,我并没有修复成功,触发了异常以后,我的OD并没有进入ZwTerminateProcess的流程,具体原因有待进一步分析,我个人感觉这个方法不是很通用,如果大家也搞不定的话,可以参考我之前发过的那套国外的脱壳教程全集,里面有ExeCryptor的正规脱壳方案)
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人) 原作者个人主页:http://www.ricardonarvaja.info/
感谢热心翻译的朋友:1~3章译者:BGCoder4~58章译者:安于此生
全集配套程序下载地址:链接: http://pan.baidu.com/s/1eQzTWfo 密码: vytv
恒大瞬间转了一大堆。。
[快捷回复]-感谢楼主热心分享! 欢迎常来帮助新人,谢谢~ 真是不错的东东,谢谢 大神厉害啦 发教程的都是最伟大的,感谢!!! 感谢分享。正在试用。 谢谢整理,上传分享!
页:
[1]