puahad 和 popad#
加壳程序:unpackme.aspack.2.2
,可從互聯網上搜索。
ida 打開,手動加載程序,取消勾選創建輸入段。
加壳程序入口
上圖中,第一條指令是pushad
,pushad
會把所有通用寄存器的值傳到堆棧上。
pushad
按如下順序將所有寄存器的值保存到堆棧上。
pushad 傳值順序
popad
是和pushad
相反的操作,它將堆棧中的值傳出,按如下順序保存到寄存器。
在大部分簡單的殼中,使用了pushad
指令將寄存器的初始狀態保存,然後再跳轉到OEP
執行內存中的原始代碼之前使用POPAD
恢復寄存器的初始狀態。
根據這條規律,可以通過pushad-popad法
來輕鬆找到OEP
。
使用 idapython 進行調試#
什麼是pushad-popad法
呢?
使用調試器來運行程序,運行調試器有兩種方式,第一種通過菜單欄-調試器-選擇調試器,選擇local windows debugger
,第二種方式是使用 python 來運行調試器,這次選用第二種方式運行調試器。
1、通過import idc
導入 idc 模塊,輸入idc.load
然後按 tab 鍵補全,找到idc.load_debugger
,參數為idc.load_debugger("win32",0) //1為遠程調試
,返回 True,代表執行成功。
載入 load_debugger 並設置參數
此時已加載了local windows debugger
。
在 pusha 之後設置斷點
pusha 之後設置斷點
pushad-popad法
是在執行 pushad 後一條指令之前,找到堆棧上保存寄存器值的位置,然後在該位置設置一個斷點,在程序解密出原始代碼之後跳轉到 OEP 執行之前會通過 popad 恢復寄存器的初始值,然後觸發該斷點暫停執行,從而確定 OEP 的位置。
按 F2 鍵在 pusha 的下一條指令上設置斷點,那麼執行 pusha 後調試會暫停下來。(pusha 類似於 pushad)
如果使用 python 來設置斷點,可以使用如下語句:
idaapi.add_bpt(0x46b002,0,idc.BPT_SOFT)
idaapi.add_bpt(0x46b002,0,0)
- 第一個參數是斷點地址
- 第二個參數是斷點的長度
- 第三個參數是斷點的類型(軟件斷點 BPT_SOFT 或 0)
選擇了調試器並且設置了第一處斷點後,現在需要啟動調試器運行程序然後暫停在該斷點上,按 F9 鍵或者使用 python 語句運行程序。
idc.StartDebugger("","","");
在高版本(ida 7.7)中,上述命令報錯,需要使用下述命令。
idc.start_process("","","")
執行上述語句後,調試器會運行到之前設置的斷點處,也就是0x46b002
處。
運行到斷點0x46b002
下圖中,堆棧視圖中的值就是 pushad 保存的寄存器的值,之後會通過 popad 指令讀取,所以可以在第一行上設置一處斷點。
在這次調試中,斷點的位置是 0x19ff54,就是 esp 執行堆棧中的位置。
點擊 ESP 右側的小箭頭,會跳轉到對應窗口的該地址(此處是匯編窗口)。
按 F2 在此處可設置斷點,修改成讀取和寫入時觸發,而不是執行時觸發。
如果窗口未出現,可以打開菜單調試器 - 斷點 - 斷點列表,然後右鍵選擇編輯設置。
如果用 python 可以安裝如下方式設置斷點:
idaapi.add_bpt(0x19ff54,1,3)
斷點列表如下
斷點列表
上述代碼中,第一個參數表示斷點地址,第二個參數表示斷點的大小,第三個參數表示類型,3 代表 read-write access(讀取和寫入),執行這語句和手動設置斷點一樣。
BPT_EXEC = 0,
BPT_WRITE = 1,
BPT_RDWR = 3,
BPT_SOFT = 4,
設置斷點類型的參數
在斷點列表中,右鍵單擊第一次設置的斷點,選擇 DISABLE 禁用,或者使用如下 python 語句。
idaapi.enable_bpt(0x46b002,0)
- 第一個參數為斷點地址
- 第二個參數如果為 1 則啟用斷點,0 為禁用斷點
0x46b002 標記為綠色代表禁用,堆棧上的斷口是紅色為啟用。
按 F9 鍵繼續調試或者輸入如下的 python 語句
idaapi.continue_process()
下圖中,調試在 popad 指令取回寄存器初始值之後中斷,然後程序將跳轉到 OEP 也就是 0x4271b0,因為 push ret 就和 jmp 類似。
popa 指令
繼續單步執行
執行到 oep。
既然找到了 oep 入口,那麼就可以重新分析可執行文件,識別函數。
重新分析程序
使用 idapython 進行 dump#
找到了 oep 後,下一步就是進行轉存,需要文件的基址以及可執行文件最後一個區段的最高地址。
上圖可以知道,基址是 0x40000,最高地址是 0x46e000。
轉存腳本如下:
import idaapi
import idc
import struct
start_ea = 0x400000
end_ea = 0x46e000
step = 4 # 每個地址處的數據占用4個字節
file_path = "dump.bin"
with open(file_path, "wb") as f:
for ea in range(start_ea, end_ea, step):
# 讀取指定地址處的4個字節數據並進行小端字節序轉換
bin_data = struct.pack("<L", idaapi.get_32bit(ea))
f.write(bin_data)
通過文件 - 腳本文件加載上面的 py 腳本,執行後,在當前目錄生成一個 dump.bin 文件,修改其擴展名為 exe。
通過 peeditor 打開,打開區段視圖,對所有的區段右鍵單擊,選擇 dumpfixer。
此時,圖標已修復。
修復圖標後,使用 Scylla 0.98 附加進程到被調試的加壳文件,目前執行到 OEP 入口。
加載此進程。
輸入 oep 為 004271B0,單擊 IAT Autosearch 和 Get Imports。
點擊 show invalid,會發現有一處未能識別的 API,嘗試自動修復失敗,需要手動修復。
0x460818 處的 API
上圖中,0x460818 是第一個有效區段的 API 函數,它上方更多的是未能識別的 API 地址。
檢查第一個未能識別的 0x46080c 處是什麼內容,按 D 鍵改變數據類型重組字節,如下圖:
這裡的內容不指向任何有效地址,如果按 CTRL+X,也沒有任何引用。
而對於真正的 API 函數,應該有引用信息表明它在何處被調用。
所以,由於這些不是 API 函數,所以可以刪除。
單擊 clear 鍵,然後點擊 IAT Autosearch,彈出的窗口選擇否。
現在的 IAT 起點位與 0x460810,然後點擊 Get Import,就可以找到所有的 API。
點擊 Fix Dump 並轉存文件,選擇之前導出的文件。
最後脫殼程序正常運行。
到此處,整個程序的脫殼就算是結束了。