Armadillo 2.52加殼原理分析和改進的脫殼方法 (12千字)

看雪資料發表於2015-11-15

目標軟體:The Armadillo Software Protection System Version2.52 build 1164
目標檔案:Armadillo.exe
加殼方式:Armadillo 2.52
使用工具:WinDbg或trw2000, peditor, WinHex 10.2 SR-2,m$的win32 sdk文件,<winnt.h>
     
URL:      http://www.siliconrealms.com/
本文作者:leo_cyl

    Armadillo 的保護方式主要有以下特點:修改pe頭,使WinDbg等偵錯程式無法“attach”,使procdump無法dump;anti-debug程式碼(我以前的貼子有討論);利用偵錯程式捕獲頁面保護異常,分段解碼。

(一)捕獲頁面保護異常、找OEP

    和以前版本一樣,Armadillo 外殼產生另外一個程式(被加殼的程式)。所以下斷點“bpx createprocessa”

004C39E4                call    ds:CreateProcessA
004C39EA                test    eax, eax
004C39EC                jnz    4C39F5
……
……

0187:004C3A0F 51              PUSH    ECX
0187:004C3A10 68E6314C00      PUSH    DWORD 004C31E6
0187:004C3A15 6A00            PUSH    BYTE +00
0187:004C3A17 6A00            PUSH    BYTE +00
0187:004C3A19 FF1554A04C00    CALL    `KERNEL32!CreateThread`
0187:004C3A1F 6A00            PUSH    BYTE +00
0187:004C3A21 FF1530A14C00    CALL    `KERNEL32!GetModuleHandleA`
……
……
……
0187:004C3A42 6A00            PUSH    BYTE +00
0187:004C3A44 FF1530A14C00    CALL    `KERNEL32!GetModuleHandleA`
……
……

0187:004C3A68 E80C0C0000      CALL    004C4679
0187:004C3A6D 83C40C          ADD      ESP,BYTE +0C
0187:004C3A70 8B8D68FEFFFF    MOV      ECX,[EBP+FFFFFE68]
0187:004C3A76 51              PUSH    ECX
0187:004C3A77 8B157CE54C00    MOV      EDX,[004CE57C]
0187:004C3A7D 52              PUSH    EDX
0187:004C3A7E E8BA0C0000      CALL    004C473D

以上程式碼在被加殼的程式第一條指令處寫入“eb fe”程式碼,即“jmp eip” 使被加殼程式“掛起”


0187:004C3A83 83C408          ADD      ESP,BYTE +08
0187:004C3A86 A17CE54C00      MOV      EAX,[004CE57C]
0187:004C3A8B 8B4804          MOV      ECX,[EAX+04]
0187:004C3A8E 51              PUSH    ECX
0187:004C3A8F FF158CA04C00    CALL    `KERNEL32!ResumeThread`
0187:004C3A95 8B157CE54C00    MOV      EDX,[004CE57C]
0187:004C3A9B 8B4208          MOV      EAX,[EDX+08]
0187:004C3A9E 50              PUSH    EAX
0187:004C3A9F FF1588A04C00    CALL    `KERNEL32!DebugActiveProcess`
0187:004C3AA5 8B0D7CE54C00    MOV      ECX,[004CE57C]
0187:004C3AAB 8B5104          MOV      EDX,[ECX+04]
0187:004C3AAE 52              PUSH    EDX
0187:004C3AAF FF1584A04C00    CALL    `KERNEL32!SuspendThread`

以上程式碼通過呼叫“DebugActiveProcess”開始除錯被加殼的程式。
……
……
……

0187:004C3AFA 68E8030000      PUSH    DWORD 03E8  《===== dwMilliseconds
0187:004C3AFF 8B9574FEFFFF    MOV      EDX,[EBP+FFFFFE74]
0187:004C3B05 52              PUSH    EDX          〈====  lpDebugEvent
0187:004C3B06 FF1580A04C00    CALL    `KERNEL32!WaitForDebugEvent`
0187:004C3B0C 85C0            TEST    EAX,EAX
0187:004C3B0E 0F8414060000    JZ      NEAR 004C4128
0187:004C3B14 C78570FDFFFF68B4+MOV      DWORD [EBP+FFFFFD70],004CB468
0187:004C3B1E 33C0            XOR      EAX,EAX
0187:004C3B20 A06EE54C00      MOV      AL,[004CE56E]
0187:004C3B25 85C0            TEST    EAX,EAX
0187:004C3B27 7565            JNZ      004C3B8E
0187:004C3B29 8B4DE4          MOV      ECX,[EBP-1C]
0187:004C3B2C 81E1FF000000    AND      ECX,FF
0187:004C3B32 85C9            TEST    ECX,ECX
0187:004C3B34 7458            JZ      004C3B8E      〈==跳轉
0187:004C3B36 C645E400        MOV      BYTE [EBP-1C],00
0187:004C3B3A C745FC00000000  MOV      DWORD [EBP-04],00
0187:004C3B41 669C            PUSHFW            +
0187:004C3B43 6658            POP      AX        |
0187:004C3B45 66056200        ADD      AX,62    |設定單步除錯標誌
0187:004C3B49 66059E00        ADD      AX,9E    |anti-debug???
0187:004C3B4D 6650            PUSH    AX        |
0187:004C3B4F 669D            POPFW              +
0187:004C3B51 66050100        ADD      AX,01
0187:004C3B55 C745FCFFFFFFFF  MOV      DWORD [EBP-04],FFFFFFFF
0187:004C3B5C EB30            JMP      SHORT 004C3B8E

以上進入除錯迴圈,根據DebugEvent結構中的debugging event code 做相應處理。其中最重要的是EXCEPTION_DEBUG_EVENT(0x00000001)。請參考MSDN中WaitForDebugEvent的描述。

……
……
0187:004C3BA5 MOV      DWORD [EBP+FFFFFB60],80010001
0187:004C3BAF MOV      EDX,[EBP+FFFFFE74]  〈====注意!edx = lpDebugEvent
0187:004C3BB5 CMP      DWORD [EDX],BYTE +01      //EXCEPTION_DEBUG_EVENT
0187:004C3BB8 JNZ      NEAR 004C3ED7
0187:004C3BBE MOV      DWORD [EBP+FFFFFD70],004CB470
0187:004C3BC8 XOR      EAX,EAX
0187:004C3BCA MOV      AL,[004CE56F]
0187:004C3BCF TEST    EAX,EAX
0187:004C3BD1 JZ      NEAR 004C3CB7
0187:004C3BD7 MOV      ECX,[EBP+FFFFFE74]
0187:004C3BDD CMP      DWORD [ECX+0C],80000001    //STATUS_GUARD_PAGE_VIOLATION
0187:004C3BE4 JNZ      NEAR 004C3CB7
0187:004C3BEA MOV      DWORD [EBP+FFFFFD70],004CB488
0187:004C3BF4 MOV      EDX,[EBP+FFFFFE74]  〈====注意!edx = lpDebugEvent
0187:004C3BFA MOV      EAX,[EDX+24]
……
……
0187:004C3BB5 處比較是否是EXCEPTION_DEBUG_EVENT。(包括頁面異常)
注意在NT下,頁面異常的程式碼是0x80000001(STATUS_GUARD_PAGE_VIOLATION)而9x下是0xc0000005(STATUS_ACCESS_VIOLATION)所以在9x下會來到這裡:

0187:004C3CBD CMP      DWORD [ECX+0C],C0000005    //STATUS_ACCESS_VIOLATION
0187:004C3CC4 JNZ      NEAR 004C3E89
0187:004C3CCA MOV      EDX,[EBP+FFFFFE74] 〈====注意!edx = lpDebugEvent
0187:004C3CD0 CMP      DWORD [EDX+5C],BYTE +00
0187:004C3CD4 JZ      004C3CE2
0187:004C3CD6 MOV      DWORD [EBP+FFFFFD70],004CB4E8
0187:004C3CE0 JMP      SHORT 004C3CEC

當被加殼的程式需要解碼時,會觸發頁面異常,來到上述程式碼,如果你參考MSDN中有關DebugEvent結構的描述,很容易找到產生頁面異常的地址,是在lpDebugEvent+0x18處。(即edx+18)。
這有什麼用呢?呵呵……這是找OEP的關鍵!!!假如在0187:004C3CD0下斷點,當程式第一次在這裡中斷時,檢視edx+18h的內容“dd edx+18”即可看到OEP。(41EF80H)

繼續跟蹤……

0187:004C3D52 8B8D50FBFFFF    MOV      ECX,[EBP+FFFFFB50]
0187:004C3D58 83E901          SUB      ECX,BYTE +01
0187:004C3D5B 85C9            TEST    ECX,ECX
0187:004C3D5D 7C14            JL      004C3D73
0187:004C3D5F 6A01            PUSH    BYTE +01
0187:004C3D61 8B9550FBFFFF    MOV      EDX,[EBP+FFFFFB50]
0187:004C3D67 83EA01          SUB      EDX,BYTE +01
0187:004C3D6A 52              PUSH    EDX
0187:004C3D6B E8D0030000      CALL    004C4140              //關鍵,進入
0187:004C3D70 83C408          ADD      ESP,BYTE +08
0187:004C3D73 8B8550FBFFFF    MOV      EAX,[EBP+FFFFFB50]
0187:004C3D79 83C001          ADD      EAX,BYTE +01
0187:004C3D7C 3B0588E54C00    CMP      EAX,[004CE588]
0187:004C3D82 7D14            JNL      004C3D98

以上程式碼將產生頁面異常的地址進行頁對齊,並CALL 004C4140解碼。
進入函式004C4140後,來到這裡:

0187:004C4271 PUSH    BYTE +00
0187:004C4273 MOV      ECX,[EBP+08]
0187:004C4276 PUSH    ECX
0187:004C4277 CALL    004C432A  〈==這裡對異常頁解碼
0187:004C427C ADD      ESP,BYTE +08
0187:004C427F AND      EAX,FF
0187:004C4284 TEST    EAX,EAX
0187:004C4286 JNZ      004C428F〈===解碼成功?
0187:004C4288 XOR      AL,AL
0187:004C428A JMP      004C4326
0187:004C428F MOV      EDX,[004CE58C] 〈==注意!!!
0187:004C4295 ADD      EDX,BYTE +01
0187:004C4298 MOV      [004CE58C],EDX
0187:004C429E MOV      EAX,[004CE588]
0187:004C42A3 LEA      ECX,[EAX*4+FFFFFFFC]

注意0187:004C428F處,[004CE58C]是一個記數器,解碼成功後加一,有什麼用呢?看後面……

……
……
0187:004C42D1 25FF000000      AND      EAX,FF
0187:004C42D6 85C0            TEST    EAX,EAX
0187:004C42D8 754A            JNZ      004C4324
0187:004C42DA 8B0D8CE54C00    MOV      ECX,[004CE58C]
0187:004C42E0 3B0D2CB44C00    CMP      ECX,[004CB42C]
0187:004C42E6 7E3C            JNG      004C4324
0187:004C42E8 6A01            PUSH    BYTE +01
0187:004C42EA 8B158CE54C00    MOV      EDX,[004CE58C]

當解碼一定數量的頁面後,將以前解碼的頁面重新加密!在這裡的數量是0x13頁。為了以後脫殼方便。
我用peditor計算出0187:004C428F的實際偏移,然後用WinHex,將ADD EDX,BYTE +01改為ADD EDX,BYTE +00;只改了一個byte。

簡要分析0187:004C4277處的解碼函式“ CALL    004C432A”。以下是它的流程:

1。呼叫VirtualProtectEx將異常頁面屬性改為PAGE_READWRITE
2。呼叫ReadProcessMemory,將異常頁面讀入
3。解碼。
4。呼叫WriteProcessMemory,寫入正確程式碼。
5。呼叫VirtualProtectEx將頁面屬性改為PAGE_EXECUTE_READ


(二)修改PE頭

    被加殼的程式執行後,用procdump沒法dump出,會非法操作。用ring3的偵錯程式沒法“attach”,用peditor dump出的檔案是無效的exe。為什麼呢?通過比較dump出的PE頭,發現偏移0x3c處被修改。重新裝入Armadillo,下斷點“bpm 400003c w”,執行……

0187:00A8EF54 MOV      EAX,[EBP-0C]  〈== EAX=400000H
0187:00A8EF57 LEA      ECX,[EBP-04] 
0187:00A8EF5A SUB      EBX,EDI      〈=== EBX = 4E7119
0187:00A8EF5C PUSH    ECX
0187:00A8EF5D ADD      [EAX+3C],EBX  〈== 修改PE頭,[EAX+3C] = 40003C
0187:00A8EF60 PUSH    DWORD [EBP-04]
0187:00A8EF63 PUSH    BYTE +40
……
……
……
0187:00A8EF89 AND      EAX,BYTE +03
0187:00A8EF8C LEA      ECX,[EBP-08]
0187:00A8EF8F INC      EAX
0187:00A8EF90 ADD      [EDI+06],AX    〈==修改PE頭,[EDI+06] = 4000FE
0187:00A8EF94 CALL    00A88597
0187:00A8EF99 MOV      EBX,EAX

共修改3C和FE兩處,參考有關PE頭的資料,可知偏移3C的DWORD是PE表頭的偏移(原值是F8)。偏移FE的WORD是sections 的個數(原值是8)。Armadillo故意將PE頭改錯,難怪沒法dump出。

(三)dump出加殼的程式

好了,原理分析清楚了。要dump出加殼程式就很容易了。

事先用peditor檢視,得知size of image 是104000;

首先找到OEP是41EF80,另外修改004C428F,將ADD EDX,BYTE +01改為ADD EDX,BYTE +00。
在41EF80下斷點。中斷後,將PE頭改回,40003C處是000000F8,4000FE處是0008,注意高位在前!!!

在程式中找一個沒用到的地址。不要選程式碼段,因為解碼後會覆蓋掉我們寫的程式碼。我選.Data1段的最後256個byte即4CF000處,將eip改為4CF000,輸入以下程式碼:

push esi
push ecx
push eax
mov esi,401000
mov ecx,103000  //size of image 減 1000h
rep lodsb
pop eax
pop ecx
pop esi
int3

注意我沒有使用hying的程式碼,因為要申請記憶體和查詢api地址,而且記憶體不夠的話也麻煩,其實只要掃描一遍記憶體就足以觸發偵錯程式了,修改004C428F的原因就是防止外殼把解碼過的頁面從新加密。
f5執行,在int3處停下(注意開啟 i3here on),將eip改回41EF80,可看到已經解碼了,掛起程式,回到window桌面,這時用什麼工具都可以dump出了!(任我魚肉了!!)。

另外,還有一個地方要注意,最好在OEP處掛起程式再dump,因為這時程式的初始化變數還沒有被修改。
大家可對比一下,當程式執行後,用procdump dump出的檔案和在OEP處dump出的有何不同。(會非法操作!!因為初始化變數[438110]已經不同了。)

(四)修復IAT

沒什麼方便的方法!:(
我的方法是用peditor檢視,得知IAT在426000處,下斷點“bpm 426000 w” 在這停下:

0187:00A8E4BB 8A06            MOV      AL,[ESI]
0187:00A8E4BD 3AC3            CMP      AL,BL
0187:00A8E4BF 7468            JZ      00A8E529
0187:00A8E4C1 3CFF            CMP      AL,FF
0187:00A8E4C3 7537            JNZ      00A8E4FC
0187:00A8E4C5 668B7E01        MOV      DI,[ESI+01]
0187:00A8E4C9 46              INC      ESI
0187:00A8E4CA 0FB7C7          MOVZX    EAX,DI
0187:00A8E4CD 50              PUSH    EAX
0187:00A8E4CE 46              INC      ESI
0187:00A8E4CF FF75F4          PUSH    DWORD [EBP-0C]
0187:00A8E4D2 46              INC      ESI
0187:00A8E4D3 E84075FFFF      CALL    00A85A18

然後把[ESI]前後的內容儲存下來,以後手工修復IAT時參考。

(五)小結

無論用什麼方法脫殼,原理和hying的是一樣的。
但為什麼用procdump 不會觸發偵錯程式呢?我的解釋是這樣:無論procdump 或 WinHex 在讀取其他程式的內容時,是通過WriteProcessMemory來完成的,而WriteProcessMemory最終呼叫VMM完成讀取,VMM是在ring0上,所以不會觸發ring3上的偵錯程式。我能想到的方法是:在ring3下、在自身程式空間內掃描整個記憶體,以觸發偵錯程式。

附錄:

附上部分DEBUG_EVENT結構的宣告:(註釋是我加的)
typedef struct _DEBUG_EVENT {
  DWORD dwDebugEventCode;  //偏移0x0 
  DWORD dwProcessId;      //偏移0x4
  DWORD dwThreadId;        //偏移0x8
  union {
      EXCEPTION_DEBUG_INFO Exception;    //偏移0xC
      CREATE_THREAD_DEBUG_INFO CreateThread;
      CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
      EXIT_THREAD_DEBUG_INFO ExitThread;
      EXIT_PROCESS_DEBUG_INFO ExitProcess;
      LOAD_DLL_DEBUG_INFO LoadDll;
      UNLOAD_DLL_DEBUG_INFO UnloadDll;
      OUTPUT_DEBUG_STRING_INFO DebugString;
      RIP_INFO RipInfo;
  } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

typedef struct _EXCEPTION_DEBUG_INFO { //即異常程式碼是0x00000001時的結構
  EXCEPTION_RECORD ExceptionRecord;      //偏移0xC;0x80000001為STATUS_GUARD_PAGE_VIOLATION(NT)
                                        //0xC0000005為STATUS_ACCESS_VIOLATION(WIN (win 9x)
  DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;

typedef struct _EXCEPTION_RECORD {
  DWORD ExceptionCode;              //偏移0xc
  DWORD ExceptionFlags;              //偏移0x10
  struct _EXCEPTION_RECORD *ExceptionRecord;  //偏移0x14
  PVOID ExceptionAddress;              //偏移0x18
  DWORD NumberParameters;
  ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

相關文章