1)搜索JMP或者CALL指令的機(jī)器碼(即一步直達(dá)法,只適用于少數(shù)殼,包括UPX,ASPACK殼)
? 對(duì)于一些簡單的殼可以用這種方式來定位OEP,但是對(duì)于像AsProtect這類強(qiáng)殼(PS:AsProtect在04年算是強(qiáng)殼了,嘿嘿)就不適用了,我們可以直接搜索長跳轉(zhuǎn)JMP(0E9)或者CALL(0E8)這類長轉(zhuǎn)移的機(jī)器碼,一般情況下(理想情況)殼在解密完原程序各個(gè)區(qū)段以后,需要一個(gè)長JMP或者CALL跳轉(zhuǎn)到原程序代碼段中的OEP處開始執(zhí)行原程序代碼。
下面我們將上一章中加了UPX殼的那個(gè)CrueHead的CrackMe加載到OD中:

這里我們按CTRL+B組合鍵搜索一下JMP的機(jī)器碼E9,看看有沒有這樣一個(gè)JMP跳轉(zhuǎn)到原程序的代碼段。

這個(gè)JMP是跳轉(zhuǎn)到第一個(gè)區(qū)段的,我們在這條指令處設(shè)置一個(gè)斷點(diǎn),斷在這里時(shí),我們按F7鍵就可以單步跳轉(zhuǎn)到OEP處。
除了搜索JMP指令的機(jī)器碼以外,大家還可以嘗試搜索CALL EAX,CALL EBX,JMP EAX等指令的機(jī)器碼,因?yàn)楹芏鄽な菍EP的值存放在寄存器中,然后通過CALL 某寄存器或者JMP 某寄存器來跳往OEP的。OllyBbg提供了搜索ALL COMMANDS的功能,我們可以通過單擊鼠標(biāo)右鍵選擇-Search for-All Commands來搜索,然后各個(gè)指令處依次設(shè)置斷點(diǎn),下面我們來看個(gè)例子。
2)堆棧平衡法(即ESP定律法)
? 這種方法適用于一些古老的殼。這些殼首先會(huì)使用PUSHAD指令保存寄存器環(huán)境,在解密各個(gè)區(qū)段完畢,跳往OEP之前,會(huì)使用POPAD指令恢復(fù)寄存器環(huán)境。
這里我們來看看CRACKME UPX。

我們可以看到第一條指令就是PUAHAD,有的情況下保存寄存器環(huán)境可能不是第一條指令,但也在附近了,還有些情況下,有些殼不使用PUSHAD,而是逐一PUSH各個(gè)寄存器(例如:PUSH EAX,PUSH EBX等等),總而言之,在解密完區(qū)段,跳往OEP之前會(huì)恢復(fù)寄存器環(huán)境。
這里我們按F7鍵執(zhí)行PUSHAD:

可以看到各個(gè)寄存器的初始值被壓入到堆棧中了,這里我們可以對(duì)這些初始值設(shè)置內(nèi)存或者硬件訪問斷點(diǎn),當(dāng)解密例程讀取這些初始值的時(shí)候就會(huì)斷下來,斷下來處基本上就在OEP附近了。
這里我們可以通過在ESP寄存器值上面單擊鼠標(biāo)右鍵選擇-Follow in dump在數(shù)據(jù)窗口中定位到這些寄存器的初始值。

數(shù)據(jù)窗口中顯示的堆棧內(nèi)容如下:

這里我們可以對(duì)這些初始值的第一個(gè)字節(jié)或者前4個(gè)字節(jié)設(shè)置硬件訪問斷點(diǎn)。

選擇字節(jié),字,雙字都可以,只要解密例程在讀取這些值的時(shí)候斷下來就OK,運(yùn)行起來。

我們可以斷在了POPAD指令的下一行,當(dāng)殼的解密例程讀取該值的時(shí)候斷了下來,緊接著下面就是跳往OEP處,說明這個(gè)方法起作用了。
3)VB應(yīng)用程序定位OEP法(Native 或者 P-CODE)
定位VB程序的OEP比較容易,因?yàn)閂B應(yīng)用程序都有一個(gè)特點(diǎn)-開始都是一個(gè)PUSH指令,緊接著一個(gè)CALL指令調(diào)用一個(gè)VB API函數(shù)。我們可以使用Patch過的OD,首先定位到VB的動(dòng)態(tài)庫,接著給該動(dòng)態(tài)庫的代碼段設(shè)置內(nèi)存訪問斷點(diǎn),
當(dāng)殼的解密例程解密完原程序各個(gè)區(qū)段,接著就會(huì)斷在VB DLL的第一條指令處,接著我們可以在堆棧中定位到返回地址,就可以來到OEP的下一條指令處。這里我們也可以使用前面介紹的方法-跟逐一給各個(gè)區(qū)段設(shè)置內(nèi)存訪問斷點(diǎn)(使用Patch過的OD),但是很多殼會(huì)檢測這種方法,所以大家可能根據(jù)需要不同的情況來嘗試這不同的方法。這種方法很容易理解,我就不舉例子了,以后大家如果遇到了VB程序可以試試這種方法。
4)?最后一次異常法
? 如果我們在脫殼的過程中發(fā)現(xiàn)目標(biāo)程序產(chǎn)生大量異常的話,就可以使用最后一次異常法,我們來看一個(gè)例子,名字叫做”bitarts_evaluations.c”。
我們還是使用Patch過的OD來加載它,并且配置好反反調(diào)試插件。
然后將EXCEPTIONS菜單項(xiàng)中的忽略各個(gè)異常的選項(xiàng)都勾選上,運(yùn)行起來。

我們可以看到程序運(yùn)行起來了,我們單擊工具欄中L按鈕打開日志窗口。

這里我們可以看到產(chǎn)生了好幾處異常,但是都不是位于第一個(gè)區(qū)段,說明這些異常不是在原程序運(yùn)行期間發(fā)生的,是在殼的解密例程執(zhí)行期間產(chǎn)生的異常,最后一次是46e88f處的這個(gè)異常。
好,現(xiàn)在我們重新啟動(dòng)OD,將EXCEPTIONS菜單項(xiàng)中忽略的異常選項(xiàng)的對(duì)勾都去掉,僅保留Ignore memory access violations in KERNEL32這個(gè)選項(xiàng)的對(duì)勾。

我們運(yùn)行起來,產(chǎn)生異常斷了下來,我們直接按SHIFT + F9忽略異常繼續(xù)運(yùn)行。直到停在了46E88F處為止。

這里不是,我們按SHIFT + F9忽略異常繼續(xù)運(yùn)行,我們知道最后一次異常是46E88F處的INT 3指令引發(fā)的。

這里是殼的解密例程執(zhí)行過程中產(chǎn)生的最后一次異常,接著就是執(zhí)行原程序的代碼了。
5)利用應(yīng)用程序調(diào)用的第一個(gè)API函數(shù)來定位OEP
這種方法就是直接給應(yīng)用程序調(diào)用的第一個(gè)API函數(shù)設(shè)置斷點(diǎn),比如說,很多程序(VC++)一開始會(huì)調(diào)用GetVersion,GetModuleHandleA,對(duì)于bitarts_evaluations.c來說我們可以斷GetVersion,對(duì)于CRACKME UPX來說我們可以斷GetModuleHandleA。這里是bitarts_evaluations.c,所以我們給GetVersion設(shè)置斷點(diǎn)。

運(yùn)行起來。

這里我們斷在了GetVersion的入口點(diǎn)處,從堆棧中我們可以看到返回地址位于第一個(gè)區(qū)段。我們直接在返回地址上面單擊鼠標(biāo)右鍵選擇-Follow in Disassembler。


里我們又定位到了OEP。以上就給大家演示的如何利用應(yīng)用程序調(diào)用的第一個(gè)API函數(shù)來定位OEP了。如果我們遇到有的殼檢測GetVersion入口處的INT 3斷點(diǎn)的話,我們可以嘗試在該API函數(shù)的返回指令RET處下斷。
其實(shí)還有很多適用于特定殼定位OEP的方法,這里就不給大家一一介紹了,基本上也是根據(jù)上面的這些基本方法變通來的,所以說大家掌握好上述這些基本的定位OEP的方法和原理就即可。
這里給大家留一個(gè)小程序練習(xí),名字叫做UnPackMe_tElock0.98。大家嘗試定位其OEP。這個(gè)殼專門采取了一些技巧來干擾利用上述方法定位OEP,所以說如果直接利用上述這些方法的話,就不能奏效了,大家可以好好琢磨一下這個(gè)殼。
大家記住,如果殼檢測INT 3斷點(diǎn)或者硬件斷點(diǎn)的話,你使用ESP定律給堆棧中的寄存器初始值設(shè)置硬件斷點(diǎn)也是不起作用的,只能換其他方法。