传说中的最强壳——甲壳3一直处于ungelivable的状态,只好拿着甲壳2反复膜拜,于是有了这篇手记
OD载入主程序(10.10.04版),停在这里:
00401500 >/$ 55 push ebp
00401501 |. 8BEC mov ebp, esp
00401503 |. 53 push ebx
00401504 |. 56 push esi
00401505 |. 57 push edi
00401506 |. BF 04304000 mov edi, 00403004
0040150B |. 6A 00 push 0 ; /pModule = NULL
0040150D |. 893D 28304000 mov dword ptr [403028], edi ; |
00401513 |. FF15 18204000 call dword ptr [<&KERNEL32.GetModuleH>; \GetModuleHandleW
00401519 |. 68 80204000 push 00402080 ; /ResourceType = "DLL"
0040151E |. 6A 65 push 65 ; |ResourceName = 65
00401520 |. 6A 00 push 0 ; |hModule = NULL
00401522 |. FF15 10204000 call dword ptr [<&KERNEL32.FindResour>; \FindResourceA
00401528 |. 8BF0 mov esi, eax
0040152A |. 56 push esi ; /hResource
0040152B |. 6A 00 push 0 ; |hModule = NULL
0040152D |. FF15 14204000 call dword ptr [<&KERNEL32.LoadResour>; \LoadResource
00401533 |. 50 push eax ; /nHandles
00401534 |. FF15 20204000 call dword ptr [<&KERNEL32.LockResour>; \SetHandleCount
0040153A |. 56 push esi ; /hResource
0040153B |. 6A 00 push 0 ; |hModule = NULL
0040153D |. 8BD8 mov ebx, eax ; |
0040153F |. FF15 1C204000 call dword ptr [<&KERNEL32.SizeofReso>; \SizeofResource
甲壳从资源段里释出一个dll,之后是要load的,这里先不甩它,直接alt+M,对.text下内存写入断点,F9,停在这里
003D460D C600 00 mov byte ptr [eax], 0 ; 内存块清0
003D4610 ^ EB E4 jmp short 003D45F6
003D4612 C745 CC 0000000>mov dword ptr [ebp-34], 0
003D4619 EB 09 jmp short 003D4624
由于这是申请的内存地址,不保证在你机器上的地址与我的一样。删除内存断点。003D4610处的jmp,将跳回循环继续清0,所以直接在003D4612上F4,下面开始循环解码
003D461B 8B45 CC mov eax, dword ptr [ebp-34] ; 累加计数器(或偏移)
003D461E 83C0 01 add eax, 1
003D4621 8945 CC mov dword ptr [ebp-34], eax
003D4624 8B45 CC mov eax, dword ptr [ebp-34]
003D4627 3B45 FC cmp eax, dword ptr [ebp-4]
003D462A /73 12 jnb short 003D463E
这里eax是从0开始自增的,通过一个局部变量传递一下,最后比较。毫无疑问,这样做唯一的目的,就是用迅猛的花指令晃瞎追踪者的眼睛。
接下来是解码的核心
003D462C 8B45 DC mov eax, dword ptr [ebp-24]
003D462F 0345 CC add eax, dword ptr [ebp-34]
003D4632 8B4D F0 mov ecx, dword ptr [ebp-10]
003D4635 034D CC add ecx, dword ptr [ebp-34]
003D4638 8A11 mov dl, byte ptr [ecx]
003D463A 8810 mov byte ptr [eax], dl
003D463C ^ EB DD jmp short 003D461B
003D463E ^ E9 2FFFFFFF jmp 003D4572
这里用到了同样迅猛的花指令,核心算法是eax中的自增数值加上一个基地址得到一个新的地址,然后ecx用同样的办法得到一个地址并导出一个byte,最后赋值。
接下来两个jmp都是在跳回循环(两个循环)继续纠结的,时间关系我们就不接着观赏了,F4到第二个jmp的下一个指令,然后对.rsrc段下写入断点,F9,停在这里
003D3DF4 891481 mov dword ptr [ecx+eax*4], edx ; kernel32.GetFileAttributesW
003D3DF7 ^ E9 33FFFFFF jmp 003D3D2F
003D3DFC ^ E9 75FEFFFF jmp 003D3C76
003D3E01 5F pop edi
003D3E02 5E pop esi
003D3E03 5B pop ebx
003D3E04 8BE5 mov esp, ebp
003D3E06 5D pop ebp
003D3E07 C2 0400 retn 4
这里负责折腾IAT,下面也有两个jmp,第一个jmp是取下一个API地址,第二个jmp是开始下一个dll的循环,同样由于时间关系,我们不看了,直接从retn处返回——别忘了先删除内存断点。
乱入一句,并不是所有甲壳2加的程序都可以通过对rsrc下断点到这的,所以我自作主张地提供了特征码89 14 81 E9,在完成第一个内存断点后,直接搜特征码就可以到这了。
接上文,返回到这:
003D464C 8B45 EC mov eax, dword ptr [ebp-14]
003D464F 50 push eax
003D4650 E8 EBFCFFFF call 003D4340
003D4655 83C4 04 add esp, 4
以我的智商显然无法理解这一块的作用,所以我选择直接ctrl+F9,到这:
003D46C2 8945 FC mov dword ptr [ebp-4], eax ; JOCOC-VC.004320D8
003D46C5 6A 00 push 0
003D46C7 FF15 18503D00 call dword ptr [3D5018] ; kernel32.GetModuleHandleW
菊花都露出来了。003D46C2处eax的值就是oep,我们看看甲壳是怎样匠心独运到达OEP的:
003D471D A3 04643D00 mov dword ptr [3D6404], eax
003D4722 8B65 08 mov esp, dword ptr [ebp+8]
003D4725 61 popad
003D4726 FF25 04643D00 jmp dword ptr [3D6404]
啊哈,就是这样,eax直接赋值给一个变量,然后用这个变量间接跳转。
当然,像甲壳这种高强度的保护壳,是不会让eax的值一直保持为OEP,不过那一串酷炫的花指令太过耀眼,为了防止误伤,我就都统统省略了。
由于w哥,还是t哥说过,脚本什么的最讨厌了,所以就不放脚本了,接着我们看看神奇的VCPU。
定位想不到太优雅的办法,只好继续用粗鄙的特征码:89 25 ???????? 8B 25 ???????? 9C 60,找到一切罪恶的开始:
00D50000 8925 0C613D00 mov dword ptr [3D610C], esp
00D50006 8B25 9C603D00 mov esp, dword ptr [3D609C]
00D5000C 9C pushfd
00D5000D 60 pushad
00D5000E A1 0C613D00 mov eax, dword ptr [3D610C]
00D50013 894424 0C mov dword ptr [esp+C], eax
00D50017 A1 F8603D00 mov eax, dword ptr [3D60F8]
00D5001C A3 24633D00 mov dword ptr [3D6324], eax
00D50021 8B25 24633D00 mov esp, dword ptr [3D6324]
我深知自己是菜鸟,所以见到pushfd神马的,一定会毫不犹豫地对esp下硬断,到这里:
003D2FF7 8B25 0C613D00 mov esp, dword ptr [3D610C]
003D2FFD - FF25 98633D00 jmp dword ptr [3D6398]
003D2FFD处的跳转,就是跳去执行被替换,不,是被VCPU打乱然后重组的代码,随便执行个看看:
00D40000 55 push ebp
00D40001 90 nop
00D40002 90 nop
00D40003 90 nop
00D40004 90 nop
第一个就是原来的指令,无花无码,童叟无欺,至于剩下的nop,都是白送的
VCPU每次执行一条原来的指令。不过上面只是第一次执行的样子,第二次执行时,除了第一个是原来的指令外,还会多一些别的东西。而且从第二个指令开始,就没有pushfd之类的可以利用,还多了许多EB 02的乱序,恩,应该叫乱序吧?还是叫乱序吧?果然是叫乱序吧?灰常强大,绝非人力所能还原,所以执行到003D2FFD,我就先删了硬断,然后下了F2,甲壳2每次都从这里执行到原始指令,而且原始指令始终在同一个位置,果然是从一而终,这种执着,叫人怎能不佩服。
要看原来完整的指令,只要在下断后看一条按一次F9,至此,整个世界清静了。