FASM--Win32彙編學習2
在本課中,我們將用組合語言寫一個 Windows 程式,程式運行時將彈出一個訊息框並顯示"Win32 fasm"。
理論:
Windows 為我們編程人員提供了大量的資源。其中最重要的就是Windows API (Application Programming Interface)。Windows API
是一組強大的函數。它們本身駐紮在Windows中供人們隨時使用。這些函數大部分被包含在幾個動態連結程式庫中,譬如 “Kernel32.dll”, “user32.dll”, “GDI32.DLL”,Kernel32.dll 中的函數主要供我們處理記憶體管理和進程調度。user32.dll中的函數主要供我們控制使用者介面。GDI32.DLL中的函數主要供我們處理圖形方面。除了以上這3個動態連結程式庫,你可以包含其他的動態連結程式庫中的函數。當然你必須要有足夠的這些動態連結程式庫的資料。
動態連結程式庫,顧名思義,這些API的代碼本身不包含在Windows可執行檔中,而是在使用時才被載入。為了讓應用程式在啟動並執行時候找到這些函數。就必須首先把有關重定位的資訊嵌入到可執行檔中。這些資訊存在引入庫中。由連結器在連結程式的時候將相關資訊找出嵌入到可執行檔中。
我們FASM是通過宏來構建引入表的,所以我們需要自己通過Fasm的提供宏來包含相關的DLL和引入相應的函數。
當我們的應用程式在被載入時,PE Loader會讀取相應引入表結構的成員來絕對能夠讀入的DLL,和相應DLL中的函數,最後將DLL載入到記憶體後,然後將相應的函數地址重定位,以便我們調用函數。
如果從字元集的相關性來分我們的API分為兩類,一類是處理ANSI字元集的,一類是處理UNICODE字元集的。前一類的尾部帶有“A”字元。 後一類的尾部帶有“W”字元。(我想:W是代表寬字元的意思吧)。我們比較熟悉的ANSI字串是以NULL結尾的一串字元數組。每一個ANSI字元是一個BYTE寬。對於歐洲體系ASNI已經足夠了。但是對於成千上萬的唯一字元的幾種東方語言體系來說只能用UNICODE字元集了。每一個UNICODE字元佔2個位元組寬。這樣就就可以在一個字串中使用65336個不同字元了。
這也是為了增加UNICODE的原因。在大多數情況下,我們都可以包含一個標頭檔,在其中定義一個宏,然後在實際調用函數的時候就不用在函數名稱後面加“A”和“W”字元了。
如
<#ifdef UNICODE
#define fool() foolW()
#else
#define fool()
#endif
>
先來個FASM的架構
format PE GUI 4.0
entry start
section '.data' data readable
section '.import' import data readable writeable
section '.code' code readable executable
start:
我們的應用程式是從entry指定的進入點處開始執行的。程式逐條指令開始執行,因為我們cpu是通過讀取eip寄存器的值來絕定讀入相應的資料執行的,所以我們程式一直執行直到遇到jmp jnz je 等跳躍陳述式後將程式控制權轉移其他語句,最後程式若要退出WINDOWS則必須調用ExitProcess函數來退出程式。。
這裡是函數的原型。函數原型會告訴編譯器該函數的屬性。這樣在編譯連結的時候編譯器就會進行相應的類型檢查。
FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
這個就是fasm函數的原型。
在前面的ExitProcess函數有一個dword型別參數。當你調用高層invoke的時候,你可以簡單的認為invoke有一個參數類型檢查的語句。
例如你這樣call ExitProcess
但你實現沒有將dword類型的參數壓入堆棧,編譯連結的時候不會出錯,程式在啟動並執行時候顯然出錯。
但是你可以這樣寫 invoke ExitProcess ,那麼編譯器則會進行類型檢查,並提示錯誤。。
invoke語句格式
INVOKE expression [,arguments]
expression 既可以是一個函數名也可以是一個函數指標。參數由逗號隔開。大多數api原型放在標頭檔中。我們fasm的標頭檔在include目錄下。
現在我們回到ExitProcess。 其中uExitCode是退出碼,用來退出程式返回給windows的。你可以這樣寫。
invoke ExitProcess, 0
把這一行放到開始的標示符下。
format PE GUI 4.0
include 'win32a.inc'
entry start
section '.data' data readable
section '.import' import data readable writeable
library, kernel32, 'kernel32.dll'
include 'api/kernel32.inc'
section '.code' code readable executable
start:
invoke ExitProcess, 0
我們的應用程式從win32a.inc標頭檔中得到相關變數結構體的定義,還需要從其他的標頭檔中得到函數原型。上面我們調用的函數在kernel32.dll中,我們必須通過fasm提供library宏來包含相應的dll,並且需要包含相應的標頭檔。因為這裡是相應函數的原型。
接下來我們來調用一個訊息框,
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hWnd 是父視窗的控制代碼。控制代碼代表您引用的視窗的一個地址指標。它的值對您編 Windows 程式並不重要(譯者註:如果您想成為高手則是必須的),您只要知道它代表一個視窗。當您要對視窗做任何操作時,必須要引用該視窗的指標。
lpText 是指向您要顯示的文本的指標。指向文本串的指標事實上就是文本串的首地址。
lpCaption 是指向您要顯示的對話方塊的標題文本串指標。
uType 是顯示在對話方塊視窗上的小表徵圖的類型。
下面是完整的程式
format PE GUI 4.0
include 'win32a.inc'
entry start
offset equ
section '.data' data readable
szCaption db 'test',0
szText db 'win32 fasm',0
section '.code' code readable executable
start:
xor ecx, ecx
invoke MessageBox,eax, offset szText, offset szCaption,MB_OK
invoke ExitProcess,ecx
section '.import' import data readable writeable
library kernel32, 'kernel32.dll',
user32, 'user32.dll'
include 'apikernel32.inc'
include 'apiuser32.inc'
你編譯連結上面的程式,呵呵出現一個訊息框。 “win32 fasm ” 。 由於fasm中沒有offset操作符,因為fasm是自動取全域變數的地址的,但是我們可以通過聲明一個無值的符號常量offset。這樣編譯器在編譯的時候將我們的offset是拋棄的,但是我們的原始碼中看起來可讀性比較好。。library是fasm提供我們的宏,因為fasm就是靠宏來構建輸入表的。library的格式是macro library [name,string] .
ExitProcess proto uExitCode: DWORD