学习脱壳也有一年多时间,强壳基本上没脱过几个,基本上是屡战屡败,屡败屡战。这个securom是一个叫做白发魔女的游戏,原日文版似乎是96年的,这个简体版是04年发布。游戏很早就到手,初学脱壳时,拿它完全没有办法,不管怎么调试都会跑飞。最近又试着拿它研究了一番,虽然没有脱成功,不过也小有收获。

此版本securom的伎俩:
1、除了通用的反调试手段外,还会用EnumWindows或者FindWindow检测OD,检测到后运行失败。
2、运行程序时,会调用CreateProcessInternalA自创建进程,并且生成一个调用参数,此参数依赖于不同的机器,并且任何时刻运行都不同,该参数有效期有时限,不过时限比较长,大概一天内没问题。
3、用随机数作VirtualAlloc的指定地址,将生成的代码分配到零散的空间去。
4、程序频繁异常,需要把00000000-FFFFFFFF异常全部忽略,并且通过这些异常,把DRx寄存器作通用寄存器使,也就是说硬件断点不管用,也不能用PlantOm插件保护DRx,错误的DRx值无法使程序正常运行起来。
5、对一些API(GetVersionExA等)或者自身函数的入口作检测,如果发现是int3则运行出错,也就是说加断点要注意。
6、使用cpuid,GetSystemInfo,GetCurrentThreadId,GetCurrentProcessId对代码加密,前两者对于本机脱壳暂时没有影响,后两者比较棘手。
7、使用GetTickCount,GetSystemTimeAsFileTime,QueryPerformanceCounter对调用时间进行判断,时间间隔太长太短都无法正常运行。

应对方法:
1、HideOD插件能选的都选,PlantOm插件单选一个换标题就够了。
2、下断CreateProcessInternalA,获取参数,以后直接带参数运行。
3、设法Hook掉VirtualAlloc,将非DLL(有几个DLL也会调用VirtualAlloc,如果换掉程序会挂)调用的VirtualAlloc指定地址的参数全部换成0,这样可以使得分配的空间集中起来,此外最好在一开始将小于0x00400000空间抢先用VirtualAlloc占用掉,这样securom分配出来的空间都在0x00400000之后,至于判断调用者是否是Dll可以根据返回地址。虽然可以用OD脚本解决这个问题,不过这么复杂的脚本我不会,只好自己写了一个Loader,启动游戏进程后,不ResumeThread,让OD附进来去启动。不过即便如此,还是有一块内存是随机的(漏网之鱼),那块内存VirtualAllocEx也没有断下来,不知道是哪里分配,暂时不管,再写了一个Loader去帮助加载好了。
4、忽略除int3以外的所有异常,不能用硬件断点就不用吧。
5、加API断点不加在头部一般没问题,GetVersionExA加在下面一点,可以正常跑起来。
6、设法Hook掉GetCurrentThreadId和GetCurrentProcessId,如果非DLL调用则返回一个我们指定的值,另外对于前者,只有主线程需要偷天换日,其他线程不能换。既然第3步写了Loader了,那这里当然也让这个Loader去解决。值得一提的是,我这里的Loader的主要功能是把一个DLL提前注进去,Hook代码都在Dll里面(编译的时候Dll默认基址设大一点,免得占了VirtualAlloc的空间)。
7、设法Hook掉GetTickCount,GetSystemTimeAsFileTime,QueryPerformanceCounter,记录调用时刻的时间。

半脱过程:
1、esp定律不管用,而且就算管用也不能用,因为内存断点不能下(一堆异常处理),硬件断点也不能下。不过么,这类游戏一般都是VC写的,下断GetVersionExA,断点不能设在头部,此外加上条件,比如[esp] < 0x00600000之类,这样能过滤掉Dll的调用。
2、在不放盘的情况下,大概可以断下两次,每次回去都像极了VC的EP,可惜没有一个是真的。不管它继续运行,会提示让你放盘,把盘放进去运行,GetVersionExA再次立功了,返回后找到真正的OEP。
3、OEP是找到了,不过让它停在OEP倒是比较费劲,断点软硬不吃。把盘退出来,我再次运行到提示到让你放盘的时候,去OEP看了一下,发现还没有解出来。这时候,我试着在OEP下硬断,放盘后运行,断在了其他地方,因为DRx寄存器被修改了,再去OEP看了一下,还是没有解出来,继续运行,断在了同一个地方,再去OEP发现已经解出来了,但是GetVersionExA还没有调用,也就是说现在是下断的好机会。删除硬件断点,OEP差不多是这个样子:
push 60
push xxxxxxxx
call xxxxxxxx
如果在这几条指令上下软断,程序会挂,应该有自检,不过如果下到call过去的第一条指令上,可以正常运行,那么运气来了,下断运行,成功断下,修改eip为OEP、esp加上12,那么就相当于断在了OEP上面。这时候的esp不是程序刚开始的esp,也就是说它不遵守esp定律(如果把esp强行改成原esp,也可以正常运行)。
4、LordPE选第二个引擎可以dump,ImpRec虽然不能自动帮你找到IAT,不过既然在OEP处可以看到GetVersionExA的调用,估计找不到IAT也是不太可能的。这里securom比较强悍,本来IAT里面每一个Dll的导入函数表都会以一个0地址结尾,它这里全部换成了0x004xxxxx的地址,这样用ImpRec找不到结尾是不能直接修复的,先用它导出树文件,然后人工把这几个无法解析的0x004xxxxx地址去掉,做成可用的树文件去修复dump出来的exe,再用OD加载修复好的文件,找一片空地,加几条指令把去掉的0x004xxxxx地址改回来,OEP入口第三条指令先call到这片代码中,再jmp回原call的地址,保存,这样就完成了IAT修复。
5、接下来需要把区段补上,Hook了VirtualAlloc之后,分配的空间虽然很多而且断续,不过却是比较集中,用LordPE把自身Image之后到第一个DLL之前的一大片内存都dump下来,另外还要把那个漏网之鱼的区段dump下来,共两个区段。写一个Loader,除了帮助加载区段以外,还要提前加载一个Dll进去,此Dll要Hook掉GetCurrentThreadId和GetCurrentProcessId,作与之前调试时相同的勾当,而且还要Hook掉GetTickCount,GetSystemTimeAsFileTime,QueryPerformanceCounter,让时间在上次OEP处记录的最后一次调用时间的基础上增加,这样来欺骗securom。
6、用Loader启动,遗憾,程序运行出错。修改OEP首指令int3后保存,OD调试,发现每次出错原因都不一样。
7、企图比较运行,一大片操作让原程序停在OEP,dump出来的程序也停在OEP,偶然的一次误按F9,dump程序居然运行起来了?看到了标题画面,选重新开始,片头动画成功观赏完毕后,出错退出。

脱壳疑问:
1、后来发现,必须让原程序停在OEP处,dump程序才能运行起来。这个可能性似乎比较多,比如壳可以创建一个管道、原子、事件、互斥量之类的东西,这样原程序运行起来的情况下,dump程序才能读到这些信息得以运行起来,不过我暂时没找到确切原因。
2、这个疑问比较诡异,即便原程序停在OEP处,dump程序也不能直接用Loader运行起来,不过可以是EP首字节int3被OD或VC附加运行(VC把首字节改回来后Detach也可以运行起来),或者Loader创建进程后不ResumeThread,让OD附加进来后启动线程也可以运行。不是因为调试器改指令延时后运行的原因,我直接Sleep也不能运行,必须借调试器之手才能运行起来,想不通这是什么原因?

总结:
Hook掉VirtualAlloc等借助了http://bbs.pediy.com/showthread.php?t=52414这帖softbihu的智慧,原先也正为那么多零散的空间头疼呢,再次表示感谢。不过本人太菜,还是没能将壳脱掉,不过这次脱壳学到了不少东西,积累经验,以后再战。