2020-12-3(ESP定律脱壳理解)
在我們開始討論ESP定律之前,我先給你講解一下一些簡單的匯編知識。
1.call
這個命令是訪問子程序的一個匯編基本指令。也許你說,這個我早就知道了!別急請繼續看完。
call真正的意義是什么呢?我們可以這樣來理解:1.向堆棧中壓入下一行程序的地址;2.JMP到call的子程序地址處。例如:
00401029 . E8 DA240A00 call 004A3508
0040102E . 5A pop edx
在執行了00401029以后,程序會將0040102E壓入堆棧,然后JMP到004A3508地址處!
2.RET
與call對應的就是RET了。對于RET我們可以這樣來理解:1.將當前的ESP中指向的地址出棧;2.JMP到這個地址。
這個就完成了一次調用子程序的過程。在這里關鍵的地方是:如果我們要返回父程序,則當我們在堆棧中進行堆棧的操作的時候,一定要保證在RET這條指令之前,ESP指向的是我們壓入棧中的地址。這也就是著名的“堆棧平衡”原理!
3.狹義ESP定律
ESP定律的原理就是“堆棧平衡”原理。
讓我們來到程序的入口處看看吧!
1.這個是加了UPX殼的入口時各個寄存器的值!
EAX 00000000
ECX 0012FFB0
EDX 7FFE0304
EBX 7FFDF000
ESP 0012FFC4
EBP 0012FFF0
ESI 77F51778 ntdll.77F51778
EDI 77F517E6 ntdll.77F517E6
EIP 0040EC90 note-upx.
C 0 ES 0023 32bit 0(FFFFFFFF)
P 1 CS 001B 32bit 0(FFFFFFFF)
A 0 SS 0023 32bit 0(FFFFFFFF)
Z 0 DS 0023 32bit 0(FFFFFFFF)
S 1 FS 0038 32bit 7FFDE000(FFF)
T 0 GS 0000 NULL
D 0
O 0 LastErr ERROR_MOD_NOT_FOUND (0000007E)
2.這個是UPX殼JMP到OEP后的寄存器的值!
EAX 00000000
ECX 0012FFB0
EDX 7FFE0304
EBX 7FFDF000
ESP 0012FFC4
EBP 0012FFF0
ESI 77F51778 ntdll.77F51778
EDI 77F517E6 ntdll.77F517E6
EIP 004010CC note-upx.004010CC
C 0 ES 0023 32bit 0(FFFFFFFF)
P 1 CS 001B 32bit 0(FFFFFFFF)
A 0 SS 0023 32bit 0(FFFFFFFF)
Z 1 DS 0023 32bit 0(FFFFFFFF)
S 0 FS 0038 32bit 7FFDE000(FFF)
T 0 GS 0000 NULL
D 0
O 0 LastErr ERROR_MOD_NOT_FOUND (0000007E)
呵呵~是不是除了EIP不同以外,其他都一模一樣啊!
為什么會這樣呢?
我們來看看UPX的殼的第一行:
0040EC90 n> 60 pushad //注意這里*
0040EC91 BE 15B04000 mov esi,note-upx.0040B015
PUSHAD就是把所有寄存器壓棧!我們在到殼的最后看看:
0040EE0F 61 popad //注意這里*
0040EE10 - E9 B722FFFF jmp note-upx.004010CC //JMP到OEP
POP就是將所有寄存器出棧!
而當我們PUSHAD的時候,ESP將寄存器壓入了0012FFC0–0012FFA4的堆棧中!如下:
0012FFA4 77F517E6 返回到 ntdll.77F517E6 來自 ntdll.77F78C4E //EDI
0012FFA8 77F51778 返回到 ntdll.77F51778 來自 ntdll.77F517B5 //ESI
0012FFAC 0012FFF0 //EBP
0012FFB0 0012FFC4 //ESP
0012FFB4 7FFDF000 //EBX
0012FFB8 7FFE0304 //EDX
0012FFBC 0012FFB0 //ECX
0012FFC0 00000000 //EAX
所以這個時候,在教程上面就告訴我們對ESP的0012FFA4下硬件訪問斷點。也就是說當程序要訪問這些堆棧,從而恢復原來寄存器的值,準備跳向苦苦尋覓的OEP的時候,OD幫助我們中斷下來。
于是我們停在0040EE10這一行!
總結:我們可以把殼假設為一個子程序,當殼把代碼解壓前和解壓后,他必須要做的是遵循堆棧平衡的原理,讓ESP執行到OEP的時候,使ESP=0012FFC4。
4.廣義ESP定律
很多人看完了教程就會問:ESP定律是不是就是0012FFA4,ESP定律的適用范圍是不是只能是壓縮殼!
我的回答是:NO!
看完了上面你就知道你如果用0012FFA8也是可以的,ESP定律不僅用于壓縮殼他也可以用于加密殼!!!
首先,告訴你一條經驗也是事實—當PE文件運行開始的時候,也就是進入殼的第一行代碼的時候。寄存器的值總是上面的那些值,不信你自己去試試!而當到達OEP后,絕大多的程序都第一句都是壓棧!(除了BC編寫的程序,BC一般是在下面幾句壓棧)
現在,根據上面的ESP原理,我們知道多數殼在運行到OEP的時候ESP=0012FFC4。這就是說程序的第一句是對0012FFC0進行寫入操作!
最后我們得到了廣義的ESP定律,對只要在0012FFC0下,硬件寫入斷點,我們就能停在OEP的第二句處!!
下面我們來舉個例子,就脫殼進階第一篇吧!
載入OD后,來到這里:
0040D042 N> B8 00D04000 mov eax,Notepad.0040D000 //停在這里
0040D047 68 4C584000 push Notepad.0040584C
0040D04C 64:FF35 00000000 push dword ptr fs:[0] //第一次硬件中斷,F9
0040D053 64:8925 00000000 mov dword ptr fs:[0],esp
0040D05A 66:9C pushfw
0040D05C 60 pushad
0040D05D 50 push eax
直接對0012FFC0下硬件寫入斷點,F9運行。(注意硬件中斷)
在0040D04C第一次硬件中斷,F9繼續!
0040D135 A4 movs byte ptr es:[edi],byte ptr ds:[esi] //訪問異常,不管他 shift+F9繼續
0040D136 33C9 xor ecx,ecx
0040D138 83FB 00 cmp ebx,0
0040D13B ^ 7E A4 jle short Notepad.0040D0E1
第二次硬件中斷。
004058B5 64 db 64 //斷在這里
004058B6 89 db 89
004058B7 1D db 1D
004058B8 00 db 00
004058B9 00 db 00
這里也不是,F9繼續!
004010CC /. 55 push ebp
004010CD |. 8BEC mov ebp,esp //斷在這里,哈哈,到了!(如果發現有花指令,用ctrl+A分析一下就能顯示出來)
004010CF |. 83EC 44 sub esp,44
004010D2 |. 56 push esi
快吧!還不過癮,在來一個例子。
脫殼進階第二篇
如果按上面的方法斷不下來,程序直接運行了!沒什么,我們在用另一種方法!
載入后停在這里,用插件把OD隱藏!
0040DBD6 N>^\E9 25E4FFFF jmp Note_tEl.0040C000 //停在這里
0040DBDB 0000 add byte ptr ds:[eax],al
0040DBDD 0038 add byte ptr ds:[eax],bh
0040DBDF A4 movs byte ptr es:[edi],byte ptr ds:[esi]
0040DBE0 54 push esp
F9運行,然后用SHIFT+F9跳過異常來到這里:
0040D817 ^\73 DC jnb short Note_tEl.0040D7F5 //到這里
0040D819 CD20 64678F06 vxdcall 68F6764
0040D81F 0000 add byte ptr ds:[eax],al
0040D821 58 pop eax
在這里對0012FFC0下硬件寫入斷點!(命令行里鍵入HW 12FFC0)SHIFT+F9跳過異常,就來到OEP的第二行處:(用CTRL+A分析一下)
004010CC /. 55 push ebp
004010CD |. 8BEC mov ebp,esp //斷在這里
004010CF |. 83EC 44 sub esp,44
004010D2 |. 56 push esi
004010D3 |. FF15 E4634000 call dword ptr ds:[4063E4]
004010D9 |. 8BF0 mov esi,eax
004010DB |. 8A00 mov al,byte ptr ds:[eax]
004010DD |. 3C 22 cmp al,22
就這樣我們輕松搞定了兩個加密殼的找OEP問題!
5.總結
現在我們可以輕松的回答一些問題了。
1.ESP定律的原理是什么?
堆棧平衡原理。
2.ESP定律的適用范圍是什么?
幾乎全部的壓縮殼,部分加密殼。只要是在JMP到OEP后,ESP=0012FFC4的殼,理論上我們都可以使用。但是在何時下斷點避開校驗,何時下斷OD才能斷下來,這還需要多多總結和多多積累。歡迎你將你的經驗和我們分享。
3.是不是只能下斷12FFA4的訪問斷點?
當然不是,那只是ESP定律的一個體現,我們運用的是ESP定律的原理,而不應該是他的具體數值,不能說12FFA4,或者12FFC0就是ESP定律,他們只是ESP定律的一個應用罷了!
4.對于STOLEN CODE我們怎么辦?
哈哈,這正是尋找STOLEN CODE最好的辦法!當我們斷下時,正好斷在了殼處理STOLEN CODE的地方,在F8一會就到OEP了!
6.后話
看了上面的文字希望能對你在尋找OEP的時候有幫助,但是別忘了一句話:菜鳥認為找OEP很難,高手認為修復才是最難! 好了,下一篇應該寫IAT的修復原理了!讓我們共同努力吧!
尋找真正的入口(OEP)–內存斷點
Author:Lenus
1.前言
發現論壇中很多兄弟在詢問:什么是二次內存斷點,三次內存斷點。還有很多人對內存斷點的原理不是很明白。其實只要懂得殼是如何解壓代碼的,那么就完全可以按自己的喜歡來下斷。
本文要解決的問題是:
1.什么是內存斷點?
2.如何在尋找OEP時使用內存斷點。
3.內存斷點的局限性。
2.內存斷點尋找OEP的原理
i.首先,在OD中內存斷點和普通斷點(F2下斷)是有本質區別的。
內存斷點等效與命令bpm,他的中斷要用到DR0-DR7的調試寄存器,也就是說OD通過這些DR0-DR7的調試寄存器來判斷是否斷下
普通斷點(F2下斷)等效于bpx,他是在所執行的的代碼的當前地址的一個字節修改為CC(int3)。當程序運行到int3的時候就會產生一個異常,而這個異常將交給OD處理,把這個異常的regEIP-1以后就正好停在了需要的中斷的地方(這個根據系統不同會不一樣),同時OD在把上面的int3修改回原來的代碼。
內存斷點分為:內存訪問斷點,內存寫入斷點。
我們知道,在程序運行的時候會有3種基本的狀態產生:讀取,寫入,執行。
CODE:
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處的內存執行
[Copy to clipboard]
那么我們應該如何中斷在上面的幾行呢?
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載入以后來到這里
CODE:
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
[Copy to clipboard]
根據跟過一次的經驗我們將先設置,除int3異常以外忽略其他異常,SHIFT+F9
CODE:
003725B9 90 nop //到這里
003725BA 8BCD mov ecx,ebp
[Copy to clipboard]
然后再設置除“除零”異常外,忽略其他異常。SHIFT+F9
CODE:
00372660 F7F3 div ebx //到這里
00372662 90 nop
[Copy to clipboard]
下面是很多的單步異常,太麻煩我們不管他,現在開始用內存斷點的方法。
對code段下內存訪問斷點,希望他已經解壓完畢。F9
CODE:
0040D19D A4 movs byte ptr es:[edi],byte ptr ds:[esi] //還沒解完呢
0040D19E B3 02 mov bl,2
[Copy to clipboard]
對data段下內存“寫入”斷點,試試看他是不是要寫data段。
CODE:
00372712 F3:A4 rep movs byte ptr es:[edi],byte ptr ds:[esi] //斷到這里
00372714 5E pop esi
[Copy to clipboard]
下面再對code段下內存訪問斷點。F9
CODE:
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
[Copy to clipboard]
現在如果再對data下訪問斷點已經是沒用了。這時應該格外的小心。
我們現在就想既然這一段是對code解碼的,那么我們就繞過他吧!
到0037286A下斷F2,然后清除內存斷點!!!!
F9以后停在這里,繼續對code下內存訪問斷點。
看看左下角還在解碼,哎~真是麻煩!
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
[Copy to clipboard]
又是一段解碼的代碼,再次使用上面的辦法手動跳出去。
現在繼續對code段下內存訪問斷點!!F9以后到達這里。
CODE:
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
[Copy to clipboard]
呵呵~雖然不是我們熟悉的OEP,但是地址是沒錯了,況且根據我們的步驟,我可以很肯定的說這是code段的第一次“執行”中斷!
所以這就是OEP了。
總結一下:當我們在尋找OEP的時候,要多次對code下斷“賭”一“賭”他解壓完畢,如果不是就對別的段試試如果程序跑飛了,那就沒辦法了,重來唄其實說起來要賭的是:當data段,idata段,rsrc段擺在你的面前,你會好好“珍惜”那個段,不過還好上天還會給我們從來一次的機會(ctrl+F2 _),那么我們會對那個不會跑飛的段說3個字----“先斷你”如果非要在上面加一個次數,我希望是“一次內存斷點就好了”
vi.下面來討論一下內存斷點的局限性問題。
是不是什么殼都可以用內存中斷啊?
不是每個都可以的,一些像UPX和ASPACK就不行。
為什么?
呵呵~follew me!
情況1.
我們來看看UPX的殼
首先,他的殼代碼在UPX1段。
這里是他要跳到OEP的地方
CODE:
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
[Copy to clipboard]
我們看到他在對code段解壓完畢的時候馬上就JMP到OEP去了,那么我們根本就來不及使用內存斷點的辦法。
你可能說,我可以在
0040ED6F 61 popad //這一句下段然后使用啊
呵呵~~當然可以,不過你把花在下內存斷點的時間,多按下幾次F8不更好?!
也就是說當一個殼如果他在JMP 到OEP前的一行代碼仍在都在對code段解壓,那么我們就不能再使用這種辦法了!
或者說我們沒必要使用內存斷點更貼切一點!
情況2.:
對于一些在OEP處有stolen code的代碼
我們來看看一個OEP
CODE:
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 <jmp.&kernel32.OpenFileMappingA> //API
0049E316 A3 60194A00 mov dword ptr ds:[4A1960],eax
0049E31B 833D 60194A00 00 cmp dword ptr ds:[4A1960],0
[Copy to clipboard]
這個軟件在被PESPIN加殼了以后這些全被偷掉了!
也就是說,殼在模擬OEP代碼的時候必然會執行
QUOTE:
0049E2FF E8 048CF6FF call unpack.00406F08 //這一步
而這個地方是call向code段的。如果我們使用內存訪問斷點,那么就停在這個子程序的地方
CODE:
00406F08 50 push eax //會停在這里
00406F09 6A 00 push 0
00406F0B E8 F8FEFFFF call <jmp.&kernel32.GetModuleHandleA>
00406F10 BA 04F14900 mov edx,unpack.0049F104
00406F15 52 push edx
[Copy to clipboard]
這里既不是處理stolen code的地方,也不是FOEP的地方。這就會對我們的判斷產生誤導。
當然你可以alt+F9返回到殼處理stolen的地方,然后用內存斷點,或者按幾下F8到達FOEP處,但試問如果你拿到一個未知的殼的時候又怎么知道應該這么處理呢?
還有其他一些情況留給大家總結吧!
在下的磚已拋出,各位的玉不久矣。
3.總結:
好了說了很多,大家應該對內存斷點的辦法有了全面的了解,如果了解了內存斷點的原理就不難明白他的使用方法,不難明白為什么有寫殼不能使用內存斷點的辦法,其實任何的一種辦法都需要經驗的積累。相信如果大家在回答開篇的3個問題,已經不難了。
下面給出一些使用內存斷點尋找OEP的實例,大家可以結合原理再好好的體會一下。
4.思考題
使用多次內存斷點的辦法要謹慎對待data,rsrc區域的內存訪問斷點,即使要下也要注意盡量用寫入斷點。為什么?
總結
以上是生活随笔為你收集整理的2020-12-3(ESP定律脱壳理解)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020-12-2(详细解释neg指令
- 下一篇: 2020-12-3(详解虚拟地址如何转化