發新話題

『轉貼』 脫殼基礎知識入門(2006年版)

『轉貼』 脫殼基礎知識入門(2006年版)

聲明:本篇文章個人用戶可以免費的收藏、參考,但是謝絕任何商業網站及傳統媒體(如出版物)摘編轉載!
    現在加解密發展己形成2個分支了,一個就是傳統的算法,另一個就是加密殼。越來越多的軟件採用了密碼學相關算法,現在要做出一個軟件註冊機己不像前幾年那麼容易,這就要求解密者必須要有一定的數學功底和密碼學知識,而這些在短時間內是不容易掌握的。除了密碼學的應用,越來越多的軟件加殼了,因此要求解密者必須掌握一些脫殼技術,這就使得殼成瞭解密必須邁過的一個門檻。殼發展到今天,強度越來越高了,將許多人擋在門外,使得大家望殼興歎。另外,論壇現在兩極分化比較嚴重,高手討論的脫殼技術新手看不懂,很多人想學脫殼,但看到殼這麼難,只好放棄了,造成新手與高手間一個斷檔,為了鼓勵更多新人加入脫殼的行列,很有必要將殼有關知識總結一下。www.pediy.com主頁提供的教學確實有點過時了,己到非更新不可了。為此,整理這篇脫殼入門指導的文章,方便脫殼新手們學習。相對於密碼學算法,脫殼並不難,只要肯花時間,短期內還是比較容易取得成績的。
   但是,不建議那些加解密剛入門,調試一個普通軟件都費勁的朋友來脫殼。至少要等你有一定的調試技能再來學脫殼。也就是說必須掌握這篇文章所講的東西:新手入門必讀(FAQ)  

     在本篇文章沒完成前,不歡迎轉載!我會利用業餘時間不斷更新中,為保持文章連續性,因此鎖帖,如學習過程中有問題請開新帖大家一起交流。這篇入門教學將長期置頂,以代替原來脫殼版塊的FAQ,收集一些常見的脫殼問題。希望更多的朋友一起參與進來,如果想參與進來請告訴我撰寫的主題,我會開通你跟此帖的權限。

註:本人水平有限,因此這篇文章只是負責將新手領進門,更深入技術的請參考論壇中的精華文章。

第一課 PE格式
    要想學脫殼,第一步就得掌握PE格式,PE是Portable Executable File Format(可移植的執行體)簡寫,它是目前Windows平台上的主流可執行文件格式。
Microsoft Visual C++提供的訪客無法瀏覽此圖片或連結,請先 註冊登入會員 裡有PE數據結構的完整定義。
推薦文檔:
    ah007翻譯的「PE文件格式」1.9版  
    qduwg翻譯的PE文件格式   
    Iczelion's 的PE文件格式
    PE結構各字段偏移參考  
    學習PE格式的方法是自己先準備一個十六進制工具,如訪客無法瀏覽此圖片或連結,請先 註冊登入會員 訪客無法瀏覽此圖片或連結,請先 註冊登入會員 ,用這些工具打開一個EXE文件對照著學。強烈推薦你用訪客無法瀏覽此圖片或連結,請先 註冊登入會員
這款工具輔助學習PE格式。PE格式學習的重點是在輸入表(Import Table)這塊。
Stud_PE工具界面:

PE結構圖:


第二課 SEH技術
    結構化異常處理(Structured Exception Handling,SEH)是Windows操作系統處理程序錯誤或異常的技術。SEH是Windows操作系統的一種系統機制,與特定的程序設計語言無關。
    外殼程序裡大量地使用了SEH,如果不瞭解SEH,將會使你跟蹤十分困難。
   
    加密與解密二版菜鳥學習筆記(2) - SEH 結構化異常處理 by ytcswb   
由於訪客無法瀏覽此圖片或連結,請先 註冊登入會員 對SEH處理異常靈活,因此脫殼用Ollydbg會大大提高效率。
附CONTEXT結構環境:

代碼:
-------------------------------------------------------------------------------
引用:
-typedef struct _CONTEXT {/*000*/ DWORD       ContextFlags;

/*004*/ DWORD       Dr0;
/*008*/ DWORD       Dr1;
/*00C*/ DWORD       Dr2;
/*010*/ DWORD       Dr3;
/*014*/ DWORD       Dr6;
/*018*/ DWORD       Dr7;

/*01C*/ FLOATING_SAVE_AREA FloatSave;

/*08C*/ DWORD       SegGs;
/*090*/ DWORD       SegFs;
/*094*/ DWORD       SegEs;
/*098*/ DWORD       SegDs;

/*09C*/ DWORD       Edi;
/*0A0*/ DWORD       Esi;
/*0A4*/ DWORD       Ebx;
/*0A8*/ DWORD       Edx;
/*0AC*/ DWORD       Ecx;
/*0B0*/ DWORD       Eax;

/*0B4*/ DWORD       Ebp;
/*0B8*/ DWORD       Eip;
/*0BC*/ DWORD       SegCs;
/*0C0*/ DWORD       EFlags;
/*0C4*/ DWORD       Esp;
/*0C8*/ DWORD       SegSs;

/*0CC*/     BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

/*2CC*/ } CONTEXT;
--------------------------------------------------------------------------------

第三課 認識殼
1. 什麼是殼?
   在一些計算機軟件裡也有一段專門負責保護軟件不被非法修改或反編譯的程序。它們一般都是先於程序運行,拿到控制權,然後完成它們保護軟件的任務。由於這段程序和自然界的殼在功能上有很多相同的地方,基於命名的規則,就把這樣的程序稱為「殼」了。
   
描述殼的示意圖:


2. 殼的加載過程
    這裡談的加殼工具不是WinZIP、WinRAR等數據壓縮工具,而是談壓縮可執行文件EXE或DLL的工具。加殼過的EXE文件是可執行文件,它可以同正常的EXE文件一樣執行。用戶執行的實際上是外殼程序,這個外殼程序負責把用戶原來的程序在內存中解壓縮,並把控制權交還給解開後的真正程序,這一切工作都是在內存中運行的,整個過程對用戶是透明的。
    殼和病毒在某些方面比較類似,都需要比原程序代碼更早的獲得控制權。殼修改了原程序的執行文件的組織結構,從而能夠比原程序的代碼提前獲得控制權,並且不會影響原程序的正常運行。
    這裡簡單說說一般殼的裝載過程。

   1)獲取殼自己所需要使用的API地址
   如果用PE編輯工具查看加殼後的文件,會發現未加殼的文件和加殼後的文件的輸入表不一樣,加殼後的輸入表一般所引入的DLL和API函數很少,甚至只有Kernel32.dll以及GetProcAddress這個API函數。
   殼實際上還需要其他的API函數來完成它的工作,為了隱藏這些API,它一般只在殼的代碼中用顯式鏈接方式動態加載這些API函數:
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

   2)解密原程序的各個區塊(Section)的數據
   殼出於保護原程序代碼和數據的目的,一般都會加密原程序文件的各個區塊。在程序執行時外殼將會對這些區塊數據解密,以讓程序能正常運行。 殼一般按區塊加密的,那麼在解密時也按區塊解密,並且把解密的區塊數據按照區塊的定義放在合適的內存位置。
   如果加殼時用到了壓縮技術,那麼在解密之前還有一道工序,當然是解壓縮。這也是一些殼的特色之一,比如說原來的程序文件未加殼時1~2M大小,加殼後反而只有幾百K。

   3)重定位
   文件執行時將被映像到指定內存地址中,這個初始內存地址稱為基地址(ImageBase)。當然這只是程序文件中聲明的,程序運行時能夠保證系統一定滿足其要求嗎?
   對於EXE的程序文件來說,Windows系統會盡量滿足。例如某EXE文件的基地址為0x400000,而運行時Windows系統提供給程序的基地址也同樣是0x400000。在這種情況下就不需要進行地址「重定位」了。由於不需要對EXE文件進行「重定位」,所以加殼軟件把原程序文件中用於保存重定位信息的區塊乾脆也刪除了,這樣使得加殼後的文件更加小巧。有些工具提供「Wipe Reloc」的功能,其實就是這個作用。
   不過對於DLL的動態鏈接庫文件來說,Windows系統沒有辦法保證每一次DLL運行時提供相同的基地址。這樣「重定位」就很重要了,此時殼中也需要提供進行「重定位」的代碼,否則原程序中的代碼是無法正常運行起來的。從這點來說,加殼的DLL比加殼的EXE更難修正。

   4)HOOK-API
   程序文件中的輸入表的作用是讓Windows系統在程序運行時提供API的實際地址給程序使用。在程序的第一行代碼執行之前,Windows系統就完成了這個工作。
   殼一般都修改了原程序文件的輸入表,然後自己模仿Windows系統的工作來填充輸入表中相關的數據。在填充過程中,外殼就可填充HOOK-API的代碼的地址,這樣就可間接地獲得程序的控制權。

   5)跳轉到程序原入口點(OEP)
    從這個時候起殼就把控制權交還給原程序了,一般的殼在這裡會有明顯的一個「分界線」。但現在的猛殼己沒這界限了,殼裡有肉,肉裡有殼。


3. 壓縮引擎   
    各類加殼軟件,其壓縮算法一般不是自己實現的,大多是調用其他的壓縮引擎。目前壓縮引擎種類比較多,不同的壓縮引擎有不同特點,如一些對圖像壓縮效果好,一些對數據壓縮效果好。而加殼軟件選擇壓縮引擎有一個特點,在保證壓縮比的條件下,壓縮速度慢些關係不是太大,但解壓速度一定要快,這樣加了殼的EXE文件運行起來速度才不會受太大的影響。例如下面幾個壓縮引擎就能滿足這要求:
1. aPLib壓縮引擎 訪客無法瀏覽此圖片或連結,請先 註冊登入會員 ,這個庫對於低於64K的文件壓縮效果較好,速度較快。
2. JCALG1壓縮引擎,相對於aPlib,JCALG1對於大文件效果好些。
3. LZMA壓縮引擎 訪客無法瀏覽此圖片或連結,請先 註冊登入會員 ,LZMA 是 7-Zip 程序中 7z 格式 的默認壓縮算法,壓縮率很高。

第四課 常見壓縮殼與加密殼
    加殼軟件按照其加殼目的和作用,可分為兩類:一是壓縮(Packers),二是保護(Protectors)。壓縮這類殼主要目的是減小程序體積,如ASPacK、UPX和PECompact等。另一類是保護程序,用上了各種反跟蹤技術保護程序不被調試、脫殼等,其加殼後的體積大小不是其考慮的主要因素,如ASProtect、Armadillo、EXECryptor等。隨著加殼技術的發展,這兩類軟件之間的界線越來越模糊,很多加殼軟件除具有較強的壓縮性能,同時也有了較強的保護性能。
1. ASPacK   主頁:http://www.aspack.com/
   ASPack是款Win32可執行文件壓縮軟件,可壓縮Windows 32位可執行文件(.exe)以及庫文件(.dll、.ocx),文件壓縮比率高達40%~70%。


2. UPX主頁:訪客無法瀏覽此圖片或連結,請先 註冊登入會員
   UPX是一個以命令行方式操作的可執行文件經典免費壓縮程序,壓縮算法自己實現,速度極快,壓縮比極高。(開源)
3. PECompact主頁:訪客無法瀏覽此圖片或連結,請先 註冊登入會員
   PECompact同樣也是一款能壓縮可執行文件的工具(支持EXE、DLL、SCR、OCX等文件)。相比同類軟件,PECompact提供了多種壓縮項目的選擇,用戶可以根據需要確定哪些內部資源需要壓縮處理。同時,該軟件還提供了加解密的插件接口功能。


4. ASProtect主頁:訪客無法瀏覽此圖片或連結,請先 註冊登入會員
   ASProtect是一款非常強大的Windows 32位保護工具,這4、5年來,其一直在更新進步。其開發者是俄國人Alexey Solodovnikov。它擁有壓縮、加密、反跟蹤代碼、反-反彙編代碼、CRC校驗和花指令等保護措施。它使用Blowfish、Twofish、TEA等強勁的加密算法,還用RSA1024作為註冊密鑰生成器。它還通過API鉤子(API hooks,包括Import hooks(GPA hook)和Export hooks)與加殼的程序進行通信。甚至用到了多態變形引擎(Polymorphic Engine)。反Apihook代碼(Anti-Apihook Code)和BPE32的多態變形引擎(BPE32的Polymorphic Engine)。並且ASProtect為軟件開發人員提供SDK,實現加密程序內外結合。


更多與殼有關的描述參考:
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

常見加密殼官方站點
ASProtect           訪客無法瀏覽此圖片或連結,請先 註冊登入會員

ACProtect           訪客無法瀏覽此圖片或連結,請先 註冊登入會員

Armadillo           訪客無法瀏覽此圖片或連結,請先 註冊登入會員

EXECryptor          訪客無法瀏覽此圖片或連結,請先 註冊登入會員

Obsidium            訪客無法瀏覽此圖片或連結,請先 註冊登入會員

PESpin              訪客無法瀏覽此圖片或連結,請先 註冊登入會員

VMProtect           訪客無法瀏覽此圖片或連結,請先 註冊登入會員

Xtreme-Protector    訪客無法瀏覽此圖片或連結,請先 註冊登入會員

Themida/WinLicense  
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

本站的工具欄目:   
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

[ 本帖最後由 蔡逸竹 於 2006-10-14 17:01 編輯 ]

TOP

第五課 文件類型分析
   拿到一個殼,第一步就是用相關工具分析一下是什麼殼,然後就可心中有數地跟蹤分析。文件分析工具有訪客無法瀏覽此圖片或連結,請先 註冊登入會員 訪客無法瀏覽此圖片或連結,請先 註冊登入會員 等。
1.PEiD
   PEiD的GUI界面操作非常方便直觀。它的原理是利用查特徵串搜索來完成識別工作的。各種開發語言都有固定的啟動代碼部分,利用這點就可識別出是何種語言編編譯的。同樣,不同的殼也有其特徵碼,利用這點就可識別是被何種殼所加密。PEiD提供了一個擴展接口文件userdb.txt ,用戶可以自定義一些特徵碼,這樣就可識別出新的文件類型。
   有些外殼程序為了欺騙PEiD等文件識別軟件,會偽造啟動代碼部分,例如將入口代碼改成與Visual C++ 6.0所編程程序入口處類似代碼,即可達到欺騙目的。所以,文件識別工具所給出的結果只是個參考,文件是否被加殼處理過,還得跟蹤分析程序代碼才可得知。 可參考這個文檔瞭解如何偽裝:訪客無法瀏覽此圖片或連結,請先 註冊登入會員 。目前Hying的殼PE-Armor偽裝能力是最強的:


   PEiD分析不出類型的文件就報告是「Nothing found *」,如出現這情況一般都是未知殼或新版的殼。
下面PEiD識別出這個軟件是用Asprotect 1.2x加的殼。


2.FileInfo
   FileInfo(簡稱Fi)另一款不錯的文件檢測工具。Fi運行時是DOS界面,在DOS窗口中運行程序相當不便,建議採用下面的技巧:
1.用鼠標將文件拖到Fi主文件上。
2.將Fi快捷方放進Windows的SendTo文件夾裡.以後要分析某文件,只需右擊「發送到」功能就可打開Fi。   
   FileInfo升級慢,其識別庫不能自定義。而PEiD升級比較頻繁,用戶也可自定義特徵碼,因此PEiD用的比較普遍。
   有時,FileInfo和PEID會報「PE Win GUI」,Win GUI就是Windows圖形用戶界面程序統稱,表明程序可能沒加殼。但不排除也有加殼的可能性,下圖是一個ExeCryptor 2.2x的殼,FileInfo報「*PE Win GUI *section* ??」,其不能識別出來。識別信息中帶了個問號,表明FI對給出的結果不是太肯定。



第六課 尋找OEP
   一般的壓縮殼,如Aspack等都有訪客無法瀏覽此圖片或連結,請先 註冊登入會員 。而加密殼(如ASProtect,Armadillo) 一般很少有脫殼機,必須手工脫殼。手工脫殼一般情況是分三步:一是查找程序的真正入口點(OEP);二是抓取內存映像文件;三是輸入表重建。(當然現在的加密殼複雜些,要考慮更多的東西)
   OEP是Original Entry Point縮寫,即程序加殼前的真正的入口點。
   外殼初始化的現場環境(各寄存器值)與原程序的現場環境是相同的。加殼程序初始化時保存各寄存器的值,外殼執行完畢,會恢復各寄存器內容。其代碼形式一般如下:
    PUSHFD         ; 將標誌寄存器入棧保存
    PUSHAD         ; push eax, ecx, edx, ebx, esp, ebp, esi, edi
    ……           ; 外殼代碼部分
    POPAD          ; pop edi, esi, ebp, esp, ebx, edx, ecx, eax
    POPFD          ; 恢復標誌寄存器
    JMP OEP        ;
OEP: ……          ; 解壓後的程序原代碼
   為了講述方便,本節用UPX加殼的Win98記事本來演示。首先用PEid查看加殼前的記事本:


   PEid顯示Notepad.exe程序是用Microsoft Visual C++ 6.0編譯的,接下來用UPX來加殼,方法是開個DOS窗口,用命令upx notepad.exe。如下圖所示:

   這時再用PEid查看加殼的文件,PEid會給出如下信息:UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo

   UPX的殼可以用UPX.exe自身來脫,命令是:upx -d 文件名 。一些變種的UPX殼用UPX.EXE自身脫不了,這時可以試試訪客無法瀏覽此圖片或連結,請先 註冊登入會員 這款工具。
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

   脫殼前建議用PE工具訪客無法瀏覽此圖片或連結,請先 註冊登入會員
打開目標文件查看一下區塊,以盡可能地多瞭解一些信息,對脫殼有幫助,如下圖:


1.根據跨段指令尋找OEP
   推薦用Ollydbg來調試脫殼,比SoftICE和TRW2000方便多了。運行Ollydbg,點擊菜單「選項/調試設置」,將第一次暫停設在WinMain函數上。再用Ollydbg打開實例notepad.upx.exe就可中斷在外殼的入口點處了:


上圖相關代碼如下:
0040E8C0 >  60              pushad     //一開始Ollydbg就會中斷這行,這個就是外殼的入口點,注意這個pushad指令
    絕大多數加殼程序在被加密的程序中加上一個或多個段,所以依據跨段的轉移指令(JMP)就可找到真正的入口點,此時就會有POPAD/POPFD指令出現。UPX 用了一次跨段的轉移指令(JMP),在跳到OEP處會看到虛擬地址的值有一個突變,此時就能確定OEP了。
    UPX殼比較簡單,大家不必要跟蹤去找這個跨段的轉移指令,中斷WinMain後,只需要在Ollydbg裡往下翻屏,就會發現這個跨段轉移指令:


上圖相關代碼如下:
0040EA0E    61              popad            //注意這裡的popad指令,和開始的pushad對應            
0040EA0F  - E9 B826FFFF     jmp     004010CC
//這裡跳到OEP,將光標移到這,按F4執行到這行
   這一句
0040EA0F   jmp  004010CC
就是跳到OEP的指令,執行到這,UPX外殼己將程序解壓完畢,並模擬Windows加載器的將原始程序加載到內存,004010CC 就是映射到內存目標程序的入口點,此時就可抓取內存映像文件了。
2.根據堆棧平衡原理找OEP
   這個堆棧平衡原理其找OEP原理這篇文檔描述的比較詳細:
訪客無法瀏覽此圖片或連結,請先 註冊登入會員
操作方法:多數殼在運行到OEP的時候ESP=0012FFC4,這就是說程序的第一句是對0012FFC0進行寫入操作,只要在0012FFC0下硬件寫入斷點(命令行裡鍵入HW 12FFC0),我們就能停在OEP的第二句處。
   用OllyDBG重新加載實例程序notepad.upx.exe,在命令行下硬件寫斷點:


   按F9執行程序,就會中斷在OEP第二行:


此時如果將光標向上移,會發現第一句代碼變亂了:
004010C7    000D 0A000055  add     [5500000A], cl
004010CD    8BEC           mov     ebp, esp
這是因為Ollydbg將數據當彙編代碼來分析了,你可以按 Ctrl+ALT+向上光標鍵 將當前顯示的代碼向上滾動一個字節就可看到正確的彙編代碼了:
004010CC    55              push    ebp
004010CD    8BEC            mov     ebp, esp
   //中斷在這行
004010CF    83EC 44         sub     esp, 44
004010D2    56              push    esi
004010D3    FF15 E4634000   call    [4063E4]                         ; kernel32.GetCommandLineA
中斷後,別忘點擊菜單「調試/硬件斷點/」打開硬件斷點面板,將剛才的硬件斷點刪除。


小知識:

訪客無法瀏覽此圖片或連結,請先 註冊登入會員

3.根據編譯語言特點找OEP
   各類語言編譯的文件入口點都有一些規律,可以這利用這點來尋找入口點。
1)Delphi程序
執行程序,用LordPE(或Prodump)選dump(full)脫殼,存為dump.exe。接著用Hex Workshop打開dump.exe,搜索文本「runtime」,搜到後,向前查找離「runtime」最近的十六進制數字「55 8B EC」,數字所在的地址就是程序的OEP。
2)Visual C程序
可以利用Visual C啟動部分的幾個函數GetCommandLineA(W)、GetVersion、GetModuleHandleA(W)、GetStartupInfoA(W) 等來定位程序的OEP。
   常見的各類編譯語言的入口彙編代碼都要熟悉,因為一些加密強殼會偷OEP處的代碼到殼裡,一般情況各編譯語言入口代碼都相同,到時只需要直接引用相關程序的入口代碼,這給我們恢復代碼帶來方便。

4.用內存斷點找OEPAuthor:Lenus
From:   
訪客無法瀏覽此圖片或連結,請先 註冊登入會員 &
訪客無法瀏覽此圖片或連結,請先 註冊登入會員
E-mail︴email]GLenus_M@163.com[/email]
--------------------------------------------------
1.前言
   發現論壇中很多兄弟在詢問:什麼是二次內存斷點,三次內存斷點。還有很多人對內存斷點的原理不是很明白。其實只要懂得殼是如何解壓代碼的,那麼就完全可以按自己的喜歡來下斷。
   
   本文要解決的問題是:
   1.什麼是內存斷點?
   2.如何在尋找OEP時使用內存斷點。
   3.內存斷點的局限性。
   
2.內存斷點尋找OEP的原理
  i.首先,在OD中內存斷點,硬件斷點和普通斷點(F2下斷)是有本質區別的。硬件斷點等效與SoftICE命令bpm,他的中斷要用到DR0-DR7的調試寄存器,也就是說OD通過這些DR0-DR7的調試寄存器來判斷是否斷下。
    普通斷點(F2下斷)等效於bpx,他是在所執行的的代碼的當前地址的一個字節修改為CC(int3)。當程序運行到int3的時候就會產生一個異常,而這個異常將交給OD處理,把這個異常的regEIP-1以後就正好停在了需要的中斷的地方(這個根據系統不同會不一樣),同時OD在把上面的int3修改回原來的代碼。
  而內存斷點基本上使用的是對代碼使用的保護屬性來實現中斷。
  內存斷點分為:內存訪問斷點,內存寫入斷點。
  我們知道,在程序運行的時候會有3種基本的狀態產生:讀取,寫入,執行。
004AE242     A1 00104000       mov eax,dword ptr ds:[004AE24C]            //004AE24C處的內存讀取
004AE247     A3 00104000       mov dword ptr ds:[004AE24C],eax            //004AE24C處的內存寫入
004AE24C     83C0 01           add eax,1                                  //004AE24C處的內存執行
  
  那麼我們應該如何中斷在上面的幾行呢?
  1.當我們對004AE24C下內存訪問斷點的時候,可以中斷在004AE242也可以中斷在004AE247。
  2.當我們對004AE24C下內存寫入斷點的時候,只能中斷在004AE247。
  3.當我們對004AE24C下內存訪問斷點的時候,能中斷在004AE24C。
  到這裡你可能不明白了,為什麼內存訪問斷點能中斷在004AE247這一句對004AE24C的寫入,而且還能中斷在004AE24C的執行呢?
  其實很簡單,我們只要仔細體會一下「內存訪問」這四個字的含義遍可以知道,當我們對004AE24C進行讀取的時候需要「訪問」他吧,當我對004AE24C進行寫入的時候也需要「訪問」他吧!!當然我們要執行內存地址004AE24C的代碼的時候也是還是要「訪問」他的!
  所以我們不難得出下面的結論:
  1.內存寫入中斷的地方,一定是也可以用內存訪問中斷。
  2.內存執行的地方,也可以用內存訪問中斷。
  如果這時你認為,那麼內存寫入豈不是沒用了。呵呵~那我要告訴你當然不是,如果你想快速的準確的定位到004AE247這一行的時候,那麼他就大有作用了!
  總結一下:內存斷點不修改改原代碼,不會像普通斷點那樣因為修改代碼被程序校驗而導致中斷失敗;對於區段的訪問只是區域大了一點,其原理和上面分析的三行代碼是一樣的。
  ii.如何使用內存斷點來尋找OEP呢?
  要回答這個問題首先要回答這一個問題:殼是如何解壓代碼的?
  正如我們知道的,殼如果要把原來加密或壓縮的代碼運行起來就必須要解壓和解密原來的代碼。而這一個過程我們難道不能將他看做是對代碼段(code段)的寫入嗎?好了,解壓完畢了。我們要從殼代碼的區段JMP到原來的代碼段的時候,難道不正是對代碼段(code段)的執行嗎?
  理清了上面的關係就好辦了,那麼如果載入OD後,我們直接對code段下內存訪問斷點的時候,一定會中斷在殼對code段的寫入的代碼的上面,就像上面的004AE247的這一行。而如果當他把code段的代碼全部解壓解密完畢了以後,JMP到OEP的時候,我們是不是還可以停在OEP的代碼上面呢?而且每按下F9都會中斷,因為這時code段在執行中哦!
  相信很多人到這裡已經明白了,為什麼在教程中到達了某一個時候,某一行的時候。牛人們就叫我們對code段下內存訪問斷點了吧。
  而如果你還要繼續問我為什麼一定要到那個地方才可以下斷呢?我難道不可以一開始就下斷嗎?
  正入我上面所說的,如果你在前面下斷很可能殼對code段還沒解壓完畢呢,這時如果你不停的按F9,你將會看到OD的下方不斷的在提示你「對401000寫入中斷」 「對401002寫入中斷」「對401004寫入中斷」.......如果你不介意按F9到他把正個code段寫完的話,我除了同情你的「F9」以外,沒什麼其他的意見!
  
  那麼我們就沒有別更快一點的辦法了嗎?
  有的!那就是我們呼之欲出的兩次內存斷點辦法。
  怎麼理解兩次內存斷點呢?
  讓我來做一個假設吧,假設我是一個殼的作者。一個EXE文件的有code段,data段,rsrc段.....依次排列在你的內存空間中,那麼我會怎麼解碼呢?呵呵~我比較笨一點,我會先將code段解碼,然後再將data段解壓,接著是rsrc段......那麼聰明的你不難發現,只要你在data斷或者rsrc段下內存訪問斷點,那麼中斷的時候code段就已經解壓完畢了。這時我們再對code段下內存反問斷點,不就可以到達OEP了嗎?
  這裡注意上面雖然下了兩次內存訪問斷點,但是本質是不一樣的,目的也是不一樣的。
  1.對data段下內存訪問斷點而中斷是因為內存寫入中斷,目的是斷在對對data段的解壓時,這時殼要對data段寫數據,但是code段已經解壓完畢。
  2.對code段下內存訪問斷點而中斷是因為內存執行中斷,目的當然就是尋找OEP了。
  總結一下:如果我們知道殼在什麼地方對code段解壓完畢我們就可以使用內存斷點,找到OEP。如果不知道,那麼我們就依靠2次內存斷點去找,如果還不行就用多次內存斷點。總之明白了原理在多次的內存斷點其實都一樣。從這個過程中我們瞭解的是殼在對區段解碼的順序!
  iii.實戰
  說了這麼多,我想大家都越越欲試了吧。
  好吧,來弄一個猛殼怎麼樣:
註:本節實例有些難度,不適合新手練習,新手可以跳過這個實例的學習,等找到合適的實例會補充上來的


訪客無法瀏覽此圖片或連結,請先 註冊登入會員

  這個殼是一個hying的舊版,我們用他來實驗一下我們內存斷點法。
  
  OD載入以後來到這裡
0040D000 u>  56                    push esi              //這裡
0040D001     52                    push edx
0040D002     51                    push ecx
0040D003     53                    push ebx
0040D004     55                    push ebp
0040D005     E8 15010000           call unpackme.0040D11F
  根據跟過一次的經驗我們將先設置,除int3異常以外忽略其他異常,SHIFT+F9


003725B1    64:8925 0000000>       mov     fs:[0], esp
003725B8    CC                     int3
003725B9     90                    nop                    //到這裡
003725BA     8BCD                  mov ecx,ebp
  然後再設置除「除零」異常外,忽略其他異常。SHIFT+F9


00372660     F7F3                  div ebx                //到這裡
00372662     90                    nop
  
  下面是很多的單步異常,太麻煩我們不管他,現在開始用內存斷點的方法(記得將所有異常忽略)。
對code段下內存訪問斷點,希望他已經解壓完畢。方法是按ALT+M鍵打開內存窗口,在.code段按F2設斷:


SHIFT+F9執行:
0040D19D     A4                    movs byte ptr es:[edi],byte ptr ds:[esi]         //還沒解完呢
0040D19E     B3 02                 mov bl,2
對data段下內存「寫入」斷點,試試看他是不是要寫data段。
00372712     F3:A4                 rep movs byte ptr es:[edi],byte ptr ds:[esi]      //斷到這裡
00372714     5E                    pop esi
下面再對code段下內存訪問斷點。F9
00372855     8907                  mov dword ptr ds:[edi],eax                         ; SHELL32.DragFinish  //這裡是對IAT加密
的地方了!!!
00372857     5A                    pop edx
00372858     0FB642 FF             movzx eax,byte ptr ds:[edx-1]
0037285C     03D0                  add edx,eax
0037285E     42                    inc edx
0037285F     83C7 04               add edi,4
00372862     59                    pop ecx
00372863   ^ E2 A9                 loopd short 0037280E
00372865   ^ E9 63FFFFFF           jmp 003727CD
0037286A     8BB5 93060000         mov esi,dword ptr ss:[ebp+693]                      //到這裡下斷F2
現在如果再對data下訪問斷點已經是沒用了。這時應該格外的小心。
我們現在就想既然這一段是對code解碼的,那麼我們就繞過他吧!
到0037286A下斷F2,然後清除內存斷點!!!!
F9以後停在這裡,繼續對code下內存訪問斷點。
看看左下角還在解碼,哎~真是麻煩!
003728E1    /EB 1D                 jmp short 00372900
003728E3    |25 FFFFFF7F           and eax,7FFFFFFF
003728E8    |0385 83060000         add eax,dword ptr ss:[ebp+683]
003728EE    |2B85 8F060000         sub eax,dword ptr ss:[ebp+68F]
003728F4    |8BDE                  mov ebx,esi
003728F6    |2BD8                  sub ebx,eax
003728F8    |8958 FC               mov dword ptr ds:[eax-4],ebx                     //停在這裡
003728FB    |83C7 08               add edi,8
003728FE   ^|EB DB                 jmp short 003728DB
00372900    \64:FF35 30000000      push dword ptr fs:[30]                          //清除內存斷點以後到這裡下斷,F9
又是一段解碼的代碼,再次使用上面的辦法手動跳出去。
現在繼續對code段下內存訪問斷點!!F9以後到達這裡。
004010CC     FFD7                  call edi                                           ; unpackme.004010CE   //OEP哦
004010CE     58                    pop eax
004010CF     83EC 44               sub esp,44
004010D2     56                    push esi
004010D3     90                    nop
004010D4     E8 B518F7FF           call 0037298E
004010D9     8BF0                  mov esi,eax
呵呵~雖然不是我們熟悉的OEP,但是地址是沒錯了,況且根據我們的步驟,我可以很肯定的說這是code段的第一次「執行」中斷!
所以這就是OEP了。
總結一下:當我們在尋找OEP的時候,要多次對code下斷「賭」一「賭」他解壓完畢,如果不是就對別的段試試~如果程序跑飛了,那就沒辦法了,重來唄~其實說起來要賭的是:當data段,idata段,rsrc段擺在你的面前,你會好好「珍惜」那個段,不過還好上天還會給我們從來一次的機會(ctrl+F2 ^_^),那麼我們會對那個不會跑飛的段說3個字----「先斷你」如果非要在上面加一個次數,我希望是「一次內存斷點就好了」
  vi.下面來討論一下內存斷點的局限性問題。
  是不是什麼殼都可以用內存中斷啊?
  不是每個都可以的,一些像UPX和ASPACK就不行。
  為什麼?
  呵呵~follew me!
  情況1.
  我們來看看UPX的殼
  
首先,他的殼代碼在UPX1段。
這裡是他要跳到OEP的地方
0040ED4F    /77 11             ja short NOTEPAD_.0040ED62            
0040ED51    |01C3              add ebx,eax
0040ED53    |8B03              mov eax,dword ptr ds:[ebx]
0040ED55    |86C4              xchg ah,al
0040ED57    |C1C0 10           rol eax,10                           //在解碼
0040ED5A    |86C4              xchg ah,al
0040ED5C    |01F0              add eax,esi
0040ED5E    |8903              mov dword ptr ds:[ebx],eax
0040ED60   ^|EB E2             jmp short NOTEPAD_.0040ED44
0040ED62    \24 0F             and al,0F
0040ED64     C1E0 10           shl eax,10
0040ED67     66:8B07           mov ax,word ptr ds:[edi]
0040ED6A     83C7 02           add edi,2
0040ED6D   ^ EB E2             jmp short NOTEPAD_.0040ED51        //回跳解碼
0040ED6F     61                popad
0040ED70   - E9 5723FFFF       jmp NOTEPAD_.004010CC             //跳到OEP
我們看到他在對code段解壓完畢的時候馬上就JMP到OEP去了,那麼我們根本就來不及使用內存斷點的辦法。
你可能說,我可以在
0040ED6F     61                popad //這一句下段然後使用啊
呵呵~~當然可以,不過你把花在下內存斷點的時間,多按下幾次F8不更好?!
也就是說當一個殼如果他在JMP 到OEP前的一行代碼仍在都在對code段解壓,那麼我們就不能再使用這種辦法了!
或者說我們沒必要使用內存斷點更貼切一點!
  情況2.
  對於一些在OEP處有stolen code的代碼
  我們來看看一個OEP
0049E2F4 u>  55                push ebp                               //OEP
0049E2F5     8BEC              mov ebp,esp
0049E2F7     83C4 F4           add esp,-0C
0049E2FA     B8 BCE04900       mov eax,unpack.0049E0BC
0049E2FF     E8 048CF6FF       call unpack.00406F08                   //這裡調用子程序
0049E304     A1 B8FE4900       mov eax,dword ptr ds:[49FEB8]
0049E309     50                push eax
0049E30A     6A 00             push 0
0049E30C     68 1F000F00       push 0F001F
0049E311     E8 E68EF6FF       call   //API
0049E316     A3 60194A00       mov dword ptr ds:[4A1960],eax
0049E31B     833D 60194A00 00  cmp dword ptr ds:[4A1960],0
這個軟件在被PESPIN加殼了以後這些全被偷掉了!
也就是說,殼在模擬OEP代碼的時候必然會執行
0049E2FF     E8 048CF6FF       call unpack.00406F08  //這一步
而這個地方是call向code段的。如果我們使用內存訪問斷點,那麼就停在這個子程序的地方
00406F08     50                push eax                                        //會停在這裡
00406F09     6A 00             push 0
00406F0B     E8 F8FEFFFF       call
00406F10     BA 04F14900       mov edx,unpack.0049F104
00406F15     52                push edx
這裡既不是處理stolen code的地方,也不是FOEP的地方。這就會對我們的判斷產生誤導。
當然你可以alt+F9返回到殼處理stolen的地方,然後用內存斷點,或者按幾下F8到達FOEP處,但試問如果你拿到一個未知的殼的時候又怎麼知道應該這麼處理呢?
還有其他一些情況留給大家總結吧!
在下的磚已拋出,各位的玉不久矣。
--------------------------------------------------
3.總結
      好了說了很多,大家應該對內存斷點的辦法有了全面的瞭解,如果瞭解了內存斷點的原理就不難明白他的使用方法,不難明白為什麼有寫殼不能使用內存斷點的辦法,其實任何的一種辦法都需要經驗的積累。相信如果大家在回答開篇的3個問題,已經不難了。
      大家可以結合原理再好好的體會一下《手動脫殼進階第八篇Skvp1.32》這篇文章。

TOP

第七課 Dump內存映像
    外殼程序解壓還原後就會跳到OEP處執行,此時內存映像文件是己解壓的程序。這時就可抓取內存映像文件了(該過程稱為Dump)。當然不一定非要在程序原入口點抓取,只要能保證內存映像文件是己還原的就行了。
    有關Dump技術的原理大家可以參考:
訪客無法瀏覽此圖片或連結,請先 註冊登入會員

    繼續上一個實例notepad.upx.exe,到OEP後就可以Dump取內存映像文件:
004010CC    55             push    ebp
004010CD    8BEC           mov     ebp, esp
004010CF    83EC 44        sub     esp, 44
   運行LordPE.EXE,點擊Options,默認選項如下:

   默認選上「Full dump:paste header from disk」,PE頭的信息直接從磁盤文件獲得。設置好後,在LordPE的進程窗口選擇notepad.upx.exe,點擊右鍵,執行「dump full」菜單命令。如圖:

   將內存抓取的文件另存為dumped.exe,此時程序還不能運行,接下來就是重建輸入表。

第八課 重建輸入表
   在脫殼中輸入表處理是很關鍵的一個環節,因此要求脫殼者對PE格式中的輸入表概念非常清楚。在磁盤文件中,PE文件的輸入表結構如下圖所示:

                         圖8.1 磁盤文件中的輸入表
   PE文件運行時,Windows系統加載器首先搜索OriginalFirstThunk,如果存在,裝載程序迭代搜索數組中的每個指針,找到每個IMAGE_IMPORT_BY_NAME結構所指向的輸入函數的地址,然後用函數入口地址來替代由FirstThunk指向的 IMAGE_THUNK_DATA 數組裡的元素值(即用真實的函數地址填充到IAT裡)。因當PE文件裝載內存後準備執行時,上圖己轉換成這種情況了:

                         圖8.2 PE文件裝載內存後的輸入表
   此時輸入表中其它部分就不重要了,程序依靠IAT提供的函數地址就可正常運行(圖8.2 紅圈部分)。如果程序加殼了,那殼自己模仿Windows裝載器的工作來填充IAT中相關的數據,此時內存中就一張IAT表,輸入表的其他部分是不存的(當然不是絕對的,也有不少殼,如Aspack等,內存中會出現完整的輸入表結構),如圖8.3所示。
                       

             圖8.3 外殼加載程序後的內部IAT
   輸入表重建就是根據圖8.3這張IAT恢復整個輸入表的結構(即圖8.1這個結構),ImpREC這款工具就是這個功能。
   一些壓縮殼,填充IAT過程中沒做什麼手腳,用ImpREC工具可以直接重建輸入表。而一些加密殼為了防止輸入表被還原,就在IAT加密上大作文章,此時殼填充IAT裡的不是實際的API地址,而是填充殼中用來HOOK-API的外殼代碼的地址。這樣殼中的代碼一旦完成了加載工作,在進入原程序的代碼之後,仍然能夠間接地獲得程序的控制權。 因為程序總是需要與系統打交道,與系統交道的途徑是API,而API的地址已經替換成了殼的HOOK-API的地址,那程序每一次與系統打交道,都會讓殼的代碼獲得一次控制權,這樣殼可以進行反跟蹤繼續保護軟件,同時也可完成某些特殊的任務。所以重建輸入表的關鍵是獲得沒加密的IAT ,一般的做法是跟蹤加殼程序對IAT處理過程,修改相關指令,不讓外殼加密IAT。
   UPX、ASPack等加殼保護的殼沒加密IAT,而ASProtect、tElock等加密保護的殼都對IAT進行了加密處理。這篇先來簡單的,即UPX殼。用OD打開上面的notepad.upx.exe實例,運行到OEP。(實際跟蹤過程中,不一定要到OEP,只要外殼處理完IAT就可)然後如下操作:
1) 運行ImportREC,在下拉列表框中選擇notepad.upx.exe進程,如圖:

2) 上面己得知notepad.upx.exe的OEP地址是4010CC,則在左下角OEP處填入OEP的RVA值,這裡填上10CC。點擊「IAT AutoSearch」按鈕,讓其自動檢測IAT偏移和大小,如出現下圖表示ImportREC自己找到IAT的地址與大小了,即IAT地址:000062E0,大小248。

如果ImportREC沒找到IAT偏移,則必須手工填入IAT偏移和大小(IAT偏移手動獲得以後再講述)。
3) 點擊「Get Import」按鈕,讓其分析IAT結構得到基本信息,如下圖所示:

4)如發現某個DLL顯示"valid :NO" ,按"Show Invalids"按鈕將分析所有的無效信息,在Imported Function Found欄中點擊鼠標右鍵,選擇"Trace Level1 (Disasm)",再按"Show Invalids"按鈕。如果成功,可以看到所有的DLL都為"valid:YES"字樣;
5)再次刷新"Show Invalids"按鈕查看結果,如仍有無效的地址,繼續手動用右鍵的Level 2或3修復;
6)如還是出錯,可以利用"Invalidate function(s)"、"Delete thunk(s)"、編輯Import表(雙擊函數)等功能手動修復。
7)開始修復已脫殼的程序。選擇Add new section (缺省是選上的) 來為Dump出來的文件加一個Section(雖然文件比較大,但避免了許多不必要的麻煩) 。
8)單擊"Fix Dump"按鈕,並選擇剛在前面己Dump出來的文件。如修復的文件名是"Dump.exe",它將創建一個"Dump_.exe",此外OEP也被修正。
    經過這些步驟,這個UPX殼己成功脫掉。此時再用PEID查一下脫殼後的程序dumped_.exe,會顯示是「Microsoft Visual C++ 6.0 SPx Method 1」,如下圖所示:

再用LordPE查看脫殼後的輸入表:

從上圖可以看出,輸入表己正確修復,此時脫殼後的文件己能成功運行了。

第九課 手動確定IAT的地址與大小
    在第八課中講到,點擊ImportREC的「IAT AutoSearch」按鈕,一般情況下ImportREC可以自動識別出IAT地址與大小。但如果不能自動識別,就必須手動確定IAT地址與大小,然後將IAT的RVA與Size填進ImportREC,點擊「Get Import」按鈕就可得到輸入表。
    還是用上一節實例演示,用OD打開notepad.upx.exe,來到OEP處:

    隨便找一個API函數調用語句,如:
004010D3    FF15 E4634000   call    [4063E4]          ; kernel32.GetCommandLineA
   其中地址4063E4就是IAT中的一部分,在數據窗口下命令:D 4063E4,顯示如下:

  上圖每一組數據都是指向一個API函數,如 8D 2C 81 7C 就是地址:7C812C8D,在OD裡按Ctrl+G,輸入7C812C8D跳到這個地址就會發現是kernel32.GetCommandLineA函數:


   IAT是一塊連續排列的數據,因此在數據窗口向上翻屏,直到出現00數據,尋找IAT起始地址:

然後向下翻屏,尋找IAT結束地址:

為了直觀些,你也可以這樣讓數據窗口直接顯示這些API函數,以確定IAT是否正確,在數據窗口點擊鼠標右鍵:

調整顯示格式後的數據窗口:

這樣就直觀了,IAT中每組數據指向一個API函數,各DLL之間是以000000分開的。
因此IAT範圍:0x4062E4~0x406524 ,大小為0x406524-0x4062E4=0x240
如果IAT加密了,此時IAT中的地址不是指向系統DLL中的API函數了,可能指向外殼。這就十分有必要找到外殼處理IAT的代碼了,前面己說過,外殼加載時,會模擬Windows加載器,向IAT裡填充當前操作系統API函數的實際地址。所以,在IAT裡設個內存寫斷點,就可中斷到這段代碼處。
重新加載notepad.upx.exe,在IAT某個地址下內存寫斷點,這裡選擇0x4062E4這個地址設內存寫斷點,先在數據窗口下命令:D 4062E4

然後選擇一個地址,點擊鼠標右鍵,下「內存寫斷點」。

此時只要有數據寫入4062E4地址處,OD就會中斷,按F9運行OD,會中斷這裡:
0040E96D    > /8A02          mov     al, [edx]
0040E96F    . |42            inc     edx
0040E970    . |8807          mov     [edi], al
0040E972    . |47            inc     edi
0040E973    . |49            dec     ecx
0040E974    .^\75 F7         jnz     short 0040E96D
這段還不是處理IAT,按F9繼續執行程序,會中斷這裡:
0040E9E9    > /8A07          mov     al, [edi]
0040E9EB    . |47            inc     edi
0040E9EC    . |08C0          or      al, al
0040E9EE    .^|74 DC         je      short 0040E9CC
0040E9F0    . |89F9          mov     ecx, edi
0040E9F2    . |57            push    edi                        // 函數名字符串  
0040E9F3    . |48            dec     eax
0040E9F4    .  F2:AE         repne   scas byte ptr es:[edi]
0040E9F6    . |55            push    ebp                        // DLL模塊句柄
0040E9F7    .  FF96 A4EC0000 call    [esi+ECA4]                 ;  kernel32.GetProcAddress
0040E9FD    . |09C0          or      eax, eax
0040E9FF    . |74 07         je      short 0040EA08
0040EA01    . |8903          mov     [ebx], eax                // EBX指向IAT,將取得的API地址填充進IAT
0040EA03    . |83C3 04       add     ebx, 4                    // 指向下一個地址
0040EA06    .^\EB E1         jmp     short 0040E9E9
0040EA08    >  FF96 A8EC0000 call    [esi+ECA8]
上面這段就是UPX外殼填充IAT的全過程,感興趣的,動態跟蹤一下就明白了。這裡用GetProcAddress函數獲得函數地址:
FARPROC GetProcAddress(
  HMODULE hModule,    // DLL模塊句柄
  LPCSTR lpProcName   // 函數名
);

TOP

趕快來下載來看,感謝大大的分享 。 ...............

TOP

assembly, win32人才

有人才懂assembly, win32。 工具:PE explorer, OllyDBG, IAT, OEP 等等...?歡迎長期合作關係。  top.formosa@gmail.com

TOP

好棒的資料庫

TOP

發新話題

本站所有圖文均屬網友發表,僅代表作者的觀點與本站無關,如有侵權請通知版主會盡快刪除。