常用寄存器
寄存器
|
名稱
|
常見用途(未完)
|
eax
|
累加器(Accumulator) |
函數(shù)返回值
|
ebx |
基址寄存器(Base) |
可作為存儲(chǔ)指針來使用
|
ecx
|
計(jì)數(shù)器(Counter)
|
在循環(huán)和字符串操作時(shí),用來控制循環(huán)次數(shù) __thiscall中傳遞this指針 |
edx
|
數(shù)據(jù)寄存器(Data)
|
|
esp
|
堆棧指針寄存器(Stack)
|
|
ebp
|
基地址指針寄存器(Base)
|
|
esi
|
源地址寄存器(Source Index)
|
|
edi
|
目的地址寄存器(Destination)
|
|
常用匯編指令
push |
把一個(gè)32位的操作數(shù)壓入堆棧,這個(gè)操作會(huì)導(dǎo)致esp減4. |
pop |
與push相反,esp加4,一個(gè)數(shù)據(jù)出棧 |
call |
調(diào)用函數(shù)。將下一條指令的地址壓棧,然后跳轉(zhuǎn)到所調(diào)用函數(shù)的開始處,本質(zhì)相當(dāng)于push+jump |
ret |
與call相對(duì)應(yīng),跳轉(zhuǎn)到棧頂數(shù)據(jù)所指的地址,本質(zhì)相當(dāng)于pop+jump。對(duì)于_cdecl 調(diào)用的函數(shù),通常會(huì)在ret之后進(jìn)行exp-[n],用于清理調(diào)用參數(shù)堆棧 |
xor |
異或,常用于清零操作,例如: xor eax eax |
lea |
取得地址(第二個(gè)參數(shù))后放入前面的寄存器中。 |
stosw |
將eax中的數(shù)據(jù)傳送給edi,之后edi+4。常與rep一起使用,用于初始化內(nèi)存段 |
rep |
當(dāng)eax>0時(shí),重復(fù)后面的指令 |
jp,jl,jge |
根據(jù)eax中值與0的關(guān)系跳轉(zhuǎn) |
cmp |
比較指令,將結(jié)果放入eax中,往往是jp,jl,jge之類跳轉(zhuǎn)指令的執(zhí)行條件 |
函數(shù)調(diào)用規(guī)則
調(diào)用方式
|
簡(jiǎn)要說明
|
堆棧清理 |
參數(shù)傳遞規(guī)則
|
_cdecl |
C 編譯器的默認(rèn)調(diào)用規(guī)則 |
Caller
|
從右到左 |
_stdcall |
又稱為WINAPI |
Callee
|
從右到左 |
__thiscall |
C++成員函數(shù)調(diào)用方式
|
Callee |
this放入ecx,其他從右到左 |
__fastcall
|
|
Callee
|
前兩個(gè)等于或者小于DWORD大小的參數(shù)放入ecx和edx,其他參數(shù)從右到左
|
_cdecl調(diào)用通常的asm代碼:
被調(diào)用方:
1.保存ebp。ebp總是用來保存這個(gè)函數(shù)執(zhí)行之前的esp值。執(zhí)行完畢之后,我們用ebp回復(fù)esp;同時(shí),調(diào)用此函數(shù)的上層函數(shù)也用ebp做同樣的事情。
2.保存esp到ebp中。
;保存ebp,并把esp放入ebp中,此時(shí)ebp與esp都為這次函數(shù)調(diào)用的棧頂
push ebp
mov ebp,esp
3.在堆棧中預(yù)留一個(gè)區(qū)域用于保存局部變量。方法是將esp減少一個(gè)數(shù)值,這樣就等于壓入了一堆變量。要恢復(fù)的時(shí)候直接把esp回復(fù)成ebp保存的數(shù)據(jù)就可以了。
4.保存ebx、esi、edi到堆棧中,函數(shù)調(diào)用完成后恢復(fù)。
;把esp往下移動(dòng)一個(gè)范圍,等于在堆棧中預(yù)留一片新的空間來保存局部變量
sub esp,010h
push ebx
push esi
push edi
5.(debug版)把局部變量全部初始化為0xcccccccch.
;將保存局部變量的區(qū)域全部初始化為0xcccccccch
lea edi,[ebp-010h]
mov ecx,33h
mov eax,0xcccccccch
rep stos dword ptr [edi]
6.然后執(zhí)行函數(shù)的具體邏輯。傳入?yún)?shù)的獲取為:ebp+4為函數(shù)的返回地址;ebp+8為第一個(gè)參數(shù),ebp+12為第二個(gè)參數(shù),以此類推。
7.回復(fù)ebx、esi、edi、esp、ebp,最后返回。如果有返回值,在返回之前將保存在eax中,供調(diào)用方式用。
pop edi ;恢復(fù)edi、esi、ebx
pop esi
pop ebx
mov esp, ebp ;恢復(fù)原來的ebp和esp
pop ebp
ret
調(diào)用方:
mov eax,dword ptr [b]
push eax
move ecx,dword ptr [a]
push ecx
call myfunction
add esp,8 ;回復(fù)堆棧
常見的基礎(chǔ)代碼結(jié)構(gòu)
for循環(huán)
for(int i = 0; i < 20; ++i )
0040B93E mov dword ptr [i],0
0040B945 jmp wmain+30h (40B950h)
0040B947 mov eax,dword ptr [i]
0040B94A add eax,1
0040B94D mov dword ptr [i],eax
0040B950 cmp dword ptr [i],14h
0040B954 jge wmain+38h (40B958h)
{
}
0040B956 jmp wmain+27h (40B947h)
可以看到主循環(huán)主要由這么幾條指令來實(shí)現(xiàn):mov進(jìn)行初始化;jmp跳過修改循環(huán)變量的代碼;cmp實(shí)現(xiàn)跳轉(zhuǎn)判斷;jge根據(jù)條件跳轉(zhuǎn)。用jmp回到修改循環(huán)變量的代碼進(jìn)行下一次循環(huán)。大體結(jié)構(gòu)如下:
mov <循環(huán)變量>,<初始值> ;給循環(huán)變量賦值
jmp A ;跳到第一次循環(huán)處
A: (改動(dòng)循環(huán)變量) ;修改循環(huán)變量

B: cmp <循環(huán)變量>,<限制變量> ;檢查循環(huán)變量
jge 跳出循環(huán)
(循環(huán)體)

jmp A ;跳回修改循環(huán)變量
do循環(huán)
int i = 0;
0040B93E mov dword ptr [i],0
do
{
++i;
0040B945 mov eax,dword ptr [i]
0040B948 add eax,1
0040B94B mov dword ptr [i],eax
} while (i<10);
0040B94E cmp dword ptr [i],0Ah
0040B952 jl wmain+25h (40B945h)
上面的do循環(huán)就是用一個(gè)簡(jiǎn)單的條件比較指令跳轉(zhuǎn)回去:
cmp <循環(huán)變量><限制變量>
jl <循環(huán)開始>
while循環(huán)
int i = 0;
0040B93E mov dword ptr [i],0
while (i<10)
0040B945 cmp dword ptr [i],0Ah
0040B949 jge wmain+36h (40B956h)
{
++i;
0040B94B mov eax,dword ptr [i]
0040B94E add eax,1
0040B951 mov dword ptr [i],eax
}
0040B954 jmp wmain+25h (40B945h)
while要復(fù)雜一些,因?yàn)閣ile除了開始的時(shí)候判斷循環(huán)條件之外,后面還要有一條無條件跳轉(zhuǎn)指令:
A: cmp <循環(huán)變量>,<限制變量>
jge B
(循環(huán)體)

jmp A
B: (跳出循環(huán))
if-else判斷分支
int i = 0;
0040B93E mov dword ptr [i],0
int j = 0;
0040B945 mov dword ptr [j],0
if ( i < 10 )
0040B94C cmp dword ptr [i],0Ah
0040B950 jge wmain+3Bh (40B95Bh)
{
j = 10;
0040B952 mov dword ptr [j],0Ah
0040B959 jmp wmain+51h (40B971h)
}
else if (i < 20 )
0040B95B cmp dword ptr [i],14h
0040B95F jge wmain+4Ah (40B96Ah)
{
j = 20;
0040B961 mov dword ptr [j],14h
}
else
0040B968 jmp wmain+51h (40B971h)
{
j = 30;
0040B96A mov dword ptr [j],1Eh
}
return 0;
0040B971 xor eax,eax
if 判斷都是使用cmp加上條件跳轉(zhuǎn)指令。
所以開始的反匯編為:
if ( i < 10 )
0040B94C cmp dword ptr [i],0Ah ;判斷點(diǎn)
0040B950 jge wmain+3Bh (40B95Bh) ;跳轉(zhuǎn)到下一個(gè)else if
else if和else的特點(diǎn)是,在開始的地方都有一條無條件跳轉(zhuǎn)指令,跳轉(zhuǎn)到判斷結(jié)束處,阻止前面的分支執(zhí)行結(jié)束后,直接進(jìn)入這個(gè)分支的可能,這個(gè)分支執(zhí)行的唯一條件為前面的判斷不滿足。else則在jmp之后直接執(zhí)行操作,而else if則開始重復(fù)if之后的操作,用cmp比較,然后用條件質(zhì)量進(jìn)行跳轉(zhuǎn)。
0040B959 jmp wmain+51h (40B971h) ;跳轉(zhuǎn)到判斷塊外
}
else if (i < 20 )
0040B95B cmp dword ptr [i],14h
0040B95F jge wmain+4Ah (40B96Ah) ;比較,條件跳轉(zhuǎn),目標(biāo)為下一個(gè)分支
{
j = 20;
0040B961 mov dword ptr [j],14h
}
switch-case 判斷分支
switch的特點(diǎn)是有多個(gè)判斷。因?yàn)閟witch顯然不會(huì)判斷大于小于,所以都是je,分別跳轉(zhuǎn)到每個(gè)case處,最有一個(gè)是無條件跳轉(zhuǎn),直接跳到default處。
對(duì)于break,會(huì)增加一個(gè)無條件跳轉(zhuǎn)語句,跳轉(zhuǎn)至結(jié)尾
int i = 0;
0040B93E mov dword ptr [i],0
int j = 0;
0040B945 mov dword ptr [j],0
switch (i)
0040B94C mov eax,dword ptr [i]
0040B94F mov dword ptr [ebp-0DCh],eax
0040B955 cmp dword ptr [ebp-0DCh],0
0040B95C je wmain+49h (40B969h) ;判斷case 1
0040B95E cmp dword ptr [ebp-0DCh],1
0040B965 je wmain+52h (40B972h) ;判斷case 2
0040B967 jmp wmain+59h (40B979h) ;跳轉(zhuǎn)到default

{
case 0:
j = 0;
0040B969 mov dword ptr [j],0
break; ;跳轉(zhuǎn)到結(jié)束
0040B970 jmp wmain+60h (40B980h)
case 1:
j = 1;
0040B972 mov dword ptr [j],1
default:
j = 3;
0040B979 mov dword ptr [j],3
}

return 0;
0040B980 xor eax,eax
所以如果看到有多個(gè)連續(xù)的
cmp
je
標(biāo)志著可能是swith語句
訪問結(jié)構(gòu)體數(shù)組成員
對(duì)于以下代碼:
struct A


{
int a;
int b;
int c;
};

int wmain(int argc, wchar_t* argv[])


{
A ar[3];
for (int i=0;i<3;++i)

{
ar[i].a = 0;
ar[i].b = 0;
ar[i].c = 0;
}

return 0;
}
for循環(huán)中所對(duì)應(yīng)的匯編為
ar[i].a = 0;
0040B956 mov eax,dword ptr [i] ;訪問第i個(gè)數(shù)據(jù)
0040B959 imul eax,eax,0Ch ;0ch為結(jié)構(gòu)體的大小,這里得到訪問第i個(gè)機(jī)構(gòu)體的地址偏移
0040B95C mov dword ptr ar[eax],0 ;取得第i個(gè)結(jié)構(gòu)體的第一個(gè)元素地址
ar[i].b = 0;
0040B964 mov eax,dword ptr [i]
0040B967 imul eax,eax,0Ch
0040B96A mov dword ptr [ebp+eax-24h],0
ar[i].c = 0;
0040B972 mov eax,dword ptr [i]
0040B975 imul eax,eax,0Ch
0040B978 mov dword ptr [ebp+eax-20h],0
對(duì)于結(jié)構(gòu)體數(shù)組的訪問有個(gè)很明顯的特征:使用imul取得某個(gè)數(shù)組元素的地址偏移,然后在加上所要訪問結(jié)構(gòu)體成員的地址偏移。同時(shí),大多數(shù)情況下結(jié)構(gòu)的的大小都是在編譯期決定的,imul的最后一個(gè)參數(shù)會(huì)是個(gè)常量。
閱讀匯編代碼的一些技巧
1.將指令分類:
首先F(function)類指令:是函數(shù)調(diào)用相關(guān)代碼,這些代碼用于函數(shù)或者作為一個(gè)函數(shù)數(shù)被調(diào)用。幾乎凡是堆棧操作(備份集陳啟或者壓入?yún)?shù))可全部歸入此類。此外還有call指令、堆棧恢復(fù)。
然后C(control)類指令 :設(shè)計(jì)判斷和跳轉(zhuǎn)指令,以及對(duì)循環(huán)變量操作的指令。這些代碼用于循環(huán)、判斷語句。
剩余D(data)類指令:數(shù)據(jù)處理指令,應(yīng)該不包含函數(shù)調(diào)用,多半不含有堆操作,也不會(huì)含有跳轉(zhuǎn)。
2.翻譯D類指令。
3.表達(dá)式的合并與控制流程的結(jié)合。
Reference:
學(xué) Win32 匯編[29] - 串指令: MOVS*、CMPS*、SCAS*、LODS*、REP、REPE、REPNE 等
《天書夜讀-從匯編語言到Windows內(nèi)核編程》