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

