彙編裏面的esp解釋

最近在學加密解密,用od進行反彙編的時候,又重新對彙編瞭解研究了一下。。特別是對esp做了一個深入的研究。。下面是網上搜索到的。先記在這邊看看。。
               

      在寄存器裏面有很多寄存器雖然他們的功能和使用沒有任何的區別,但是在長期的編程和使用中,在程序員習慣中已經默認的給每個寄存器賦上了特殊的含義,比 如:EAX一般用來做返回值,ECX用於記數等等。在win32的環境下EBP寄存器用與存放在進入call以後的ESP的值,便於退出的時候回覆ESP 的值,達到堆棧平衡的目的。
應用以前說過的一段話:
原程序的OEP,通常是一開始以 Push EBP 和MOV Ebp,Esp這兩句開始的,不用我多說大家也知道這兩句的意思是以EBP代替ESP,作爲訪問堆棧的指針。
爲什麼要這樣呢?爲什麼幾乎每個程序都是的開頭能?因爲如果我們寫過C等函數的時候就應該清楚,程序的開始是以一個主函數main()爲開始的,而函數在訪問的過程中最重要的事情就是要確保堆棧的平衡,而在win32的環境下保持平衡的辦法是這樣的:
1.讓EBP保存ESP的值;
2.在結束的時候調用
mov esp,ebp
pop ebp
retn
或者是
leave
retn
兩個形式是一個意思。
這樣做的好處是不用考慮ESP等於多少,PUSH了多少次,要POP多少次了,因爲我們知道EBP裏面放的是開始時候的ESP值。
2.推廣的ESP定律
在尋找OEP的時候,往往下斷HW ESP-4不成功,除了殼代碼將硬件斷點刪除了以外,很可能的情況就是因爲殼代碼在運行到OEP的時候他的ESP已經不再是在EP時候的ESP(12FFC4)了,這樣我們下斷當然是不成功的。
那麼如何找到在殼到達OEP的時候的堆棧的值將是關鍵。
在這裏我們應用的關鍵是
Push EBP
MOV Ebp,Esp----》關鍵是這句
我 來解釋一下,當程序到達OEP的時候Push EBP這句對於ESP的值來說就是ESP-4,然後是ESP-4賦給了EBP,而做爲保存ESP值作用的EBP寄存器在這個“最上層的程序”中的值將始終 不會改變。雖然他可能在進入子call裏面以後會暫時的改變(用於子程序的堆棧平衡)但是在退出了以後依*pop ebp這一句將還原原來的EBP的值。
以這句做爲突破口,就是說只要我們能斷在“最上層的程序”中,就能通過觀察EBP的值得到殼在JMP到OEP的時候的ESP的值了。
3.實戰
來看看pespin1.1的殼,在pespin1.0的殼中,我們使用HW 12FFC0能很容易的找到stolen code的地方,但是到pespin1.1的時候,我們就不行了。用HW 12FFC0根本斷不下來。
現在我們就使用這個推廣的ESP定律,載入程序後來到最後的一個異常
0040ED85 2BDB sub ebx,ebx //停在這裏
0040ED87 64:8F03 pop dword ptr fs:[ebx]
0040ED8A 58 pop eax
0040ED8B 5D pop ebp
0040ED8C 2BFF sub edi,edi
0040ED8E EB 01 jmp short pespin1_.0040ED91
0040ED90 C466 81 les esp,fword ptr ds:[esi-7F]
我用使用內存斷點辦法來到FOEP處
004010D3 0000 add byte ptr ds:[eax],al
004010D5 0000 add byte ptr ds:[eax],al
004010D7 0000 add byte ptr ds:[eax],al
004010D9 0000 add byte ptr ds:[eax],al
004010DB 0000 add byte ptr ds:[eax],al
004010DD 0000 add byte ptr ds:[eax],al
004010DF 75 1B jnz short pespin1_.004010FC //這裏是FOEP
004010E1 56 push esi
004010E2 FF15 99F44000 call dword ptr ds:[40F499]
004010E8 8BF0 mov esi,eax
004010EA 8A00 mov al,byte ptr ds:[eax]
好了,這裏就是“最上層的程序”的地方了,看看寄存器
EAX 00141E22
ECX 0040C708 pespin1_.0040C708
EDX 0040C708 pespin1_.0040C708
EBX 0040C708 pespin1_.0040C708
ESP 0012F978
EBP 0012F9C0 //注意這裏
ESI 00141EE0
EDI 0040E5CD pespin1_.0040E5CD
EIP 004010DF pespin1_.004010DF
看到了吧,EBP=0012F9C0,我們來想象一下這個值是怎麼得到的。
首先肯定是通過MOV ESP,EBP這一句,也就是說ESP這時是0012F9C0的,然而上面還有一句PUSH EBP也就是說ESP在到達OEP的時候應該是0012F9C4的。好了得到這個結論我們就能很快的找到stolen code的所在了。
重來停在最後的異常
0040ED85 2BDB sub ebx,ebx //停在這裏
0040ED87 64:8F03 pop dword ptr fs:[ebx]
0040ED8A 58 pop eax
0040ED8B 5D pop ebp
0040ED8C 2BFF sub edi,edi
0040ED8E EB 01 jmp short pespin1_.0040ED91
0040ED90 C466 81 les esp,fword ptr ds:[esi-7F]
然後下斷HW 0012F9C0 ,F9運行,來到這裏
0040D8FB 61 popad
0040D8FC 55 push ebp
0040D8FD EB 01 jmp short pespin1_.0040D900 //停在這裏
0040D8FF 318B ECEB01AC xor dword ptr ds:[ebx+AC01EBEC],ecx
0040D905 83EC 44 sub esp,44
0040D908 EB 01 jmp short pespin1_.0040D90B
0040D90A 72 56 jb short pespin1_.0040D962
0040D90C EB 01 jmp short pespin1_.0040D90F
0040D90E 95 xchg eax,ebp
0040D90F FF15 6CF34000 call dword ptr ds:[40F36C]
0040D915 EB 01 jmp short pespin1_.0040D918
於是就很快的找到了stolen code的所在了。
4.總結
上面的這個辦法大概可以總結以下的步驟:
(1).直接或間接的斷在“最上層的程序”的地方。
(2).得到“最上層的程序”的EBP的值。
(3).利用程序初始化的兩個固定語句找到殼JMP到OEP的堆棧值。這個辦法有很大的侷限性,因爲只有VC和delphi程序使用這個初始化的開頭。
但是找到“最上層的程序”的辦法除了內存斷點還有很多辦法,例如對於VC來說使用 bp ExitProcess也是一個很好的斷點,可以直接得到EBP的數值。
5.後話
原來這個辦法有很強的前提條件,不是一個很具普遍性的辦法,我原來也不想單獨的提出來,但是對於jney2兄弟的anti-ESP定律來說這個辦法卻是一個解決之道。
當然還有更多的辦法,在這裏我只想說很多事情有矛就有盾,沒有什麼辦法是一定沒有漏洞的,只是希望這篇文章給大家闊寬思路,起到拋磚引玉的作用。
當調用函數時
原EBP值已經被壓棧(位於棧頂),而新的EBP又恰恰指向棧頂。
此時EBP寄存器就已經處於一個非常重要的地位,該寄存器中存儲着棧中的一個地址(原EBP入棧後的棧頂),
從該地址爲基準,向上(棧底方向)能獲取返回地址、參數值,向下(棧頂方向)能獲取函數局部變量值,
而該地址處又存儲着上一層函數調用時的EBP值!


10關於變量的賦值,

能否瞭解到對變量的賦值過程在算法研究中是非常重要的。由於變量是用地址訪問的,,因此對形如 MOV [AAA],BBB 的代碼要高度關注,它通常是修改變量(地址爲AAA,或AAA爲寄存器時地址爲AAA的值)的值爲BBB(BBB爲寄存器時取BBB的值)。
      在子程序內部說明的變量稱爲局部變量,局部變量的作用域是其所在的子程序。從彙編角度來看,局部變量就是一個臨時堆棧緩存,用完釋放。
00401000 >/$ 6A 04 push 4 ; /Arg2 = 00000004
00401002 |. 6A 03 push 3 ; |Arg1 = 00000003
00401004 |. E8 16000000 call 0040101F ; /Add.0040101F
00401009 |. 8BD8 mov ebx, eax
0040100B |. 6A 00 push 0 ; /ExitCode = 0
0040100D /. FF15 00204000 call [<&KERNEL32.ExitProcess>] ; /ExitProcess

0040101F /$ 55 push ebp ; 保護現場原先的EBP指針
00401020 |. 8BEC mov ebp, esp ; 設置新的EBP指針,指向棧頂
00401022 |. 83EC 04 sub esp, 4 ; 分配局部變量所有空間
00401025 |. 8B45 0C mov eax, [ebp+C] ; 調用參數2
00401028 |. 8B5D 08 mov ebx, [ebp+8] ; 調用參數1
0040102B |. 895D FC mov [ebp-4] , ebx ; 參數1放局部變量裏
0040102E |. 0345 FC add eax,[ebp-4] ; 參數2與局部變量相加
00401031 |. 83C4 04 add esp, 4 ; 釋放局部變量所有空間
00401034 |. 5D pop ebp ; 恢復現場的ebp指針
00401035 /. C2 0800 retn 8

    在分析彙編代碼時總是要遇到無數的Call,對於這些Call,儘量要根據Call之前傳遞的參數和Call的返回值來判斷Call的功能。傳遞參數的工作必須由函數調用者和函數本身來協調,計算機提供了一種被稱爲棧的數據結構來支持參數傳遞。
    當參數個數多於一個時,按照什麼順序把參數壓入堆棧。函數調用後,由誰來把堆棧恢復。在高級語言中,通過函數調用約定來說明這兩個問題。常見的調用約定有:
【例】按__stdcall約定調用函數test2(Par1, Par2)
push par2 ; 參數2
push par1 ; 參數1
call test2;
{
push ebp ; 保護現場原先的EBP指針
mov ebp, esp ; 設置新的EBP指針,指向棧頂
mov eax, [ebp+0C] ; 調用參數2
mov ebx, [ebp+08] ; 調用參數1
sub esp, 8 ; 若函數要用局部變量,則要在堆棧中留出點空間

add esp, 8 ; 釋放局部變量佔用的堆棧
pop ebp ; 恢復現場的ebp指針
ret 8 ; 返回(相當於ret; add esp,8)
}
其堆棧調用示意圖:

1.2 返回值

在調試程序時,不要見Call就跟進,在Call之前所做的所有PUSH動作以及對寄存器的操作都可能是在給函數傳遞參數,而函數的返回值一般都放在EAX裏面,當然這個值可能是一個指針,指向一個數據結構。從彙編角度來看,主要有如下形式:

1)通過寄存器返回函數值;
2)通過參數按引用方式返回函數值;
3)通過全局變量返回函數值;
4)通過處理器標誌返回函數值;
一般情況下,由retrun操作符返回的值放在EAX寄存器之中,如果結果超過這個寄存器的位容量,那麼該結果的高32位會加載到EDX寄存器中。 如果返回一個含有幾百個字節的結構或者一個近似大小的對象,編譯器會在不告訴程序的情況下,給函數傳遞一個隱式參數,這個指針指向保存的返回結果。
     若a*=4 可以使用shl a,2邏輯左移相當於乘於2的平方
    
;CBW(Convert Byte to Word):         將 AL 擴展爲 AX 
;CWDE(Convert Word to Extended Double): 將 AX 擴展爲 EAX 
;CDQ(Convert Doubleword to Quadword):  將 EAX 擴展爲 64 位數 EDX:EAX 
;CWD(Convert Word to Doubleword):    將 AX 擴展爲 DX:AX 

       若a/=2 可以是使用sar a,1算術右移相當於除於2的1次方

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章