在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了
問題解決了,嘿嘿。