【破解作者】 h_f22
【使用工具】 FI4.0、PEID0.94、OllyDBG、ImportREC1.6f
【破解平台】 WinXPSP2
【软件名称】 Auth.exe
【软件简介】 一个游戏
【加壳方式】 SVKP1.11
【破解声明】 水平很菜,一点感受,希望对大家有用
本文应用一个简单的SVKP脱壳实例来说明手工修复IAT的几种方法和技巧,因为重点在手工修复IAT所以
挑了一个简单的壳--SVKP1.11,这个壳难度不大,但是有几个非常典型的函数需要手工修复,虽然早就
有了相关的脚本,但是对于真正的破解者来说,知其然,还要知其所以然。
这是一个游戏,由SVKP1.11加壳,可以看出svkp的比较早期的壳强度并不大。没有StolenCode,AntiDebug
里面的rdtsc命令还是比较烦的,但他只影响单步,单步运行时注意一下即可,其余的用HIDEOD就对付了。
解码代码段的函数用内存断点即可断下,很容易找到解码的地方。入口也不难,不过这次偷个懒用
QuickUnpack的寻找OEP的插件找到:
OEP:00414FA7
知道入口后到达入口就很容易了。在00414FA7上下个内存访问的断点,忽略所有异常,F9运行程序第一
次断在一个异常处,继续会断在解码函数中,有兴趣可以自己看看其实并不复杂,完全可以自己写出来,
但是要注意的是解码是分几个段的,不同段解码方式不一样,有几段花指令挺烦人。解码函数过去了就
到入口了,看到熟悉的push ebp:
在这里Dump出来,注意不用重建IAT,重建也没用浪费地方,等会用ImportREC修复。代码:00414FA7 55 push ebp 00414FA8 8BEC mov ebp, esp 00414FAA 6A FF push -1 00414FAC 68 B0A84400 push 0044A8B0 00414FB1 68 40984100 push 00419840 00414FB6 64:A1 00000000 mov eax, dword ptr fs:[0] 00414FBC 50 push eax 00414FBD 64:8925 0000000>mov dword ptr fs:[0], esp ......
然后是IAT修复,这就是ImportREC的活了,基本上他的IAT乱序没什么难度。大部分都可以用ImportREC
的Trace1找到,自己找的话也不难,但是有6个函数是找不到的:
根据以往经验SVKP特殊处理的函数不外乎以下六个:代码:0005D95C 0C9E1F48 0005D960 0C9E28DC 0005DA40 0C9E32B5 0005DA44 0C9EB57F 0005DA64 0C9E2F30 0005DA88 0C9EB1FA
关键是哪个是哪个这个不太好办,有几点经验代码:ExitProcess GetCommandLineA GetModuleHandleA GetVersionExA GetCurrentProcess GetVersion
1、先用ImprotREC把程序修复,剩下的几个函数看调用情况和顺序一个一个修复。
2、如果是VC的程序,入口开始的头两个函数肯定是GetVersion和GetCommandLineA所以这两个好说。
比如本程序中:
3、GetModuleHandleA一般出现在GetProcAddress前面,另外VC的程序在入口开始后找到代码:00414FA7 55 push ebp 00414FA8 8BEC mov ebp, esp 00414FAA 6A FF push -1 00414FAC 68 B0A84400 push 0044A8B0 00414FB1 68 40984100 push 00419840 00414FB6 64:A1 00000000 mov eax, dword ptr fs:[0] 00414FBC 50 push eax 00414FBD 64:8925 0000000>mov dword ptr fs:[0], esp 00414FC4 83EC 58 sub esp, 58 00414FC7 53 push ebx 00414FC8 56 push esi 00414FC9 57 push edi 00414FCA 8965 E8 mov dword ptr [ebp-18], esp 00414FCD FF15 88DA4500 call dword ptr [45DA88] //这是第一个API调用,显然是GetVersion 00414FD3 33D2 xor edx, edx 00414FD5 8AD4 mov dl, ah 00414FD7 8915 EC9B4500 mov dword ptr [459BEC], edx 00414FDD 8BC8 mov ecx, eax 00414FDF 81E1 FF000000 and ecx, 0FF 00414FE5 890D E89B4500 mov dword ptr [459BE8], ecx 00414FEB C1E1 08 shl ecx, 8 00414FEE 03CA add ecx, edx 00414FF0 890D E49B4500 mov dword ptr [459BE4], ecx 00414FF6 C1E8 10 shr eax, 10 00414FF9 A3 E09B4500 mov dword ptr [459BE0], eax 00414FFE 6A 01 push 1 00415000 E8 D4470000 call 004197D9 00415005 59 pop ecx 00415006 85C0 test eax, eax 00415008 75 08 jnz short 00415012 0041500A 6A 1C push 1C 0041500C E8 C3000000 call 004150D4 00415011 59 pop ecx 00415012 E8 653C0000 call 00418C7C 00415017 85C0 test eax, eax 00415019 75 08 jnz short 00415023 0041501B 6A 10 push 10 0041501D E8 B2000000 call 004150D4 00415022 59 pop ecx 00415023 33F6 xor esi, esi 00415025 8975 FC mov dword ptr [ebp-4], esi 00415028 E8 7B440000 call 004194A8 0041502D FF15 60D94500 call dword ptr [45D960] //这是第二个API调用 是GetCommandLineA 00415033 A3 88B74500 mov dword ptr [45B788], eax ......
GetStartupInfoA之后的一个函数一般也是GetModileHandleA,也是这个函数的最后一个
API调用,他在这里当作主函数的一个参数。
情况1:
情况2:代码:0040D09F 0F95C0 setne al 0040D0A2 E9 B7000000 jmp 0040D15E 0040D0A7 68 6C794400 push 0044796C ; ASCII "USER32" 0040D0AC FF15 40DA4500 call dword ptr [45DA40] //看看这里的形式 显然是GetModileHandleA 0040D0B2 8BF8 mov edi, eax 0040D0B4 3BFB cmp edi, ebx 0040D0B6 74 76 je short 0040D12E 0040D0B8 8B35 6CDB4500 mov esi, dword ptr [45DB6C] 0040D0BE 68 58794400 push 00447958 ; ASCII "GetSystemMetrics" 0040D0C3 57 push edi 0040D0C4 FFD6 call esi //这里显然就是GetProcAddress 0040D0C6 3BC3 cmp eax, ebx 0040D0C8 A3 88954500 mov dword ptr [459588], eax 0040D0CD 74 5F je short 0040D12E 0040D0CF 68 44794400 push 00447944 ; ASCII "MonitorFromWindow" 0040D0D4 57 push edi 0040D0D5 FFD6 call esi 0040D0D7 3BC3 cmp eax, ebx 0040D0D9 A3 8C954500 mov dword ptr [45958C], eax 0040D0DE 74 4E je short 0040D12E 0040D0E0 68 34794400 push 00447934 ; ASCII "MonitorFromRect" ....
4、关于GetCurrentProcess这个函数不是很好确定,在这里提供一个参考,在VC中他往往与代码:00415051 8975 D0 mov dword ptr [ebp-30], esi 00415054 8D45 A4 lea eax, dword ptr [ebp-5C] 00415057 50 push eax 00415058 FF15 68D94500 call dword ptr [45D968] //这个就是GetStartupInfoA 0041505E E8 B53F0000 call 00419018 00415063 8945 9C mov dword ptr [ebp-64], eax 00415066 F645 D0 01 test byte ptr [ebp-30], 1 0041506A 74 06 je short 00415072 0041506C 0FB745 D4 movzx eax, word ptr [ebp-2C] 00415070 EB 03 jmp short 00415075 00415072 6A 0A push 0A 00415074 58 pop eax 00415075 50 push eax 00415076 FF75 9C push dword ptr [ebp-64] 00415079 56 push esi 0041507A 56 push esi 0041507B FF15 40DA4500 call dword ptr [45DA40] //这个就是GetModileHandleA了 00415081 50 push eax 00415082 E8 400C0100 call 00425CC7 //到这里程序是程序的主函数 00415087 8945 A0 mov dword ptr [ebp-60], eax 0041508A 50 push eax 0041508B E8 0DFDFFFF call 00414D9D .....
TerminateProcess合用,达到ExitProcess的目的,如下:
5、GetVersionExA函数也不是很好确定,可以把它留在最后,如果只剩下两三个函数,可以挨个试代码:00414DCE /75 11 jnz short 00414DE1 00414DD0 |FF7424 08 push dword ptr [esp+8] 00414DD4 |FF15 64DA4500 call dword ptr [45DA64] //这里是GetCurrentProcess 00414DDA |50 push eax 00414DDB |FF15 3CDB4500 call dword ptr [45DB3C] //这个是TerminateProcess 00414DE1 \837C24 0C 00 cmp dword ptr [esp+C], 0 ....
直道不出错为止,当然出错了也不见得是因为他,所以有必要的话一点点跟出来。
6、ExitProcess这个好说,不管它也没事,只不过程序退出的时候会出错,不影响大局。如果还剩一
两个没确定可以挨个试一下,如果ExitProcess位置正确程序不会一运行就退出。
7、当然还有一些其他的方法确定,一步步跟踪永远是最后的方法,这里介绍一个简单的方法,适用
于任何函数:
在调用API函数的地方下断点,运行程序等待断下。断下后在相应的DLL的代码段中下内存断点,并在
返回处下断(防止继续运行调用了其他API)。F9运行,正常的话会断在调用的API函数入口处,或者
是壳里,在壳里这时往往能看到要调用的API地址,不正常会断在返回处,一般是调用的下一行。当然
这招对于壳自己完全模拟系统API就失效,比如在这个壳中GetVersion等函数用这招是没用的。
这本程序中:
第一步 下断点
代码:0041BC87 68 D8694400 push 004469D8 ; ASCII "KERNEL32" 0041BC8C FF15 40DA4500 call dword ptr [45DA40] //不知道这个API是什么 在这里下断 0041BC92 85C0 test eax, eax //返回处也下断 0041BC94 74 15 je short 0041BCAB 0041BC96 68 F0AE4400 push 0044AEF0 ; ASCII "IsProcessorFeaturePresent" 0041BC9B 50 push eax 0041BC9C FF15 6CDB4500 call dword ptr [45DB6C] 0041BCA2 85C0 test eax, eax 0041BCA4 74 05 je short 0041BCAB 0041BCA6 6A 00 push 0 ....
第二步 运行
会断在 0041507B 处
第三步 下内存断点
知道是Kernel32.dll的函数,在他的代码段上下内存断点。就是这个段:
地址=7C801000
大小=00082000 (532480.)
属主=kernel32 7C800000
区段=.text
包含=代码,输入表,输出表
类型=Imag 01001040
访问=RWE
初始访问=RWE
第四步 运行 断在壳中
断在这里:
这时基本可以断定是GetModuleHandleA,但是如果不确定可以单步越过这行后继续下内存断点运行,注意代码:0C9E6248 AC lods byte ptr [esi] //在ESI中可以看到API的地址GetModuleHandleA 0C9E6249 EB 02 jmp short 0C9E624D 0C9E624B 0FE850 52 psubsb mm2, qword ptr [eax+52] 0C9E624F EB 02 jmp short 0C9E6253 0C9E6251 CD20 EB020FE9 vxdcall E90F02EB 0C9E6257 0F31 rdtsc ....
每次断下都要下内存断点。这样反复几次,终于来到kernel32.dll的空间中:
这时看看EIP,就是GetModuleHandleA,可以确定0041BC8C处的API调用就是GetModuleHandleA。即IAT中地代码:7C80B6A1 > 8BFF mov edi, edi 7C80B6A3 55 push ebp 7C80B6A4 8BEC mov ebp, esp 7C80B6A6 837D 08 00 cmp dword ptr [ebp+8], 0 7C80B6AA 74 18 je short 7C80B6C4 7C80B6AC FF75 08 push dword ptr [ebp+8] 7C80B6AF E8 C0290000 call 7C80E074 7C80B6B4 85C0 test eax, eax ....
址045DA40处应该是GetModuleHandleA的地址7C80B6A1。手工写上或者用ImportREC指定都可以。到此我们便
手工修复了一个函数。
8、上面的方法是一个通用的方法,虽然能够确定大部分函数,但是对于没被调用的函数就无能为力。这时候
我们可以在将EIP指定在这样的未知函数上,然后下DLL的内存断点和返回出的断点,原理相同,也可以得到函
数是哪个API。有人可能会问这样不会出错吗?实事上大部分情况下都不会,除非壳检测压入堆栈中的参数,
但这一般很难检测。因为壳在进行这样的API保护的时候并不知道程序什么时候会调用API,为什么会调用API。
所以壳进行API保护的时候与程序是无关的,它不关心你在什么时候调用,只关心你调用了后他能给你正确的
API入口或结果。所以无论怎样调用,都是一样会把程序正确转入到API中,有的时候有点变化,壳可能完全模
拟了API或者模拟一部分代码。至于壳是怎么做到的这一点,有兴趣的可以自己跟踪一下,方法就那么几种。
9、最后无论用什么方法确定了一个API,都要知道一个简单原则,就是import表中一般不会有重复出现的API地
址,如果你确定一个函数后发现IAT中已经有了这个函数,那肯定有问题。另外熟悉各种编译器的翻译习惯是非
常重要的,有丰富的经验可让你少走很多弯路。不过无论怎样手工修复IAT都是件非常痛苦的事情。
经过以上几步后一般就可以确定这些未知的API函数了,如下:
用ImportREC修复运行。。。OK。代码:0005D95C 0C9E1F48 ExitProcess 0005D960 0C9E28DC GetCommandLineA 0005DA40 0C9E32B5 GetModuleHandleA 0005DA44 0C9EB57F GetVersionExA 0005DA64 0C9E2F30 GetCurrentProcess 0005DA88 0C9EB1FA GetVersion
本文的这些方法都是经验,实际中还要靠读者自己灵活运用。单步跟踪是解决一切问题的方法,但不是最优的方
法,这里提供的几点经验仅供大家参考,希望能够对你有所帮助。由于作者水平有限,难免有疏漏和错误,还望
大家指教。