在begin09论坛上nick放出了一个UnpackMe,我闲来无聊就捣鼓了下。感觉这个壳还可以啦。
花指令比较少。
IAT处理上,很像是PE-Armor。
需要修复TLS表。
下面是我分析的详细过程:
在00401000下内存写入断点:
003D025F 8B95 7D040000 mov edx, dword ptr [ebp+0x47D]
003D0265 03D5 add edx, ebp
003D0267 8B3A mov edi, dword ptr [edx]
003D0269 0BFF or edi, edi
003D026B 75 02 jnz short 003D026F
003D026D EB 65 jmp short 003D02D4
003D026F 03BD 48040000 add edi, dword ptr [ebp+0x448]
003D0275 83C2 05 add edx, 0x5
003D0278 8BF2 mov esi, edx
003D027A 56 push esi
003D027B FF95 3C040000 call dword ptr [ebp+0x43C] ; kernel32.GetModuleHandleA
003D0281 0BC0 or eax, eax
003D0283 75 07 jnz short 003D028C
003D0285 56 push esi
003D0286 FF95 40040000 call dword ptr [ebp+0x440]
003D028C 0FB64E FF movzx ecx, byte ptr [esi-0x1]
003D0290 03F1 add esi, ecx
003D0292 8BD6 mov edx, esi
003D0294 8BF0 mov esi, eax
003D0296 42 inc edx
003D0297 8B0A mov ecx, dword ptr [edx]
003D0299 83C2 04 add edx, 0x4
003D029C 51 push ecx
003D029D 0FB602 movzx eax, byte ptr [edx]
003D02A0 0BC0 or eax, eax
003D02A2 75 14 jnz short 003D02B8
003D02A4 42 inc edx
003D02A5 52 push edx
003D02A6 8B02 mov eax, dword ptr [edx]
003D02A8 50 push eax
003D02A9 56 push esi
003D02AA FF95 38040000 call dword ptr [ebp+0x438]
003D02B0 8907 mov dword ptr [edi], eax
003D02B2 5A pop edx
003D02B3 83C2 04 add edx, 0x4
003D02B6 EB 13 jmp short 003D02CB
003D02B8 42 inc edx
003D02B9 52 push edx
003D02BA 52 push edx
003D02BB 56 push esi
003D02BC FF95 38040000 call dword ptr [ebp+0x438] ; kernel32.GetProcAddress
003D02C2 8907 mov dword ptr [edi], eax ; 保存API地址
003D02C4 5A pop edx
003D02C5 0FB642 FF movzx eax, byte ptr [edx-0x1]
003D02C9 03D0 add edx, eax
003D02CB 42 inc edx
003D02CC 83C7 04 add edi, 0x4
003D02CF 59 pop ecx
003D02D0 ^ E2 CA loopd short 003D029C
003D02D2 ^ EB 93 jmp short 003D0267
很明显,这些是解密IAT的。
0045E6CC 770F4880 oleaut32.SysFreeString
0045E6D0 770FA3EC oleaut32.SysReAllocStringLen
0045E6D4 770F4B39 oleaut32.SysAllocStringLen
0045E6D8 00000000
0045E6DC 77DA7ABB advapi32.RegQueryValueExA
0045E6E0 77DA7852 advapi32.RegOpenKeyExA
0045E6E4 77DA6C27 advapi32.RegCloseKey
0045E6E8 00000000
0045E6EC 77D311DB user32.GetKeyboardType
0045E6F0 77D2B19C user32.DestroyWindow
0045E6F4 77D2C908 user32.LoadStringA
0045E6F8 77D507EA user32.MessageBoxA
0045E6FC 77D2C8B0 user32.CharNextA
0045E700 00000000
0045E704 7C8099B5 kernel32.GetACP
0045E708 7C802446 kernel32.Sleep
.
继续向下,发现一个anti dump的代码:
003D0381 64:FF35 3000000>push dword ptr fs:[0x30]
003D0388 58 pop eax
003D0389 85C0 test eax, eax
003D038B 78 0F js short 003D039C ; 这里修改SF标志位,让它跳。
003D038D 8B40 0C mov eax, dword ptr [eax+0xC]
003D0390 8B40 0C mov eax, dword ptr [eax+0xC]
003D0393 C740 20 0010000>mov dword ptr [eax+0x20], 0x1000
003D039A EB 1C jmp short 003D03B8
003D039C 6A 00 push 0x0
003D039E FF95 3C040000 call dword ptr [ebp+0x43C]
003D03A4 85D2 test edx, edx
003D03A6 79 10 jns short 003D03B8
003D03A8 837A 08 FF cmp dword ptr [edx+0x8], -0x1
003D03AC 75 0A jnz short 003D03B8
003D03AE 8B52 04 mov edx, dword ptr [edx+0x4]
003D03B1 C742 50 0010000>mov dword ptr [edx+0x50], 0x1000
003D03B8 89AD D3020000 mov dword ptr [ebp+0x2D3], ebp
003D03BE EB 59 jmp short 003D0419
003D03C0 60 pushad
然后在内存映射视图,的代码段下内存访问断点,很容易,我们就来到了如下的地方:
003D049E 8DB5 14040000 lea esi, dword ptr [ebp+0x414] ; 得到原始程序代码在壳中的地址。
003D04A4 8BBD 0C040000 mov edi, dword ptr [ebp+0x40C] ; 得到要填充的地址(也就是我们OEP的地址)
003D04AA 8B8D 10040000 mov ecx, dword ptr [ebp+0x410] ; 取出大小
003D04B0 66:8B1E mov bx, word ptr [esi]
003D04B3 33D8 xor ebx, eax
003D04B5 66:891F mov word ptr [edi], bx ; 停在这里了
003D04B8 83C7 02 add edi, 0x2
003D04BB 83C6 02 add esi, 0x2
003D04BE ^ E2 F0 loopd short 003D04B0
003D04C0 8B85 0C040000 mov eax, dword ptr [ebp+0x40C]
003D04C6 894424 FC mov dword ptr [esp-0x4], eax
003D04CA 61 popad
003D04CB FF6424 DC jmp dword ptr [esp-0x24] ; 跳转到OEP
这样就来到OEP了,如下,根据这个入口点我们可以知道,这是一个Delphi6.0到7.0的程序。
0045671C 55 push ebp
0045671D 8BEC mov ebp, esp
0045671F 83C4 F0 add esp, -0x10
00456722 B8 C4524500 mov eax, 004552C4
00456727 E8 A0FEFAFF call 004065CC
0045672C A1 88894500 mov eax, dword ptr [0x458988]
00456731 8B00 mov eax, dword ptr [eax]
00456733 E8 80CAFFFF call 004531B8
00456738 A1 88894500 mov eax, dword ptr [0x458988]
0045673D 8B00 mov eax, dword ptr [eax]
0045673F B2 01 mov dl, 0x1
00456741 E8 5AE8FFFF call 00454FA0
00456746 8B0D 748A4500 mov ecx, dword ptr [0x458A74] ; UpkMe.0045DCC8
0045674C A1 88894500 mov eax, dword ptr [0x458988]
00456751 8B00 mov eax, dword ptr [eax]
00456753 8B15 24514500 mov edx, dword ptr [0x455124] ; UpkMe.00455170
00456759 E8 72CAFFFF call 004531D0
0045675E A1 88894500 mov eax, dword ptr [0x458988]
00456763 8B00 mov eax, dword ptr [eax]
00456765 E8 9ECBFFFF call 00453308
0045676A E8 B1DFFAFF call 00404720
接下来我们修复IAT。
我们知道,Delphi程序,一般第一个函数的第一个API是GetModuleHandle。我们从这里入手,来看一下。
004065CC 53 push ebx
004065CD 8BD8 mov ebx, eax
004065CF 33C0 xor eax, eax
004065D1 A3 88774500 mov dword ptr [0x457788], eax
004065D6 6A 00 push 0x0
004065D8 E8 2BFFFFFF call 00406508 ; 这里应该是GetModuleHandleAPI的间接地址。进去应该是一个JMP跳,可是被修改成了自身的地址。
{
00406508 90 nop
00406509 E8 B29EFCFF call 003D03C0
{
003D03C0 60 pushad
003D03C1 90 nop ; 去掉花
003D03C2 90 nop
003D03C3 90 nop
003D03C4 90 nop
003D03C5 90 nop
003D03C6 90 nop
003D03C7 90 nop
003D03C8 90 nop
003D03C9 90 nop
003D03CA 5D pop ebp
003D03CB 8B6D 00 mov ebp, dword ptr [ebp] ; 003D00F3
003D03CE 8B7C24 20 mov edi, dword ptr [esp+0x20] ; 取出CALL调用API的下面一行地址
003D03D2 8BB5 81040000 mov esi, dword ptr [ebp+0x481] ; 1B09
003D03D8 03F5 add esi, ebp
003D03DA 8B06 mov eax, dword ptr [esi]
003D03DC 33D2 xor edx, edx
003D03DE B9 02000000 mov ecx, 0x2
003D03E3 F7E1 mul ecx
003D03E5 D1E8 shr eax, 1
003D03E7 3BF8 cmp edi, eax ; EDI中存放的是目标IAT,EAX不停的遍历
003D03E9 75 0A jnz short 003D03F5
003D03EB 0AD2 or dl, dl
003D03ED 75 04 jnz short 003D03F3 ; CALL/ JMP的分流器。
003D03EF EB 09 jmp short 003D03FA
003D03F1 EB 02 jmp short 003D03F5
003D03F3 EB 13 jmp short 003D0408
003D03F5 83C6 08 add esi, 0x8
003D03F8 ^ EB E0 jmp short 003D03DA
003D03FA 8B46 04 mov eax, dword ptr [esi+0x4]
003D03FD 3306 xor eax, dword ptr [esi]
003D03FF 894424 FC mov dword ptr [esp-0x4], eax
003D0403 61 popad
003D0404 FF6424 DC jmp dword ptr [esp-0x24]
003D0408 8B46 04 mov eax, dword ptr [esi+0x4] ; 如果想定了,那就把[ESI+0x4]的内容跟[ESI]的内容异或,得到的就是正确的API地址了
003D040B 3306 xor eax, dword ptr [esi]
003D040D 894424 FC mov dword ptr [esp-0x4], eax
003D0411 61 popad
003D0412 83C4 04 add esp, 0x4
003D0415 FF6424 D8 jmp dword ptr [esp-0x28] ; JMP到正确的API地址中
}
}
理论上讲,按照上面的分析,改写类似于如下的代码:
00406508 90 nop
00406509 E8 B29EFCFF call 003D03C0
这样的代码,写个脚本修复所有的代码这个壳应该就算是脱OK了。
可是,情况却不是这样的,我们在运行到:
004065CC 53 push ebx
004065CD 8BD8 mov ebx, eax
004065CF 33C0 xor eax, eax
004065D1 A3 88774500 mov dword ptr [0x457788], eax
004065D6 6A 00 push 0x0
004065D8 E8 2BFFFFFF call 00406508
这里的代码的时候,程序莫名奇妙的异常了,查了一下异常信息是:0x80000004 (SINGLE STEP)异常。
具体原因,我水平有限,没有办法,我们就在到达OEP以前来修复这个IAT。
具体过程如下:
00406508 90 nop
00406509 E8 B29EFCFF call 003D03C0
根据这个代码,我们重新加载程序,在地址00406508上下内存写入断点。运行程序,来到如下代码:
003D01B3 8BF8 mov edi, eax
003D01B5 8BCA mov ecx, edx
003D01B7 56 push esi
003D01B8 F3:A4 rep movs byte ptr es:[edi], byte ptr>; 在这里了
003D01BA 5E pop esi
003D01BB 53 push ebx
003D01BC 68 00800000 push 0x8000
003D01C1 6A 00 push 0x0
003D01C3 56 push esi
003D01C4 FF95 69040000 call dword ptr [ebp+0x469]
003D01CA 5B pop ebx
003D01CB 83C3 04 add ebx, 0x4
003D01CE ^ EB A6 jmp short 003D0176
这时我们去看一下我们要关注的地址已经被改成了什么样子:
00406508 90 nop
00406509 E8 00000000 call 0040650E
0040650E 8BC0 mov eax, eax
看到了吧,我们的00406508地址处的代码已经被改写了,只不过还没有完全写完。
我们继续看代码:
003D026F 03BD 48040000 add edi, dword ptr [ebp+0x448]
003D0275 83C2 05 add edx, 0x5
003D0278 8BF2 mov esi, edx
003D027A 56 push esi
003D027B FF95 3C040000 call dword ptr [ebp+0x43C] ; kernel32.GetModuleHandleA
003D0281 0BC0 or eax, eax
003D0283 75 07 jnz short 003D028C
003D0285 56 push esi
003D0286 FF95 40040000 call dword ptr [ebp+0x440]
003D028C 0FB64E FF movzx ecx, byte ptr [esi-0x1]
003D0290 03F1 add esi, ecx
003D02B8 42 inc edx
003D02B9 52 push edx
003D02BA 52 push edx
003D02BB 56 push esi
003D02BC FF95 38040000 call dword ptr [ebp+0x438] ; kernel32.GetProcAddress
003D02C2 8907 mov dword ptr [edi], eax ; 保存API地址
003D02C4 5A pop edx
003D02C5 0FB642 FF movzx eax, byte ptr [edx-0x1]
003D02C9 03D0 add edx, eax
003D02CB 42 inc edx
003D02CC 83C7 04 add edi, 0x4
003D02CF 59 pop ecx
003D02D0 ^ E2 CA loopd short 003D029C ; 循环获取所有用到的API地址
003D02D2 ^ EB 93 jmp short 003D0267 ; 继续其它DLL
我们可以猜测出来,接下来程序要做什么。
对的,它是要把所有API调用的地址代码从类似于:
00406508 90 nop
00406509 E8 00000000 call 0040650E
0040650E 8BC0 mov eax, eax
改写成:
00406508 90 nop
00406509 E8 B29EFCFF call 003D03C0
就是说,程序会自动定位所有要改写的地方,我们就可以借用这个时机,来修复我们的IAT调用。
好,我们继续。
003D02D4 8B85 79040000 mov eax, dword ptr [ebp+0x479]
003D02DA 83F8 01 cmp eax, 0x1
003D02DD 0F85 9E000000 jnz 003D0381
003D02E3 8BBD 81040000 mov edi, dword ptr [ebp+0x481] ; 1B90
003D02E9 03FD add edi, ebp
003D02EB 8DB5 CD020000 lea esi, dword ptr [ebp+0x2CD]
003D02F1 8B07 mov eax, dword ptr [edi]
003D02F3 0BC0 or eax, eax
003D02F5 75 02 jnz short 003D02F9
003D02F7 EB 1D jmp short 003D0316
003D02F9 25 FFFFFF7F and eax, 0x7FFFFFFF ; 很关键的代码,用来计算填充位置的算法
003D02FE 8BDE mov ebx, esi
为了相信跟踪我们熟悉的API,以保证代码的准确性,我们可以在这里下条件断点: EAX == 0x0040650E,然后自己跟踪这个过程。
经过调试,我们知道,此时的[EDI]跟0x0x7FFFFFFF&运算以后,再减去4就是我们要填充的地址了。
而[EDI+4]中存放的就是我们需要的API地址。
当然,如果相信分析过我们逆向班15课的课后作业的话,我们很明白,这个壳加密的IAT不一定全是FF25类型的,还有FF15类型的。
详细分析003D03C0这个CALL的话,我们就可以明白,它有一个分流器来处理这些IAT加密。
003D0300 2BD8 sub ebx, eax
003D0302 8958 FC mov dword ptr [eax-0x4], ebx ; 改写代码了~~~~,也就是说,我们就在这里下手,嘿嘿
003D0305 83C7 04 add edi, 0x4
003D0308 8B1F mov ebx, dword ptr [edi] ; 看到了吧,EDI+4中的内容就是API地址的指针。
003D030A 8B0B mov ecx, dword ptr [ebx]
003D030C 334F FC xor ecx, dword ptr [edi-0x4]
003D030F 890F mov dword ptr [edi], ecx
003D0311 83C7 04 add edi, 0x4 ; 继续下一个位置。
003D0314 ^ EB DB jmp short 003D02F1
好了,现在让我们整理一下我们现在所掌握的信息:
现在万事俱备,就差我们去修补它了,好我们开始动手修改这个壳的代码来修复IAT了。
先找一个足够大的空间,来写我们的代码,我找到下面的地址:
003D0593 90 nop
003D0594 90 nop
好,我们先改个跳转,来转移到我们的这个地址上:
003D02F9 33D2 xor edx, edx ; 很关键的代码,用来计算填充位置的算法
003D02FB B9 02000000 mov ecx, 0x2
003D0300 F7E1 mul ecx
003D0302 D1E8 shr eax, 1 ; 改写代码了~~~~,也就是说,我们就在这里下手,嘿嘿
003D0304 08D2 or dl, dl
003D0306 0F85 87020000 jnz 003D0593 ; 这里是CALL/JMP的分流,跳了就是FF25类型的,按照FF25的方式休息,如果不跳,就是按照FF15的方式修复
003D030C E9 9D020000 jmp 003D05AE ; 跳到FF15的修复代码中
33 D2 B9 02 00 00 00 F7 E1 D1 E8 08 D2 0F 85 87 02 00 00 E9 9D 02 00 00
到我们新找到空间将代码改写成如下样子:
003D0593 66:C740 FA FF25 mov word ptr [eax-0x6], 0x25FF ; FF25的修复代码。
003D0599 8B5F 04 mov ebx, dword ptr [edi+0x4]
003D059C 8958 FC mov dword ptr [eax-0x4], ebx
003D059F 8B0B mov ecx, dword ptr [ebx]
003D05A1 334F FC xor ecx, dword ptr [edi-0x4]
003D05A4 890F mov dword ptr [edi], ecx
003D05A6 83C7 08 add edi, 0x8
003D05A9 ^ E9 43FDFFFF jmp 003D02F1
003D05AE 66:C740 FA FF15 mov word ptr [eax-0x6], 0x15FF ; FF15的修复代码
003D05B4 8B5F 04 mov ebx, dword ptr [edi+0x4]
003D05B7 8958 FC mov dword ptr [eax-0x4], ebx
003D05BA 8B0B mov ecx, dword ptr [ebx]
003D05BC 334F FC xor ecx, dword ptr [edi-0x4]
003D05BF 890F mov dword ptr [edi], ecx
003D05C1 83C7 08 add edi, 0x8
003D05C4 ^ E9 28FDFFFF jmp 003D02F1
66 C7 40 FA FF 25 8B 5F 04 89 58 FC 8B 0B 33 4F FC 89 0F 83 C7 08 E9 43 FD FF FF 66 C7 40 FA FF
15 8B 5F 04 89 58 FC 8B 0B 33 4F FC 89 0F 83 C7 08 E9 43 FD FF FF
执行完这些代码,让我们看下API调用的代码,如下:
00406505 8D40 00 lea eax, dword ptr [eax]
00406508 - FF25 8CE74500 jmp dword ptr [0x45E78C] ; kernel32.GetModuleHandleA
0040650E 8BC0 mov eax, eax
00406510 - FF25 88E74500 jmp dword ptr [0x45E788] ; kernel32.LocalAlloc
00406516 8BC0 mov eax, eax
00406518 - FF25 84E74500 jmp dword ptr [0x45E784] ; kernel32.TlsGetValue
0040651E 8BC0 mov eax, eax
00406520 - FF25 80E74500 jmp dword ptr [0x45E780] ; kernel32.TlsSetValue
00406526 8BC0 mov eax, eax
00406528 50 push eax
00406529 6A 40 push 0x40
0040652B E8 E0FFFFFF call 00406510 ; jmp to kernel32.LocalAlloc
不过这个壳好像没有FF15类型的,好,到现在已经全部修复完成了。
接下来就是让代码修复OEP出的代码,然后我们跳到OEP处就可以了。
但是如果我们现在继续在00401000段下个断点程序会抛异常而不会像我们一开始那样,到OEP附近。
我们单步跟踪:
003D0429 8D85 D7020000 lea eax, dword ptr [ebp+0x2D7]
003D042F 8947 08 mov dword ptr [edi+0x8], eax
003D0432 8B4F 0C mov ecx, dword ptr [edi+0xC]
003D0435 03C1 add eax, ecx
003D0437 8947 0C mov dword ptr [edi+0xC], eax
003D043A 8B95 E0030000 mov edx, dword ptr [ebp+0x3E0]
003D0440 8DB5 E4030000 lea esi, dword ptr [ebp+0x3E4]
003D0446 B8 FFFF0000 mov eax, 0xFFFF
003D044B 3385 FC030000 xor eax, dword ptr [ebp+0x3FC]
003D0451 3385 00040000 xor eax, dword ptr [ebp+0x400]
003D0457 3385 04040000 xor eax, dword ptr [ebp+0x404]
003D045D 8B3E mov edi, dword ptr [esi]
003D045F 83C6 04 add esi, 0x4
003D0462 B9 08000000 mov ecx, 0x8
003D0467 8A1F mov bl, byte ptr [edi]
003D0469 32C3 xor al, bl
003D046B D1E8 shr eax, 1
003D046D 73 05 jnb short 003D0474
003D046F 35 01A00000 xor eax, 0xA001
003D0474 ^ E2 F5 loopd short 003D046B
003D0476 47 inc edi
003D0477 3B3E cmp edi, dword ptr [esi]
003D0479 ^ 75 E7 jnz short 003D0462
003D047B 83C6 04 add esi, 0x4
003D047E 83EA 01 sub edx, 0x1
003D0481 83FA 00 cmp edx, 0x0
003D0484 ^ 75 D7 jnz short 003D045D ; 循环修复并填充OEP处的代码
003D0486 8DBD 0C040000 lea edi, dword ptr [ebp+0x40C]
003D048C 66:8B1F mov bx, word ptr [edi]
003D048F 33D8 xor ebx, eax
003D0491 66:891F mov word ptr [edi], bx
003D0494 66:8B5F 02 mov bx, word ptr [edi+0x2]
003D0498 33D8 xor ebx, eax
003D049A 66:895F 02 mov word ptr [edi+0x2], bx
003D049E 8DB5 14040000 lea esi, dword ptr [ebp+0x414]
003D04A4 8BBD 0C040000 mov edi, dword ptr [ebp+0x40C]
003D04AA 8B8D 10040000 mov ecx, dword ptr [ebp+0x410]
003D04B0 66:8B1E mov bx, word ptr [esi]
003D04B3 33D8 xor ebx, eax
003D04B5 66:891F mov word ptr [edi], bx ; 如果上面单步跟踪执行的话,这里的EDI不是一个正常的地址,这里就会出异常。
003D04B8 83C7 02 add edi, 0x2
003D04BB 83C6 02 add esi, 0x2
003D04BE ^ E2 F0 loopd short 003D04B0 ; 接着修复OEP处的代码。
003D04C0 8B85 0C040000 mov eax, dword ptr [ebp+0x40C] ; 这里取出OEP
003D04C6 894424 FC mov dword ptr [esp-0x4], eax
003D04CA 61 popad
003D04CB FF6424 DC jmp dword ptr [esp-0x24] ; 跳到OEP去。
当我们单步跟踪到003D04B5的时候,发现,EDI不是一个正常的地址,往这里写内容,程序会异常的。
通过我们刚才到OEP的调试,我们知道,edi本身应该是我们OEP的地址,这个代码就是填充OEP用的。我们先不管它。
在003D04C0这里,将EAX修改为我们的OEP,然后跳到OEP以后,手工改好了OEP的代码,dump出一个文件,修复了下转存文件。可是问题提示C0000005初始化错误,本来以为全盘皆输了,经Nick提示,说是要修复TLS表。
所以,我按照没脱壳的程序把TLS填好,重新计算了下索引变量的虚拟地址(就是TLS表的RVA+SIZE)。然后保存,运行,OK了
问题解决了,嘿嘿。