Windows内核 基本汇编指令
1)用VS2010新建Win32 Console Application,工程名為ACECore,工程建立完成后得到打開文件ACECore.cpp,代碼如下:
#include?"stdafx.h"
int?_tmain(int?argc, _TCHAR* argv[])
{
return?0;
}
?
2)用VS2010查看匯編代碼的方法:
1. VC必須處于debug狀態才能看到匯編指令窗口。因此在上面代碼return 0一句上設置斷點。
2.按下F5鍵調試程序,當程序停在斷點處時,打開菜單“Debug”下的“Windows”子菜單,選擇“Disassembly”。這樣就出現反匯編窗口,顯示匯編代碼:
?--- g:/acecore/acecore/acecore.cpp ---------------------------------------------
// ACECore.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
00411350??push????????ebp?
00411351??mov?????????ebp,esp?
00411353??sub?????????esp,0C0h?
00411359??push????????ebx?
0041135A??push????????esi?
0041135B??push????????edi?
0041135C??lea?????????edi,[ebp-0C0h]?
00411362??mov?????????ecx,30h?
00411367??mov?????????eax,0CCCCCCCCh?
0041136C??rep stos????dword ptr es:[edi]?
?????????return 0;
0041136E??xor?????????eax,eax?
}
3)相關匯編指令:
push:把一個32位的操作數壓入堆棧中,這個操作導致esp被減4。esp被稱為棧頂,壓入堆棧的數據越多,這個堆棧也就越堆越高,esp地址就越來越小。在32位平臺上,esp每次減少4(字節)。
?
pop:esp被加4,一個數據出棧。pop的參數一般是一個寄存器,棧頂數據被彈出到這個寄存器中。
?
某些指令會“自動”地操作堆棧:call指令會把它的下一條指令的地址壓入堆棧中,然后跳轉到它調用的函數的開頭處;而單純的jmp是不會這樣做的。同時,ret會自動彈出返回地址。
call的本質相當于push+jmp,ret的本質相當于pop+jmp。
?
不但push、pop、call和ret會操作堆棧,sub和add也可以用于操作堆棧。如果要一次在堆棧中分配4個4字節長整型的空間,那么沒有必要4次調用push,很簡單地把esp減去4*4=16即可。當然,也可以同樣地用add指令來恢復它。
?
lea:取得地址(第二個參數)后放入到前面的寄存器(第一個參數)中。實際上,有時候lea用來做和mov同樣的事情,比如賦值:
lea edi,[ebp?–?0cch]
其中,方括弧表示存儲器,也就是ebp-0cch這個地址所指的存儲器內容。但是lea語法要求取[ebp-0cch]的地址,這個地址就是ebp-0cch,把這個地址放到edi中,也就是說,這等同于:
mov edi,ebp-0cch
但以上的mov指令時錯誤的,因為mov不支持后一個操作數寫成寄存器減去數字。而lea支持,因此可以用lea來代替它。
?
Stos指令:
mov ecx, 30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
?
stos是串存儲指令,它的功能是將eax中的數據放入edi所指的地址中,同時,edi會增加4(字節數)。rep時指令重復執行ecx中填寫的次數。方括弧表示存儲器,這個地址實際上就是edi的內容所指向的地址。這里的stos其實對應的是stosd,其他還有stosb、stosw,分別對應于處理4、1、2個字節。
上面代碼中對堆棧30h*4(=0c0h)個字節初始化為0CCh(也就是int3指令的機器碼),這樣發生意外時執行堆棧里面的內容會引發調試中斷。
?
?
==============lost的分割線===============
?
C函數的參數傳遞過程:
1)C語言程序通過堆棧把參數從函數外部傳入到函數內部,同時,在堆棧中劃分區域來容納函數的內部變量。對于C語言默認的調用方式,函數調用方把參數反序(從右到左)地壓入堆棧中,被調用方把堆棧復原。這些參數對齊到機器字長,16位、32位、64位CPU下分別對齊到2、4、8個字節。
?
2)函數調用規則指的是調用者和被調用者函數間傳遞參數及返回參數的方法,在Windows上,常用的有Pascal方式、WINAPI方式(_stdcall)、C方式(_cdecl)。
?
_cdecl調用規則:
(1)參數從右到左進入堆棧;
(2)在函數返回后,調用者要負責清除堆棧,所以這個調用常會生成較大的可執行文件。
?
_stdcall又稱為WINAPI,其調用規則:
(1)參數從右到左入棧;
(2)被調用的函數在返回前自行清理堆棧,所以生成的代碼比cdecl下。
?
Pascal調用規則:
(1)參數從左到右入棧;
(2)被調用參數在返回前自行清理堆棧;
(3)不支持可變參數的函數調用。
?
此外,在Windows內核中還常見有快速調用方式(_fastcall);在C++編譯的代碼中有this call方式(_thiscall)。
?
3)以如下函數作為例子分析:
void?ACEFunction(int?a,?int?b)
{
???????int?c = a + b;
}
?
int?_tmain(int?argc, _TCHAR* argv[])
{
???????int?a = 1;
???????int?b = 2;
???????ACEFunction(a, b);
???????return?0;
}
標準的C函數調用方式(_cdecl):
(1)調用者把參數反序地壓入堆棧中;
(2)調用函數;
(3)調用者負責把堆棧清理復原。
?
注意:在Windows中,不管哪種調用方式都是返回值放在eax中,然后返回。外部從eax中得到返回值。
?
_cdecl方式下被調用函數需要做以下的事情:
(1)保存ebp。ebp總是被我們用來保存這個函數執行之前的esp的值,執行完畢后,我們用ebp恢復esp;同時,調用此函數的上層函數也用ebp做同樣的事情,所以先把ebp壓入堆棧,函數返回之前彈出,避免ebp被我們改動。
(2)保存esp到ebp中。
上面兩步的代碼如下:
;保存ebp,并把esp放入ebp中,此時ebp和esp同
;都是這次函數調用時的棧頂
00411360??push????????ebp?
00411361??mov?????????ebp,esp?
?
(3)在堆棧中騰出一個區域用來保存局部變量,這就是常說的所謂局部變量時保存在棧空間中的。方法是:把esp減少一個數值,這樣就等于壓入了一堆變量。恢復時,只有把esp恢復成ebp中保存的數據就行。
(4)保存ebx、esi、edi到堆棧中,函數調用完后恢復。
上面兩步對應代碼如下:
;把esp往下移動一個范圍,等于在堆棧中放出一片新
;的空間來保存局部變量
00411363??sub?????????esp,0CCh?
00411369??push????????ebx?
0041136A??push????????esi?
0041136B??push????????edi?
?
(5)把局部變量區域初始化成全0CCCCCCCCh。0CCh實際上是int3指令的機器碼,這是一個斷點中斷指令。因為局部變量不可能被執行,如果執行了,必然程序出錯,這時發生中斷來提示開發者。這時VC編譯Debug版本的特有操作:
0041136C??lea?????????edi,[ebp-0CCh]?
00411372??mov?????????ecx,33h?
00411377??mov?????????eax,0CCCCCCCCh?
0041137C??rep stos????dword ptr es:[edi]?
?
(6)然后做函數里應該做的事情。參數的獲取是ebp+12字節為第二個參數,ebp+8字節為第一個參數(反序入棧),依次增加。最后ebp+4字節處是要返回的地址。
?
(7)恢復ebx、esi、edi、esp、ebp,最后返回:
00411387??pop?????????edi?
00411388??pop?????????esi?
00411389??pop?????????ebx?
0041138A??mov?????????esp,ebp?
0041138C??pop?????????ebp?
0041138D??ret?
?
用VS2010編譯Debug版本,完整的反匯編代碼如下:
--- g:/acecore/acecore/acecore.cpp ---------------------------------------------
// ACECore.cpp : Defines the entry point for the console application.
//
?
#include "stdafx.h"
?
void ACEFunction(int a, int b)
{
00411360??push???ebp??;保存ebp,并把esp放入ebp中。此時ebp與esp相同
00411361??mov???ebp,esp??;都是這次函數調用時的棧頂
00411363??sub???esp,0CCh??;把esp往上移動一個范圍,等于在堆棧中放出一片新
?????????????????????????????????????;的空間用來存儲局部變量
00411369??push???ebx??;下面保存三個寄存器:ebx、esi、edi
0041136A??push???esi?
0041136B??push???edi?
0041136C??lea???edi,[ebp-0CCh] ;原本是想使用“mov edi, ebp-0CCh”,但是mov不支持
????????????????????????????????????????;“-”操作,所以先對ebp-0CCh取內容(即[ebp-0CCh]),
????????????????????????????????????????;再利用lea把[ebp-0CCh]的地址也就是ebp-0CCh放到edi中
????????????????????????????????????????;目的是把保存局部變量的區域(即ebp-0CCh開始的區域)
????????????????????????????????????????;初始化成全部為0CCCCCCCCh。
00411372??mov???ecx,33h?
00411377??mov???eax,0CCCCCCCCh?
0041137C??rep stos????dword ptr es:[edi]??;寫入0CCh指令(中斷)
?????????int c = a + b;
0041137E??mov???eax,dword ptr [a]??;加法操作,從堆棧中取得從外部傳入的參數。
00411381??add???eax,dword ptr [b]??;通過ida反匯編可以看到,其實這兩天指令是:
???????????????????????????????????????????????;mov eax, [ebp+8]
???????????????????????????????????????????????;add eax, [ebp+0Ch]
???????????????????????????????????????????????;參數是通過ebp從堆棧中取得的。這里看到的是
;VC調試器的顯示結果,是為了方便閱讀,
;直接加上了參數名
00411384??mov??dword ptr [c],eax?
}
00411387??pop?????????edi??;恢復edi、dsi、ebx
00411388??pop?????????esi?
00411389??pop?????????ebx?
0041138A??mov?????????esp,ebp??;恢復原來的ebp和esp,讓上一級調用的
0041138C??pop?????????ebp??;?函數可以正常使用
0041138D??ret?
?
主程序中對這個函數的調用方式是:
004123DC??mov?????????eax,dword ptr [b]??;把b, a兩個參數壓入堆棧
004123DF??push????????eax?
004123E0??mov?????????ecx,dword ptr [a]??
004123E3??push????????ecx?
004123E4??call????????ACEFunction (411014h)??;調用函數ACEFunction
004123E9??add?????????esp,8??;恢復堆棧
轉載于:https://www.cnblogs.com/alsofly/p/3623873.html
總結
以上是生活随笔為你收集整理的Windows内核 基本汇编指令的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 技术点
- 下一篇: 计算机:2014年考研大纲解析之数据结构