一、簡要復(fù)習(xí)常用的匯編指令

1.堆棧相關(guān)指令

push:把一個(gè)32位的操作數(shù)壓入堆棧中。這個(gè)操作導(dǎo)致esp被減4.(32位平臺(tái))。esp被形象地稱為棧頂。我們認(rèn)為頂部是地址小的區(qū)域,那么,壓入堆棧的數(shù)據(jù)越多,這個(gè)堆棧也就越堆越高,esp也就越來越少。

 

pop:相反,esp被加4,一個(gè)數(shù)據(jù)出棧。pop的參數(shù)一般是一個(gè)寄存器,棧頂?shù)臄?shù)據(jù)被彈出到這個(gè)寄存器中。

 

sub:減法。第一個(gè)參數(shù)就是被減數(shù)所在的寄存器,第二個(gè)參數(shù)是減數(shù)。

 

add:加法。

 

ret:返回。相當(dāng)于跳轉(zhuǎn)回調(diào)用函數(shù)的地方。(對(duì)應(yīng)的call指令來調(diào)用函數(shù),返回到call之后的下一條指令。)

 

call:調(diào)用函數(shù)。

 

call指令會(huì)把它的下一條指令的地址壓入堆棧中,然后跳轉(zhuǎn)到它調(diào)用函數(shù)的開頭處。而單純的jmp是不會(huì)這樣做的。同時(shí),ret會(huì)自動(dòng)彈出返回地址。

call的本質(zhì)相當(dāng)于push+jmp。ret的本質(zhì)相當(dāng)于pop+jmp。

 

如果我要一次在堆棧中分配4個(gè)4字節(jié)長整型的空間,那么沒有必要4次調(diào)用push,很簡單地把esp減去4*4=16即可。當(dāng)然,也可以同樣的用add指令來恢復(fù)它。這常常用于分配函數(shù)局部變量空間,因?yàn)镃語言函數(shù)的局部變量保存在棧里。

 

2.數(shù)據(jù)傳送指令

mov:數(shù)據(jù)移動(dòng)。第一個(gè)參數(shù)是目的,第二個(gè)參數(shù)是來源。在C語言中相當(dāng)于賦值號(hào)。

 

xor:異或。xor eax,eax這樣的操作常常用來代替mov eax,0.好處是速度更快,占用字節(jié)數(shù)更少。

 

lea:取得地址(第二個(gè)參數(shù))后放入到前面的寄存器(第一個(gè)參數(shù))中。

lea edi,[ebp-0cch]  //方括號(hào)表示存儲(chǔ)器,也就是ebp-0cch這個(gè)地址所指的存儲(chǔ)器內(nèi)容。但是lea要求取[ebp-0cch]的地址,那么地址就是ebp-0cch,這個(gè)地址將被放入到edi中。等同于mov edi,ebp-0cch的效果,但是mov不支持后一個(gè)操作數(shù)寫成寄存器減去數(shù)字。但是lea支持。

 

mov  ecx,30h

mov  eax,0CCCCCCCh

rep   stos dword ptr es:[edi]

stos把eax中的數(shù)據(jù)放入edi所指的地址中,同時(shí),edi加4,。所以上面代碼的意思就是對(duì)堆棧中30h*4(0c0h)個(gè)字節(jié)初始化為0cch(也就是int3指令的機(jī)器碼),這樣發(fā)生意外時(shí)執(zhí)行堆棧里面的內(nèi)容會(huì)引發(fā)調(diào)試中斷。

 

3.跳轉(zhuǎn)與比較指令

jmp:無條件跳轉(zhuǎn)。

jg:大于的時(shí)候跳轉(zhuǎn)。

jl:小于的時(shí)候跳轉(zhuǎn)。

jge:大于等于的時(shí)候跳轉(zhuǎn)。

cmp:比較。往往是jg、jl、jge之類的條件跳轉(zhuǎn)指令的執(zhí)行條件。

 

二、C函數(shù)的參數(shù)傳遞過程

對(duì)于C程序默認(rèn)的調(diào)用方式,堆??偸钦{(diào)用方把參數(shù)反序(從右到左)地壓入堆棧中,被調(diào)用方把堆棧復(fù)原。

函數(shù)調(diào)用規(guī)則:

image

在用C語言所寫的程序中,堆棧用于傳遞函數(shù)參數(shù)。寫一個(gè)簡單的函數(shù)如下:

void myfunction(int a,int b)
{
          int c = a+b;
}

這是標(biāo)準(zhǔn)的C函數(shù)調(diào)用方式。其過程是:

①調(diào)用者把參數(shù)反序地壓入堆棧中。

②調(diào)用函數(shù)。

③調(diào)用者把堆棧清理復(fù)原。

這就是C編譯器默認(rèn)的_cdecl方式,而Windows API一般采用的_stdcall則是被調(diào)用者恢復(fù)堆棧(可變參數(shù)函數(shù)調(diào)用除外)。

至于返回值都是寫入eax中,然后返回的。

在Windows中,不管哪種調(diào)用方式都是返回值放在eax中,然后返回。外部從eax中得到返回值。

_cdecl方式下被調(diào)用函數(shù)需要做以下一些事情。

(1)保存ebp。ebp總是被我們用來保存這個(gè)函數(shù)執(zhí)行之前的esp的值。執(zhí)行完畢之后,我們用ebp恢復(fù)esp;同時(shí),調(diào)用此函數(shù)的上層函數(shù)也用ebp做同樣的事情。所以先把ebp壓入堆棧,返回之前彈出,避免ebp被我們改動(dòng)。

(2)保存esp到ebp中。

上面兩步的代碼如下:

;保存ebp,并把esp放入ebp中,此時(shí)ebp與esp同都是這次函數(shù)調(diào)用時(shí)的棧頂
push ebp
mov ebp,esp

(3)在堆棧中騰出一個(gè)區(qū)域用來保存局部變量,這就是常說的所謂局部變量是保存在??臻g中的。方法是:把esp減少一個(gè)數(shù)值,這樣就等于壓入了一堆變量。要恢復(fù)時(shí),只要把esp恢復(fù)成ebp中保存的數(shù)據(jù)就可以了。

(4)保存ebx、esi、edi到堆棧中,函數(shù)調(diào)用完后恢復(fù)。

對(duì)應(yīng)的代碼如下:

;把esp往下移動(dòng)一個(gè)范圍,等于在堆棧中放出一片新的空間用來存局部變量
sub esp,0cch
push ebx  ;下面保存三個(gè)寄存器:ebx、esi、edi
push esi
push edi

(5)把局部變量區(qū)域初始化成全0cccccccch。0cch實(shí)際是int 3指令的機(jī)器碼,這是一個(gè)斷點(diǎn)中斷指令。因?yàn)榫植孔兞坎豢赡鼙粓?zhí)行,如果執(zhí)行了,必然程序有錯(cuò),這時(shí)發(fā)生中斷來提示開發(fā)者。這是VC編譯Debug版本的特有操作。相關(guān)代碼如下:

lea edi,[ebp-0cch] ;本來是要mov edi,ebp-0cch,但是mov不支持-操作所以對(duì)ebp-0cch取內(nèi)容,而lea把內(nèi)容的地址,也就是ebp-0cch加載到edi中。目的是把保存局部變量的區(qū)域(從ebp-0cch開始的區(qū)域)初始化成全部0cccccccch
mov ecx,33h
mov eax,0cccccccch
rep stos dword ptr [edi] ;串寫入

(6)然后做函數(shù)里應(yīng)該做的事情。參數(shù)的獲取是ebp+8字節(jié)為第一個(gè)參數(shù),ebp+12為第二個(gè)參數(shù),依次增加。ebp+4字節(jié)處是要返回的地址。

(7)恢復(fù)ebx、esi、edi、esp、ebp,最后返回。代碼如下:

pop edi   ;恢復(fù)edi、esi、ebx
pop esi
pop ebx
mov esp,ebp  ;恢復(fù)原來的ebp和esp,讓上一個(gè)調(diào)用的函數(shù)正常使用
pop ebp
ret

為了簡單起見,我的函數(shù)沒有返回值。如果要返回值,函數(shù)應(yīng)該在返回之前,把返回值放入eax中。外部通過eax得到返回值。

 

三、C語言的循環(huán)反匯編

1、for循環(huán)
   下面是一段C語言的代碼,我們的目的是來看其反匯編的結(jié)果:
   int myfunction(int a,int b)
    {
         int c = a+b;
         int i;
         for(i=0;i<50;i++)
         {
             c = c+i;
         }
         return c;
    } 

前面的反匯編暫時(shí)不理它,這里從for的地方開始反匯編,結(jié)果如下:
      for(i=0;i<50;i++)
00412BC7  mov        dword ptr [i],0   // i=0; 給循環(huán)變量賦初值
00412BCE  jmp         myfunction+39h (412BD9h)// 跳到第一次循環(huán)處
  >  00412BD0  mov        eax,dword ptr [i] 
  |   00412BD3  add         eax,1  // i++;修改循環(huán)變量
  |   00412BD6  mov        dword ptr [i],eax 
  |   00412BD9  cmp        dword ptr [i],32h //  比較 i 與50的關(guān)系, 檢查循環(huán)條件
  |   00412BDD  jge          myfunction+4Ah (412BEAh)  // 當(dāng) i>=50 [即 !(i<50) ] 時(shí)則跳出循環(huán)
  |   {
  |      c = c+i;
  |   00412BDF  mov         eax,dword ptr [c]  // 變量 c
  |   00412BE2  add         eax,dword ptr [i]   // 變量 i
  |   00412BE5  mov         dword ptr [c],eax  // c=c+i;
  |   }
  <  00412BE8  jmp         myfunction+30h (412BD0h)  // 跳回去修改循環(huán)變量
      00412BEA  mov         eax,dword ptr [c] 
     }
      可以看到for循環(huán)主要用這么幾條指令來實(shí)現(xiàn):mov進(jìn)行初始化。jmp跳過循環(huán)變量改變代碼。cmp實(shí)現(xiàn)條件判斷,jge根據(jù)條件跳轉(zhuǎn)。
用jmp回到循環(huán)改變代碼進(jìn)行下一次循環(huán)。所以for結(jié)構(gòu)有以下的顯著特征:
           mov <循環(huán)變量>,<初始值>   ; 給循環(huán)變量賦初值
       jmp B       ;跳到第一次循環(huán)處
    A: (改動(dòng)循環(huán)變量)       ;修改循環(huán)變量。
        …
    B: cmp <循環(huán)變量>,<限制變量>  ;檢查循環(huán)條件
       jgp  跳出循環(huán)
       (循環(huán)體)
          …               
        jmp A       ;跳回去修改循環(huán)變量 

2、do循環(huán) 
  再看一下do循環(huán),因?yàn)?do循環(huán)沒有修改循環(huán)變量的部分,所以比for循環(huán)要簡單一些。
        do
           {
                  c = c+i;
            00411A55  mov        eax,dword ptr [c] 
            00411A58  add         eax,dword ptr [i] 
            00411A5B  mov         dword ptr [c],eax 
            } while(c< 100);
00411A5E  cmp        dword ptr [c],64h  
            00411A62  jl           myfunction+35h (411A55h) 
            return c;
   do循環(huán)就是一個(gè)簡單的條件跳轉(zhuǎn)回去。只有兩條指令:
   cmp <循環(huán)變量>,<限制變量>
    jl <循環(huán)開始點(diǎn)> 

3、while循環(huán)
        while(c<100){
            00411A55  cmp         dword ptr [c],64h
            00411A59  jge         myfunction+46h (411A66h) 
               c = c+i;
            00411A5B  mov         eax,dword ptr [c] 
            00411A5E  add         eax,dword ptr [i] 
            00411A61  mov         dword ptr [c],eax 
              }
       00411A64  jmp         myfunction+35h (411A55h)
            return c;
    很明顯,我們會(huì)發(fā)現(xiàn)while要更復(fù)雜一點(diǎn)。因?yàn)閣hile除了開始的時(shí)候判斷循環(huán)條件之外,后面還必須有一條無條件跳轉(zhuǎn)回到循環(huán)開始的地方,共用三條指令實(shí)現(xiàn):
             A: cmp <循環(huán)變量>,<限制變量>
                 jge  B
                ( 循環(huán)體)
                …
           jmp A
             B: (循環(huán)結(jié)束了) 

四、C語言判斷與分支反匯編

1、if-else 語句
為了觀察其匯編語句,下面是一個(gè)簡單的if判斷結(jié)構(gòu): 
      if(a>0 && a<10)
         {
               printf(“a>0″);
         }
         else if( a>10 && a<100)
         {
               printf(“a>10 && a<100″);
         }
         else 
         {
               printf(“a>10 && a<100″);
          }
    if 判斷都是使用cmp再加上條件跳轉(zhuǎn)指令。對(duì)于if( A && B)的情況,一般都是使用否決法。如果A不成立,立刻跳下一個(gè)分支。依次,如果 B 不成立,同樣跳下一分支。
          cmp 條件
          jle 下一個(gè)分支
  所以開始部分的反匯編為:
         if(a>0 && a<10)
         00411A66  cmp       dword ptr [c],0 
         00411A6A  jle         411A81h   ; 跳下一個(gè)else if的判斷點(diǎn)
      00411A6C  cmp       dword ptr [c],0Ah 
         00411A70  jge        411A81h   ; 跳下一個(gè)else if的判斷點(diǎn)
      {
            printf(“a>0″);
         00411A72  push      offset string “a>0″ (4240DCh) 
         00411A77  call        @ILT+1300(_printf) (411519h) 
         00411A7C  add       esp,4 
         }
    else if 的和 else 的特點(diǎn)是,開始都有一條無條件跳轉(zhuǎn)到判斷結(jié)束處,阻止前面的分支執(zhí)行結(jié)束后,直接進(jìn)入這個(gè)分支。這個(gè)分支能執(zhí)行到的唯一途徑只是,前面的判斷條件不滿足。
   else 則在jmp之后直接執(zhí)行操作。而else if則開始重復(fù)if之后的操作,用cmp比較,然后用條件跳轉(zhuǎn)指令時(shí)行跳轉(zhuǎn)。
       else if( a>10 && a<100)
          00411A7F  jmp          411AA9h     ;直接跳到判斷塊外
       00411A81  cmp         dword ptr [c],0Ah         ;比較+條件跳轉(zhuǎn),目標(biāo)為下一個(gè)分支處
       00411A85  jle          411A9Ch 
          00411A87  cmp         dword ptr [c],64h 
          00411A8B  jge          411A9Ch 
          {
                printf(“a>10 && a<100″);
          00411A8D  push        offset string “a>10 && a<100″ (424288h) 
          00411A92  call          @ILT+1300(_printf) (411519h) 
          00411A97  add         esp,4 
           }
          else 
          00411A9A  jmp        411AA9h   ;這里是else,所以只有簡單的一條跳轉(zhuǎn)。
       {
             printf(“a>10 && a<100″);
          00411A9C  push        offset string “a>10 && a<100″ (424288h) 
          00411AA1  call          @ILT+1300(_printf) (411519h) 
          00411AA6  add          esp,4 
           }
          return c;

2、switch-case 語句
   switch 的特點(diǎn)是有多個(gè)判斷。因?yàn)?swtich 顯然不用判斷大于小于,所以都是je(因此,C語言中switch語句不支持float類型的變量),分別跳到每個(gè)case處。最后一個(gè)是無條件跳轉(zhuǎn),直接跳到default處。以下的代碼: 
          switch(a)
           {
            case 0:
                 printf(“a>0″);
            case 1:
            {
                 printf(“a>10 && a<100″);
                 break;
            }
           default:
                  printf(“a>10 && a<100″);
           }
反匯編的switch(a)
          00411A66  mov         eax,dword ptr [a] 
          00411A69  mov         dword ptr [ebp-0E8h],eax 
          00411A6F  cmp         dword ptr [ebp-0E8h],0  // case 0:
          00411A76  je            411A83h 
          00411A78  cmp         dword ptr [ebp-0E8h],1  // case 1:
          00411A7F  je            411A90h 
          00411A81  jmp         411A9Fh  // default:
          {
             …
   顯然是比較a 是否是0、1這兩個(gè)數(shù)字。匯編指令先把a(bǔ)移動(dòng)到[ebp-0E8h]這個(gè)地址,然后再比較,這是調(diào)試版本編譯的特點(diǎn)??赡苁菫榱朔乐怪苯硬僮鞫褩6鴮?dǎo)致堆棧破壞?最后一條直接跳轉(zhuǎn)到default處。當(dāng)然,如果沒有default,就會(huì)跳到swtich{}之外。
   從這里我們可以發(fā)現(xiàn):switch語句里,完成“比較判斷”的指令會(huì)與“case”指令的兩部分,在匯編中,不是按照C語句逐句翻譯的,而是分開為兩個(gè)指令模塊來實(shí)現(xiàn)的!
       case 0:
                printf(“a>0″);
          00411A83  push        offset string “a>0″  (4240DCh) 
          00411A88  call          @ILT+1300(_printf) (411519h) 
          00411A8D  add         esp,4 
          case 1:
          {
               printf(“a>10 && a<100″);
          00411A90  push        offset string “a>10 && a<100″ (424288h) 
          00411A95  call          @ILT+1300(_printf) (411519h) 
          00411A9A  add         esp,4 
              break;
          00411A9D  jmp         myfunction+8Ch (411AACh) 
          }
          default:
               printf(“a>10 && a<100″);
          00411A9F  push        offset string “a>10 && c<100″ (424288h) 
          00411AA4  call          @ILT+1300(_printf) (411519h) 
          00411AA9  add         esp,4 
           }
    至于case 和 default分支中,如果有break,則會(huì)增加一個(gè)無條件跳轉(zhuǎn)匯編指令。若沒有break,則就沒有任何循環(huán)控制代碼。

本文鏈接:http://www.blogfshare.com/c-disassembling.html

  哈爾濱品用軟件有限公司致力于為哈爾濱的中小企業(yè)制作大氣、美觀的優(yōu)秀網(wǎng)站,并且能夠搭建符合百度排名規(guī)范的網(wǎng)站基底,使您的網(wǎng)站無需額外費(fèi)用,即可穩(wěn)步提升排名至首頁。歡迎體驗(yàn)最佳的哈爾濱網(wǎng)站建設(shè)。