逆向脫殼


基本脫殼及修復

基礎知識

加殼程序

  • 1、殼是什么?
  • 加殼是可執行程序資源壓縮,是保護文件的常用手段。加殼過的程序可以直接運行,但是不能查看源代碼,要經過脫殼才可以查看源代碼。
  • 2、加殼的原理是什么?殼是怎么運作的?
  • 加殼是利用特殊的算法,對EXE、DLL文件里的資源進行壓縮、加密。類似WINZIP 的效果,只不過這個壓縮之后的文件,可以獨立運行,解壓過程完全隱蔽,都在內存中完成。它們附加在原程序上通過Windows加載器載入內存后,先於原始程序執行,得到控制權,執行過程中對原始程序進行解密、還原,還原完成后再把控制權交還給原始程序,執行原來的代碼部分。加上外殼后,原始程序代碼在磁盤文件中一般是以加密后的形式存在的,只在執行時在內存中還原,這樣就可以比較有效地防止破解者對程序文件的非法修改,同時也可以防止程序被靜態反編譯。
  • 簡單來說,殼可以完成代碼的壓縮和實時解壓執行,加殼程序可以正常執行,但是看不到原來的代碼,反編譯的時候看不懂。
  • 3、殼的分類
  • 殼的類型通常分為壓縮殼和加密殼兩類。
  • 壓縮殼的特點是減小軟件體積大小,加密保護不是重點。
  • 加密殼種類比較多,不同的殼側重點不同,一些殼單純保護程序,另一些殼提供額外的功能,如提供注冊機制、使用次數、時間限制等。

脫殼

  • 1、對特定的殼可以用專門的脫殼軟件進行脫殼。
  • 2、也可以手動脫殼

OD

反匯編工具OD=OllyDebug,OllyDbg 是一種具有可視化界面的 32 位匯編-分析調試器。它的特別之處在於可以在沒有源代碼時解決問題,並且可以處理其它編譯器無法解決的難題。

手動脫殼的基本思路

  • 1、用軟件查殼,可用PEID或者ExeinfoPE查殼。
  • 2、用進行OD動態反匯編,找到程序真正的入口。
  • 3、用OD插件或者loadPE脫殼。

OEP

  • OEP:(Original Entry Point),程序的入口點。軟件加殼一般隱藏了程序真實的OEP(或者用了假的OEP), 我們需要尋找程序真正的OEP,才可以完成脫殼。
    一般加殼程序在使用Ollydbg等動態調試工具時,會停在殼的預處理塊。即處在對於程序原始代碼塊的解壓或解密操作之前,在運行完程序自脫殼模塊后,會停留在程序加殼之前的OEP位置,此時是dump程序的最佳時期。脫殼時在真實OEP處下int3斷點,就可以捕捉到程序代碼段完全恢復的狀態。因此,尋找加殼程序的正確OEP,也成了手動脫殼時的第一要務。

OPE的基本特征

VC6

  • 區段特征

  • 入口特征

  • EBP為基指針(Base Pointer)寄存器,用它可直接存取堆棧中的數據;

  • ESP為堆棧指針(Stack Pointer)寄存器,用它只可訪問棧頂。

  • push ebp;保存ebp內容以便調用完后恢復;此時esp <- esp - 4

  • mov ebp esp; 保存調用前棧頂地址以便調用完后恢復,用ebp來保存當前esp。

VS2008和VS2013

  • 入口特征

  • 第一行是call XXXX

  • 第二行是 jmp XXXX

  • call里面是push ebp mov ebp esp

常見語言的入口點:

VB:

004012D4 > 68 54474000 push QQ個性網.00404754
004012D9 E8 F0FFFFFF call <jmp.&MSVBVM60.#100>
004012DE 0000 add byte ptr ds:[eax],al
004012E0 0000 add byte ptr ds:[eax],al
004012E2 0000 add byte ptr ds:[eax],al
004012E4 3000 xor byte ptr ds:[eax],al
004012E6 0000 add byte ptr ds:[eax],al
004012E8 48 dec eax

delphi:

004A5C54 > 55 push ebp
004A5C55 8BEC mov ebp,esp
004A5C57 83C4 F0 add esp,-10
004A5C5A B8 EC594A00 mov eax,openpro.004A59EC

BC++:

00401678 > /EB 10 jmp short btengine.0040168A
0040167A |66:623A bound di,dword ptr ds:[edx]
0040167D |43 inc ebx
0040167E |2B2B sub ebp,dword ptr ds:[ebx]
00401680 |48 dec eax
00401681 |4F dec edi
00401682 |4F dec edi
00401683 |4B dec ebx
00401684 |90 nop
00401685 -|E9 98005400 jmp 00941722
0040168A \A1 8B005400 mov eax,dword ptr ds:[54008B]
0040168F C1E0 02 shl eax,2
00401692 A3 8F005400 mov dword ptr ds:[54008F],eax
00401697 52 push edx
00401698 6A 00 push 0
0040169A E8 99D01300 call <jmp.&KERNEL32.GetModuleHandleA>
0040169F 8BD0 mov edx,eax

VC++:

0040A41E > 55 push ebp
0040A41F 8BEC mov ebp,esp
0040A421 6A FF push -1
0040A423 68 C8CB4000 push 跑跑排行.0040CBC8
0040A428 68 A4A54000 push <jmp.&MSVCRT._except_handler3>
0040A42D 64:A1 00000000 mov eax,dword ptr fs:[0]
0040A433 50 push eax
0040A434 64:8925 0000000>mov dword ptr fs:[0],esp
0040A43B 83EC 68 sub esp,68
0040A43E 53 push ebx
0040A43F 56 push esi
0040A440 57 push edi

MASM(匯編):

004035C9 > 6A 00 push 0
004035CB E8 A20A0000 call <jmp.&kernel32.GetModuleHandleA>
004035D0 A3 5B704000 mov dword ptr ds:[40705B],eax
004035D5 68 80000000 push 80
004035DA 68 2C754000 push 11.0040752C
004035DF FF35 5B704000 push dword ptr ds:[40705B]
004035E5 E8 820A0000 call <jmp.&kernel32.GetModuleFileNameA>
004035EA E8 87070000 call 11.00403D76
004035EF 6A 00 push 0
004035F1 68 0B364000 push 11.0040360B
004035F6 6A 00 push 0
004035F8 6A 64 push 64
004035FA FF35 5B704000 push dword ptr ds:[40705B]

手動脫殼方法

  • 方法1:單步跟蹤

  • 方法2:ESP定律法

  • 方法3:2次內存鏡像法

  • 方法4:一步直達法

常用匯編

1.MOV 傳送字或字節.
2.MOVSX 先符號擴展,再傳送.
3.MOVZX 先零擴展,再傳送.
4.PUSH 把字壓入堆棧.
5.POP 把字彈出堆棧.
6.PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次壓入堆棧.
7.POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次彈出堆棧.
8.JMP 無條件轉移指令
9.CALL 過程調用
10.RET/RETF過程返回.
11.2>條件轉移指令 (短轉移,-128到+127的距離內)
( 當且僅當(SF XOR OF)=1時,OP1<OP2 )
12.JA/JNBE 不小於或不等於時轉移.
13.JAE/JNB 大於或等於轉移.
14.JB/JNAE 小於轉移.
15.JBE/JNA 小於或等於轉移.
以上四條,測試無符號整數運算的結果(標志C和Z).
16.JG/JNLE 大於轉移.
17.JGE/JNL 大於或等於轉移.
18.JL/JNGE 小於轉移.
19.JLE/JNG 小於或等於轉移.
以上四條,測試帶符號整數運算的結果(標志S,O和Z).
20.JE/JZ 等於轉移.
21.JNE/JNZ 不等於時轉移.
22.JC 有進位時轉移.
23.JNC 無進位時轉移.
24JNO 不溢出時轉移.
25.JNP/JPO 奇偶性為奇數時轉移.
26.JNS 符號位為 "0" 時轉移.
27.JO 溢出轉移.
28.JP/JPE 奇偶性為偶數時轉移.
29.JS 符號位為 "1" 時轉移.
30.3>循環控制指令(短轉移)
31.LOOP CX不為零時循環.
32.LOOPE/LOOPZ CX不為零且標志Z=1時循環.
33.LOOPNE/LOOPNZ CX不為零且標志Z=0時循環.
34.JCXZ CX為零時轉移.
35.JECXZ ECX為零時轉移.

寄存器相關知識

  • EAX : 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。

  • ECX : 是計數器(counter), 是重復(REP)前綴指令和LOOP指令的內定計數器。

  • EDX : 總是被用來放整數除法產生的余數。

  • EBX : 是"基地址"(base)寄存器, 在內存尋址時存放基地址。

  • ESP : 專門用作堆棧指針,被形象地稱為棧頂指針,堆棧的頂部是地址小的區域,壓入堆棧的數據越多,ESP也就越來越小。在32位平台上,ESP每次減少4字節。

  • EBP : 是"基址指針"(BASE POINTER), 它最經常被用作高級語言函數調用的"框架指針"(frame pointer).

push ebp ;保存當前ebp
mov ebp,esp ;EBP設為當前堆棧指針
sub esp, xxx ;預留xxx字節給函數臨時變量.
  • ESI/EDI:分別叫做"源/目標索引寄存器"(source/destination index),因為在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目標串.

  • EIP: 寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令后,從EIP寄存器中讀取下一條指令的內存地址,然后繼續執行。

  • ES:附加段寄存器

  • CS:代碼段寄存器

  • SS:堆棧段寄存器

  • DS:數據段寄存器

  • FS:標志段寄存器

  • GS:全局寄存器

UPX (the Ultimate Packer for eXecutables)

是一款先進的可執行程序文件壓縮器,壓縮過的可執行文件體積縮小50%-70% ,這樣減少了磁盤占用空間、網絡上傳下載的時間和其它分布以及存儲費用。 通過 UPX 壓縮過的程序和程序庫完全沒有功能損失和壓縮之前一樣可正常地運行,對於支持的大多數格式沒有運行時間或內存的不利后果。 UPX 支持許多不同的可執行文件格式 包含 Windows 95/98/ME/NT/2000/XP/CE 程序和動態鏈接庫、DOS 程序、 Linux 可執行文件和核心。(來自百度百科)

下面,我們來給它脫殼~

1、單步運行法

  • 原理:單步跟蹤法的原理就是通過Ollydbg的單步(F8)、單步進入(F7)和運行到(F4)功能,完整走過程序的自脫殼過程,跳過一些循環恢復代碼的片段,並用單步進入確保程序不會略過OEP。這樣可以在軟件自動脫殼模塊運行完畢后,到達OEP,並dump程序。
  • 為什么要一直往下執行呢?
  • 壓縮殼的作用是代碼的壓縮和實時解壓執行,一般加殼程序會在開頭有一段代碼執行解壓縮,然后再運行到原來的程序,運行完開頭殼的代碼部分,自然就完成了解壓縮,運行到了OPE。
  • 步驟:
  • 向上的跳轉不實現,在跳轉的下一步F4,運行到選定位置;向下的跳轉實現,直接單步運行。
  • 一直單步運行找到一個大跳轉,大的跳轉之后一般就是ope。
  • 然后用OD插件或者loadPE脫殼即可得到無殼的程序。
    單步運行后找到大跳轉:

找到OEP:
Push ebp
Mov ebp,esp
將上一個函數的基址入棧,從當前esp開始作為新基址.
EBP是當前函數的存取指針,即存儲或者讀取數時的指針基地址;ESP就是當前函數的棧頂指針。每一次發生函數的調用(主函數調用子函數)時,在被調用函數初始時,都會把當前函數(主函數)的EBP壓棧,以便從子函數返回到主函數時可以獲取EBP。

遇到問題:

原因是ASLR基地址隨機化脫殼插件獲取的地址不對,不能用win7win8脫殼,換XP虛擬機。

2、ESP定律法

  • ESP定律法是脫殼的利器,是應用頻率最高的脫殼方法之一。
  • 原理:ESP定律的原理在於程序中堆棧平衡的合理利用。由於在程序自解密或者自解壓過程中,不少殼會先將當前寄存器內容壓棧,如使用pushad,在解壓結束后,會將之前的寄存器值出棧,如使用popad。因此在寄存器出棧時,往往程序代碼被自動恢復,此時硬件斷點觸發。然后在程序當前位置,只需要少許單步跟蹤,就很容易到達正確的OEP位置。
  • PUSHAD指令壓入32位寄存器,其入棧順是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI .即把所有的寄存器都壓棧。
  • “堆棧平衡”原理:call命令是訪問子程序的一個匯編基本指令,它先向堆棧中壓入下一行程序的地址,再JMP到call的子程序地址處。與call對應的就是RETN,將當前的ESP中指向的地址出棧;JMP到這個地址。這里有一個前提:如果我們要返回父程序,則當我們在堆棧中進行堆棧的操作的時候,一定要保證在RETN這條指令之前,ESP指向的是我們壓入棧中的地址。Call和retn的時候地址是對應的,這就是“堆棧平衡”。
    ###esp定律適用范圍:幾乎全部的壓縮殼,部分加密殼。

脫殼步驟:

  • 1、單步運行,右側寄存器ESP為紅色,說明esp值改變。當前ESP為pushad壓棧當前所有寄存器以后的棧頂位置。根據“堆棧平衡”原理。脫殼以后,還會在這個位置出棧。

  • 脫殼前寄存器值:

  • 2、所以,我們右鍵esp,選擇在數據窗口跟隨

  • 3、在數據窗口,我們選一個位置,右鍵“斷點”->“硬件訪問”->“Byte”設置硬件斷點,然后“調試”->“硬件斷點”,查看斷點。
  • 為什么要在這里設置斷點(⊙o⊙)?因為我們可以把殼看成一個子程序,解壓縮或者解密完了,它要把控制權交還給主程序。一開始pushad入棧,返回程序本身的時候要在同樣的位置出棧,ESP是棧頂指針,出棧時棧頂指針和入棧時相同,而出棧時,殼的部分運行完成了,也就到了OPE附近。

  • 4、運行程序到斷點處,刪除斷點。根據ESP定律,程序入口在斷點附近。

  • 此時的寄存器,可以看到,除了EIP,其他和pushad時完全一樣。

  • 5、單步運行,找到OPE

  • 6、用OD插件脫殼成功

2次內存鏡像法

步驟

  • 1、Alt+M查看內存

  • 2、找到程序段的第一個.rsrc ,設置中斷訪問並運行。

  • 3、再設一次斷點,運行

  • 4、單步運行,又到了popad

  • 5、找到OPE,脫殼

遇到的問題

  • 問題:出錯,第二次設斷運行后,沒有刪除內存斷點,再單步時出錯。
  • 解決:出錯是因為沒有刪除訪問中斷,刪除訪問中斷就可以了。

原理

  • 斷點:讓程序在運行時暫停到特定的代碼段,下斷點是調試器的功能之一,可以讓程序中斷在需要的地方,從而方便其分析。
  • 常用的斷點包括:INT3斷點、硬件斷點、內存斷點、消息斷點、條件斷點等。
  • 內存訪問斷點分為,內存訪問斷點和內存寫入斷點。
  • 內存訪問:代碼段的代碼訪問內存數據。
  • 內存寫入:代碼段的代碼執行完成后把數據寫入內存
  • 內存訪問斷點:代碼段的代碼訪問內存數據的時候,程序暫停。
  • 內存寫入斷點:代碼段的代碼寫入內存數據的時候,程序暫停。
  • 如果給內存設內存訪問斷點,那么,程序就會在讀取該內存數據的代碼上中斷。
  • 比如:
    寄存器間接尋址,操作數在以該寄存器中的內容為偏移地址的內存單元中,把以“ecx”值為地址的內存單元的值賦予dl,如果在地址為“ecx”值的內存下斷點,那么,程序運行到這一條指令時就會中斷。
  • 殼在解壓縮時,需要在內存讀取壓縮過的源代碼,進行解壓縮,然后再進行內存寫入。
  • 第一次內存斷點設在資源段,假設解壓順序是先代碼段,數據段,再資源段,那么。資源段發生寫入中斷時,程序就已經完成了代碼段和數據段的解壓。然后,我們在解壓完成的基礎上,在代碼段下第二個斷點,程序再次中斷時,就運行到了代碼段的OPE附近。
  • 在第二次中斷后,經過幾次單步運行即可到達OPE。

一步直達法

  • 這個非常簡單,但是有適用范圍,適用於USP和aspack
  • 原理:開頭有pushad入棧,那么一定會有popad出棧,出棧時就說解壓縮完成后,到達了OPE附近。
  • 步驟:
  • 直接搜索POPAD,然后F4,運行到POPAD,即可找到OPE

- 注意:不要勾選整個塊
- 如圖,jump過去以后就說OPE

脫殼后的修復

IAT修復

IAT

導入地址表(IAT):Import Address Table 由於導入函數就是被程序調用但其執行代碼又不在程序中的函數,這些函數的代碼位於一個或者多個DLL 中.當PE 文件被裝入內存的時候,Windows 裝載器才將DLL 裝入,並將調用導入函數的指令和函數實際所處的地址聯系起來(動態連接),這操作就需要導入表完成.

為什么要手動修復IAT?

我們已經掌握了如何尋找OEP和脫殼,有的時候,Dump出來的時候不能正常運行,是因為還有一個輸入表沒有進行處理,這時候,我們可以嘗試手動修復IAT。

原理

程序的IAT是連續的排列的,所以我們只需要找到IAT的起始位置和末位置,就可以確定IAT的地址和大小。在壓縮殼中,我們只要找一個調用系統的API的Call的地址,然后在數據窗口中查找,確定IAT起始和結束地址。然后在OD中手動修復。

思路

  • 1.查殼
  • 2.找到程序入口
  • 3.OD插件脫殼-->失敗
  • 4.LoadPE脫殼-->失敗
  • 5.用ImportREC自動修復-->失敗
  • 6.手動查找IAT,用ImportREC進行修復-->成功

實驗過程

  • 查殼,顯示FSG殼

  • 我們用ESP定律法脫殼,關鍵跳轉后,沒有到OPE,右鍵分析,從模塊中刪除分析。

  • 到達OPE

  • 用OD的脫殼插件脫殼,這個插件是可以自動修復ITA的,如圖勾擇重建輸入表。

  • 用OD自帶的插件脫殼以后,查殼顯示無殼,但不能運行。

  • 換一種方法脫殼試試~用LoadPE脫殼

  • 修正鏡像大小

  • 還是不能運行

  • 用ImportREC進行輸入表重建,嘗試修復程序,輸入剛剛調試時找到的OPE地址,點擊自動查找IAT

  • 點擊獲取輸入表,工具自動幫你填充RVA,這里的RVA是IAT起始的偏移量。RAV是偏移地址的意思,如果說Windows裝載器將一個PE文件裝入 00400000h處的內存中,而某個節中的某個數據被裝入0040xxxxh處,那么這個數據的RVA就是(0040xxxxh- 00400000h)=xxxxh。

- 轉儲到文件,嘗試運行,殼已經沒有了,但是還是不行,不能運行……

- 那我們只有試一下手動修復了

  • 隨便找一個調用系統的API的Call,如果這個函數的調用地址在IAT中,那么我們根據這個地址就能找到IAT的地址范圍。

- 在輸入表(IAT)中找到該地址

  • 記錄地址0x425210,在命令下輸入 d 425210

  • 到達數據窗口的這一段,這是IAT地址,名稱與輸入表重建工具中的函數對比,內容一致,可以確定這個CALL的調用函數地址在IAT中。

  • 因為IAT段是連續的,因此向上找到開頭,向下找到結尾

  • IAT段開始:

  • IAT段結束

- 獲取IAT信息
起始:00425000
結束:00425510
RAV:0x00425000-0x00400000=0x25000
Size:0x0042410-0x00425000=0x510

- 進行修復,輸入IAT信息,RVA和SIZE,獲取輸入表。

- 轉存,運行成功~脫殼完成~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM