什麼是加殼?#
本章演示了對 upx 加殼程序進行脫殼。
加殼是指通過一種壓縮或者加密的手段將程序的可執行代碼隱藏起來,避免被輕易的逆向。加殼會在程序中加入額外的區段(STUB,存根),在程序開始運行後,將加密的文件進行解密並保存到內存中其他區段,或者創建原程序中的區段,然後跳轉到解密後的代碼執行。
大部分通過破壞 IAT(import table)也就是導入函數表,以及文件頭(HEADER)來保護文件。它們會加入反調試代碼來避免被脫殼出原始文件。
通過 die 查看是否加殼。
上圖可以知道是 upx 3.91 版本加殼,程序是 32 位,i386 架構。
加載加殼文件#
加載加殼文件時,取消勾選創建輸入段
,勾選手動加載
。
點擊 ok 後,彈出如下窗口,點擊確定。
加殼後程序的入口
原始程序入口
加殼後程序的入口,地址是 0x409BE0,而原始文件的地址是 0x401000。
文件和內存佔用#
對比這 2 個文件的區段,在加殼文件的文件頭下面有個 upx0 的區段,佔用的內存比原始文件中的其他區段要大。
原始文件
加殼文件
加殼文件的 upx0 區塊結束於0x409000
,而在原始文件 header 以下區段從0x401000
到0x408200
,當一個程序執行時,它在硬盤上可能只佔用 1k,但在內存中可能佔用 20k 或者更大。
如上圖,在原始文件 CODE 區段的起始地址是0x401000
,區段文件的大小 (Section size in file) 為 0x600 字節,而內存大小 (Virtual size) 佔用 0x1000 字節。
轉到加殼文件中,如上圖,upx0 區段的起點是0x401000
,upx0 區段在硬盤的大小為 0,而內存佔用卻是 0x8000 字節,程序在這裡佔用了足夠大的空間來保存原始的程序代碼,然後跳轉過來執行。
加殼文件 0x401000 處的跳轉
0x401000 前置的 dword_表示數據類型為 DWORD,"?" 號表示只佔用了內存位置而未保存任何內容,dup 表示 0xc00 個 dword,也就是 0x3000 字節。0x404000 同樣也佔用了 0x1400 字節。
那麼總共就是 0x8000 個字節用於存儲原始代碼的內容。
如下圖,在 0x401000 處,按 x 鍵,可以看到此處有兩個引用(稍後回來看這部分內容)。
可執行代碼的引用
upx1 區段文件佔用是 0xe00,內存佔用是 0x1000。
upx1 區段的文件及內存佔用
可能程序使用了一些簡單的加密隱藏了原始代碼,對於這個區段的起點 0x409000 有幾處引用。
0x409000 的引用
有一處引用來自(下方,down)可執行部分,點擊跳轉到該處。
程序入口
stub 和 oep#
上圖程序入口之後的 stub 中,ESI 寄存器傳入 0x409000 這個地址,如下圖所示,可執行代碼在原始文件已加殼代碼的下方,同屬於 upx1 區塊,所以在 upx1 區塊中,存在原始文件加密後保存的內容以及 0x409be0 之後的 stub 代碼。
跟蹤到的可執行代碼
下圖中,可以發現,程序從 0x409000 處開始讀取內容,通過某種運算之後再保存到0x401000
(EDI=ESI-0x8000
)。程序讀取 ESI 指向的內容作為來源,然後執行操作後,再存儲到 EDI 指向的內容,恢復成原始代碼。
回到 upx0 區段,在 upx0 區段處,有一處引用
0x401000 處的引用
下圖中有一處無條件跳轉到 0x401000 處,也就是上圖中的 0x401000 處的引用。
jmp near
是直接向之後的地址數跳轉的指令,那麼這裡執行完 stub 並且生成原始代碼後,程序會跳轉到 0x401000 處 (OEP,original entry point),也就是原始程序的入口(程序最開始執行的地方),相應的存根入口 (stub entry point) 是 0x409be0。
之後原始程序入口直接叫做ORIGINAL ENTRY POINT
或OEP
。如果是一個加殼之後的程序,無法知道它的具體位置,而在此程序中,程序實有 OEP 的,OEP 就是 0x401000。
尋找 OEP#
大部分情況下,是無法獲取原程序的,所以無法直接獲得加殼程序的 OEP 地址。那麼,接下來介紹如何尋找 OEP。
當 STUB 完成解密操作並且生成原始代碼之後,會跳轉執行程序。一般來說,在寫入原始代碼的區塊執行的第一條指令就是 OEP。
在進入 OEP 之前設置斷點,查看執行到這裡之前是否已經生成了原始程序。
在跳轉處設置斷點
選擇 local windows debugger 調試,然後開始調試,運行到斷點處。
運行到斷點處
程序運行到斷點處跳轉,按 F8 單步運行。
彈出警告信息 upx0 區段原先被解析為數據,點擊是,將它解析為代碼。
運行到 OEP
現在程序已經解密出原始代碼並跳轉執行,這裡的代碼和原始程序中 0x401000 處的代碼非常類似。但是由於沒有定義為函數 (loc_401000),所以無法切換到圖形視圖。不過這也可以自動實現。
在 IDA 界面左下角有一個隱藏菜單,右鍵選擇 “重新分析程序”,現在回到 0x401000 處,顯示 sub_401000 表示這是一個函數,按空格則可以切換回圖形視圖。
重新分析後的代碼
sub_401000
通過執行斷點尋找 OEP#
另一種尋找 OEP 的方法,找到第一個區段第一條執行的指令,這種方法有時候會成功。
在調試器中啟動加殼後的程序並且在入口處暫停。
Stub entry point
轉到一個區段的起點,也就是 0x401000
按 F2 設置斷點,並且設置執行時中斷,而不是讀取或寫入的時候,否則會在解密時讀取密碼或者寫入原始代碼時中斷,由於不知道具體的位置,作者設置了覆蓋整個區段的斷點(0x8000 字節)。
設置完成後所有的指令都變紅了。
對整個區段設置斷點
然後打開菜單欄-調試器-斷點-斷點列表
,禁用其他斷點。
然後運行程序。
觸發斷點
當這種方法奏效時,發現 OEP 就是 0x401000,禁用這個斷點。
OEP
重新分析之後,0x401000 識別為函數。
目前為止,介紹了 2 種尋找 OEP 的方法,創建解密代碼的內存快照。接下來要做的就是 DUMP(轉存)以及重建 IAT 來獲得沒有加殼並且能夠運行的程序。