重在動手,重在總結!

  代碼如下:

    #include "stdlib.h"

    int sum(int a,int b,int m,int n)
    {
         return a+b;
    }

    void main()
    {
         int result = sum(1,2,3,4);
         system("pause");
    }

  有四個參數的sum函數,接著在main方法中調用sum函數。在debug環境下,單步調試如下:

11:   void main()
12:   {
00401060   push        ebp

;保存ebp,執行這句之前,ESP = 0012FF4C EBP = 0012FF88

;執行后,ESP = 0012FF48 EBP = 0012FF88,ESP減小,EBP不變
00401061   mov         ebp,esp

;將esp放入ebp中,此時ebp和esp相同,即執行后ESP = 0012FF48 EBP = 0012FF48

;原EBP值已經被壓棧(位于棧頂),而新的EBP又恰恰指向棧頂。
;此時EBP寄存器就已經處于一個非常重要的地位,該寄存器中存儲著棧中的一個地址(原EBP入棧后的棧頂),
;從該地址為基準,向上(棧底方向)能獲取返回地址、參數值(假如main中有參數,“獲取參數值”會比較容易理解,

;不過在看下邊的sum函數調用時會有體會的),向下(棧頂方向)能獲取函數局部變量值,
;而該地址處又存儲著上一層函數調用時的EBP值!

00401063   sub         esp,44h

;把esp往上移動一個范圍
;等于在棧中空出一片空間來存局部變量
;執行這句后ESP = 0012FF04 EBP = 0012FF48

00401066   push        ebx
00401067   push        esi
00401068   push        edi

;保存三個寄存器的值
00401069   lea         edi,[ebp-44h]

;把ebp-44h加載到edi中,目的是保存局部變量的區域
0040106C   mov         ecx,11h
00401071   mov         eax,0CCCCCCCCh
00401076   rep stos    dword ptr [edi]

;從ebp-44h開始的區域初始化成全部0CCCCCCCCh,就是int3斷點,初始化局部變量空間

;REP           ;CX不等于0 ,則重復執行字符串指令

;格式: STOS OPRD

;功能: 把AL(字節)或AX(字)中的數據存儲到DI為目的串地址指針所尋址的存儲器單元中去.指針DI將根據DF的值進行自動

;調整. 其中OPRD為目的串符號地址.

 

以上的語句就是在棧中開辟一塊空間放局部變量
;然后把這塊空間都初始化為0CCCCCCCCh,就是int3斷點,一個中斷指令。
;因為局部變量不可能被執行,執行了就會出錯,這時候發生中斷提示開發者。

13:       int result = sum(1,2,3,4);
00401078   push        4
0040107A   push        3
0040107C   push        2
0040107E   push        1

;各個參數入棧,注意查看寄存器ESP值的變化

;亦可以看到參數入棧的順序,從右到左

;變化為:ESP = 0012FEF8-->ESP = 0012FEF4-->ESP = 0012FEF0-->ESP = 0012FEEC-->ESP = 0012FEE8
00401080   call        @ILT+15(boxer) (00401014)

;調用sum函數,可以按F11跟進

;注:f10(step over),單步調試,遇到函數調用,直接執行,不會進入函數內部

;f11(step into),單步調試,遇到函數調用,會進入函數內部

;shift+f11(step out),進入函數內部后,想從函數內部跳出,用此快捷方式

;ctrl+f10(run to cursor),呵呵,看英語注釋就應該知道是什么意思了,不再解釋
00401085   add         esp,10h

調用完函數后恢復/釋放棧,執行后ESP = 0012FEF8,與sum函數的參數入棧前的數值一致

00401088   mov         dword ptr [ebp-4],eax

;將結果存放在result中,原因詳看最后有關ss的注釋
14:       system("pause");
0040108B   push        offset string "pause" (00422f6c)
00401090   call        system (0040eed0)
00401095   add   esp ,4

;有關system(“pause”)的處理,此處不討論

15:   }
00401098   pop         edi
00401099   pop         esi
0040109A   pop         ebx

;恢復原來寄存器的值,怎么“吃”進去,怎么“吐”出來
0040109B   add         esp,44h

;恢復ESP,對應上邊的sub esp,44h
0040109E   cmp         ebp,esp

;檢查esp是否正常,不正常就進入下邊的call里面debug
004010A0   call        __chkesp (004010b0)

;處理可能出現的堆棧異常,如果有的話,就會陷入debug
004010A5   mov         esp,ebp
004010A7   pop         ebp

;恢復原來的esp和ebp,讓上一個調用函數正常使用
004010A8   ret

;將返回地址存入eip,轉移流程

 

;如果函數有返回值,返回值將放在eax返回(這就是很多軟件給秒殺爆破的原因了,因為eax的返回值是可以改的)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

;以上即是主函數調用的反匯編過程,下邊來看調用sum函數的過程:

;上邊有說在00401080   call        @ILT+15(boxer) (00401014)這一句處,用f11單步調試,f11后如下句:

00401014   jmp         sum (00401020)

;即跳轉到sum函數的代碼段中,再f11如下:

6:    int sum(int a,int b,int m,int n)
7:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]

;可見,上邊幾乎與主函數調用相同,每一步不再贅述,可對照上邊主函數調用的注釋
8:        return a+b;
00401038   mov         eax,dword ptr [ebp+8]

;取第一個參數放在eax
0040103B   add         eax,dword ptr [ebp+0Ch]

;取第二個參數,與eax中的數值相加并存在eax中
9:    }
0040103E   pop         edi
0040103F   pop         esi
00401040   pop         ebx
00401041   mov         esp,ebp
00401043   pop         ebp
00401044   ret

;收尾操作,比前邊只是少了檢查esp操作罷了

 

有關ss部分的注釋:

;一般而言,ss:[ebp+4]處為返回地址
;ss:[ebp+8]處為第一個參數值(這里是a),ss:[ebp+0Ch]處為第二個參數(這里是b,這里8+4=12=0Ch)
;ss:[ebp-4]處為第一個局部變量(如main中的result),ss:[ebp]處為上一層EBP值
;ebp和函數返回值是32位,所以占4個字節

《完》