這幾天我想學習一點逆向的知識,但苦于入門較難,教學視頻廢話太多,也不想看,就去刷ctf題,不會的就看wp,不得不說ctf真的是居家旅行,殺人滅口必備良藥啊。這不,在做逆向題的時候就發(fā)現(xiàn)有個軟件用ida打開沒啥函數(shù),查看wp才知道被加殼了,照著wp用PEID查殼,發(fā)現(xiàn)是upx的,人家用od演示了怎么手動去殼,但我看的云里霧里的,各種名詞她也沒有解釋,例如oep,popad啥的,我就只能去查,而且我的od還跟他的不一樣,最后我通過x64dbg去掉殼,去殼的過程使我對x64dbg的使用更加熟練,也了解了殼的原理等。于是想記錄下來。
脫殼的常見概念
殼:是一種對應(yīng)用程序加密壓縮處理的技術(shù)
一般對可執(zhí)行文件加殼的目的有三:
① 軟件加殼,保護數(shù)據(jù)、防止破解
② 外掛加殼,保護數(shù)據(jù)、防止破解
③ 病毒加殼,防止被查殺。
常見的殼分為壓縮殼和加密殼兩種,upx屬于壓縮殼
脫殼:表示通過反匯編將殼去掉的過程。
OEP:程序最開始執(zhí)行的地方。
原始OEP:程序加了殼后,殼是先運行的,所以O(shè)EP是殼程序的入口點,而原始OEP就是程序原來的入口點。
dump內(nèi)存:將內(nèi)存中的數(shù)據(jù)或代碼轉(zhuǎn)儲(dump)到本地。
IAT: 導入地址表,windows下可執(zhí)行文件中文件格式中的一個字段,描述的是導入信息函數(shù)地址,在文件中是一個RVA數(shù)組,在內(nèi)存中是一個函數(shù)地址數(shù)組。
修復IAT:在脫殼后,不管是加密殼還是壓縮殼,都有一個從內(nèi)存dump到本地存儲并保存為文件,而IAT在文件中是一個RVA數(shù)組,在內(nèi)存中是一個函數(shù)地址數(shù)組,我們就需要將從內(nèi)存dump出來的IAT從函數(shù)地址數(shù)組轉(zhuǎn)換成RVA數(shù)組,這樣程序才能修復。
脫殼的環(huán)境:這個單獨出來說,主要原因就是不同的系統(tǒng)脫殼時遇到的問題可能是不一樣的,因為脫殼時要修改IAT,而不同系統(tǒng)中同一個模塊的API導出的順序是不一樣的,所以修復時一般都會出現(xiàn)點問題。因此,我建議脫殼的環(huán)境應(yīng)該是在32位系統(tǒng)的虛擬機中,以下的所有操作應(yīng)該在32位系統(tǒng)的虛擬機中操作,64位系統(tǒng)下可能會出現(xiàn)意想不到的問題。
pushad: 將所有的32位通用寄存器壓入堆棧 ESP-32 。
popad: 將所有的32位通用寄存器出棧 ESP+32。
脫殼的方法
一步一步分析每一條匯編指令,吃透每一行匯編背后所代表的意思,將殼代碼讀懂,從而找到原始OEP然后脫殼。這種方法是最鍛煉人的,也是最難的。 說白了就是一點點的調(diào)試,需要深厚的匯編功底和耐心。
平衡堆棧(又稱ESP定律,技巧法)
一般加殼的程序,都是先運行殼,然后再內(nèi)存中還原程序,然后跳轉(zhuǎn)到原始OEP,開始執(zhí)行原程序代碼。既然先運行殼,那就必然進入殼程序然后再退出殼程序,期間要遵守堆棧平衡(進入前和退出后的棧指針是相同的),也就是說殼退出后,必然會操作堆棧指針為進入之前的堆棧指針。在進入殼程序時,就可以在堆棧設(shè)置訪問斷點,讓程序跑起來,當程序暫停的時候,就是殼程序即將執(zhí)行完的時候,然后再其附近單步跟蹤就可以找到原始OEP了。 這種方法比較適用于upx這種只對代碼和數(shù)據(jù)壓縮了的殼,如果還對代碼加密了,那么就不是太好找了。加密的話就需要結(jié)合單步跟蹤法。
脫殼三步法
不管是哪種脫殼方法,都需要遵循脫殼三步法,脫殼三步法分為以下三步:
① 尋找原始OEP
這一步驟的主要作用就是要確定原始程序代碼到底在哪里,能找到原始程序的代碼,說明殼代碼執(zhí)行完了,我們只有找到原始OEP才能進行下一步的動作。
② dump內(nèi)存到文件
當我們找到原始OEP,調(diào)試運行到原始OEP時,只要代碼被還原,我們就可以在這個地方進行dump內(nèi)存,將內(nèi)存中被還原的代碼和數(shù)據(jù)抓取下來,重新保存成一個文件,這樣脫完殼時,我們就可以用靜態(tài)分析工具分析程序了。
③ 修復文件
這一步主要就是修復IAT,對從內(nèi)存中轉(zhuǎn)儲到本地的文件進行修復。
UPX殼
①經(jīng)過UPX壓縮的win32/pe文件,包含三個區(qū)段:UPX0, UPX1, .rsrc或UPX0, UPX1, UPX2(原文件本身無資源時)。
UPX0:在文件中沒有內(nèi)容,它的”Virtual size”加上UPX1的構(gòu)成了原文件全部區(qū)段需要的內(nèi)存空間,相當于區(qū)段合并。
②UPX1:起始位置為需解壓縮的源數(shù)據(jù),目標地址為UPX0基址。緊接著源數(shù)據(jù)塊是”UPX stub”,即殼代碼。一個典型的pushad/popad結(jié)構(gòu),所以人們常用”ESP定律”來脫UPX。
③.rsrc/UPX2:在原文件有資源時,含有原資源段的完整頭部和極少部分資源數(shù)據(jù)(類型為ICON、GROUP_ICON、VERSION和MANIFEST),以保證explorer.exe能正常顯示圖標、版本信息。還有就是UPX自己的Imports內(nèi)容,導出表的庫名和函數(shù)名(如果有的話)。
就是因為UPX是一個典型的pushad/popad結(jié)構(gòu),又要遵守堆棧平衡,即pushad之前的ESP需要跟popad之后的ESP相同,所以當我們看到UPX殼進入pushad后,對堆棧添加訪問斷點,當殼即將及執(zhí)行結(jié)束即執(zhí)行到popad要去還原堆棧時,必然會觸發(fā)斷點暫停下來,然后單步調(diào)試往下走,原始OEP就在附近了。

打開x64dbg,拖入程序。發(fā)現(xiàn)EIP指在這個位置,往下看看并沒有發(fā)現(xiàn)pushad,不知道為啥人家用od打開就能看到pushad。這種情況我們就按F8單步調(diào)試,不知按了多少次后終于遇到了pushad,我們可以看一下在沒有執(zhí)行pushad時的ESP為 0060FF74,如下圖:

然后在按一次F8,使其執(zhí)行pushad,將此時的ESP記錄一下,0060FF54,正好相差32。

此時我們在堆棧打上斷點,右鍵ESP,點擊在內(nèi)存窗口中轉(zhuǎn)到。

然后在內(nèi)存窗口中,右鍵斷點—>硬件訪問—>4字節(jié)。

打上斷點后,直接按F9運行到斷點處,觀察ESP值為0060FF74,跟pushad前一樣,遵循堆棧平衡。且上面就是popad,此時原始OEP就不遠了,我們要理解殼程序執(zhí)行完后,必然有個大的跳轉(zhuǎn) 跳轉(zhuǎn)到原始OEP去執(zhí)行源程序,我們能看到下面有一個jmp 且跳轉(zhuǎn)到401280,跳轉(zhuǎn)范圍很大,這應(yīng)該就是我們要找的原始OEP.

雙擊jmp跳轉(zhuǎn)到401280,使用插件sclla,修改OEP為401280,點擊dump,dump到本地文件。保存即可。


但此時的文件無法正常運行,因為沒有修復IAT,但是通過IDA可查看原始代碼了,如果有運行程序的需求可以修復IAT。
修復IAT
依然使用Scylla,使用IAT自動掃描,

成功后點擊 GET Imports 獲取導入表

然后再點擊,fix Dump 修復上一節(jié)的dump文件

即可。
總結(jié)
對于像我這種想入門逆向的,這種方式真的可以培養(yǎng)興趣,也從中學到了很多知識,我也不會僅僅止步于脫upx的。