32位匯編第二講,編寫窗口程序,加載資源,響應消息,以及調用C庫函數
(如果想看所有代碼,請下載課堂資料,里面有所有代碼,這里會講解怎么生成一個窗口程序)
一丶32位匯編編寫Windows窗口程序
首先我們知道32位匯編是可以調用Windows API的,那么今天我們就調用windowsAPI來寫一個窗口程序
如果你有windows開發知識,那么就很理解了,如果沒有,那么跟着我寫,跟着步驟去寫,那么也可以寫出來
首先我們要編寫一個窗口程序(使用SDKAPI編寫)有幾個步驟
1.設計窗口類
2.注冊窗口類
3.創建窗口
4.顯示窗口
5.更新窗口
6.建立消息循環
7.窗口過程函數
總共需要這幾步,每不單獨做個講解.
1.設計窗口類
設計窗口類,顧名思義,就是你要給你的窗口設置一些屬性,比如我窗口的風格,名字,類名,圖標,菜單什么的
這里windows為我們提供了一個結構體
WNDCLASS結構體,里面就包含了這些屬性,我們只需要依次添加,看下WNDCLASS里面的內容
WNDCLASS This structure contains the window class attributes that are registered by the RegisterClass function. typedef struct _WNDCLASS { UINT style; //窗口的風格 WNDPROC lpfnWndProc; //窗口消息處理的過程函數 int cbClsExtra; //額外內存申請(不重要) int cbWndExtra; //額外內存申請(不重要) HANDLE hInstance; //程序的實例句柄 HICON hIcon; //圖標 HCURSOR hCursor; //資源光標 HBRUSH hbrBackground; //窗口背景 LPCTSTR lpszMenuName; //窗口名字 LPCTSTR lpszClassName; } WNDCLASS ; //窗口類名
對於上面的結構體,我們只需要里面的參數需要什么內容即可
使用匯編編寫:
include windows.inc include user32.inc ;加載要使用的頭文件和lib庫,至於這些是什么,下面仔細講解 include kernel32.inc includelib user32.lib includelib kernel32.lib
.386
.model FLAT,stdcall
option casemap:none
.const ;常量區
g_szClassName db "ClassName",0 ;窗口類的類名名稱
g_szWndName db "WndName",0 ;窗口的名稱 .data ;初始化的數據區 .code ;代碼區 WinMain proc ;程序啟動的時候執行的入口函數 ;設計我們的窗口類 LOCAL @wc:WNDCLASS ;定義WNDCLASS,對里面的屬性修改
LOCAL @hInstance : HINSTANCE ;定義程序的實例句柄
LOCAL @hWnd:HWND ;定義我們的hWnd,接受創建窗口的時候的返回值\
LOCAL @msg:MSG ;定義消息循環的結構體
;思路,第一步,取得窗口的實例句柄,給hInstance
invoke GetModuleHandle,NULL ;調用API即可獲取,返回值默認放在Eax當中
mov @hInstance,eax ;check(為了排版,不寫檢查了)....
;開始給WNDCLASS各種屬性賦值
mov @wc.style, CS_VREDRAW or CS_HREDRAW; ;默認,垂直和水平拉伸窗口,窗口內容重新布局和繪制
mov @wc.lpfnWndProc, WindowProc; ;窗口過程函數
mov @wc.cbClsExtra, 0; ;額外內存
mov @wc.cbWndExtra, 0; ;額外內存
mov eax, @hInstance ;實例句柄的值給eax,下方設置進去,(內存到內存不可以,所以中轉)
mov @wc.hInstance, eax; ;給窗口設置實例句柄
mov @wc.hIcon, NULL; ;圖標資源為NULL
mov @wc.hCursor, NULL; ;鼠標光標為NULL
mov @wc.hbrBackground, COLOR_ACTIVEBORDER; ;設置背景畫刷
mov @wc.lpszMenuName, NULL; ;設置菜單名稱
mov @wc.lpszClassName,offset g_szClassName;;設置窗口類名名稱
;這里就設計完成了,下一步就要注冊這個窗口類,到系統中,所以這里為中間線,注冊窗口的代碼我會接着這下面繼續寫,上面的代碼就不重復寫了,下面的幾個步驟是一樣的,最后在把整個的匯編代碼貼上
WinMain endp
end WinMain
2.剩余步驟一起執行
;對於下方的API不熟悉的可以調用MSDN,下載地址在 www.w1x8.com,因為文件太大,所以不上傳到課堂資料中了
;注冊窗口類 invoke RegisterClass,addr @wc ;在這里我們使用偽指令addr,他的作用是自動幫我們計算局部變量所在的內存地址,如果對指令不挑明白,可以打開OD找到這個地方看下指令是怎么寫的 ;創建窗口 invoke CreateWindowEx, ;這里注意一下只能使用CreateWindowEx,因為.inc文件中沒有CreateWindows
0 ;窗口的擴展風格
offset g_szClassName, ;窗口的類名
offset g_szWndName, ;窗口的標題名字 WS_OVERLAPPEDWINDOW, ;窗口的風格 CW_USEDEFAULT, ;下面4個默認的分別是否是 窗口的高度 寬度 ,窗口的x,y坐標 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, ;窗口父類的實例句柄 NULL, ;窗口的菜單 @hInstance, ;程序的實例句柄 NULL ;創建窗口的額外參數
mov @hWnd,eax ;創建窗口后返回一個窗口句柄,返回值地方在eax中,這個上面定義了
;顯示窗口
invoke ShowWindow,@hWnd,SW_SHOW ;顯示窗口
;更新窗口
invoke UpdateWindow,@hWnd
;建立消息循環
.while TRUE
invoke GetMessage,addr @Msg,NULL,0,0
;判斷
.if (eax == -1)
.break
.endif
invoke TranslateMessage, addr @msg ;把虛擬鍵碼,轉化為鍵盤按鍵
invoke DispatchMessage, addr @msg ;把msg中的消息,放到窗口過程中執行
.endw
;建立窗口過程
WindowProc proc hWnd:HWND,uMsg:UINT,wParam,WPARAM,lParam:LPARAM
;判斷消息執行
.if uMsg == WM_KEYDOWN
.....;執行你的代碼
.endif
invoke DefWindowProc,hWnd,uMsg,wParam,lParam;
ret
WindowProc endp ;函數結束
對於上面的代碼,不保證能正確執行,因為編寫博客,不能把上面代碼調試,所以思路代碼都是一樣的,我會發到課堂資料中
請參考課堂資料中的代碼
3.資源的使用
現在我們還不能使用資源,那我們必須編譯一個資源文件,.rc結尾,
資源文件,是vc++6.0中常用的資源文件,而編譯資源文件的編譯器是.rc.exe,這個編譯器我都會放到
課堂資料中
首先編譯一個資源弄文件
這里使用VC++6.0編寫一個
主要代碼就是這里,我們使用rc.exe編譯這個資源弄文件(這個文件的后綴名是.rc結尾)
編譯出來之后是.RES的文件,我們把它當做obj文件使用,連接到PE文件中(exe文件中)即可
但是我們在設計窗口類的時候,需要使用一下這個菜單資源的ID
菜單資源的ID,在資源對應的Result.h的頭文件中,我們拿過來即可.
我們要做的就是把資源變為匯編中的即可
比如上面的DIR_MENU1 代表101
那我們用匯編編寫為 IDR_MENU1 EQU 101 即可
我們使用link 連接到一起即可
link /subsystem:windows 窗口.obj AAA.RES
然后編譯出來就有菜單了,如果響應消息,則在窗口過程函數中捕獲WM_COMMAND消息即可
然后資源文件其實是二進制,連接到EXE中(也就是放到EXE當中),那么我們使用WinHex可以再不需要源碼的
情況下,把名字修改了
我的WinHex沒有設置編碼,所以看得不太清楚,這里就是存放資源的地方,我們把名字修改了,重新打開我們的窗口
改為Y,重新打開窗口
可以看到,已經修改為YIle了,所以逆向是很好玩的.不需要代碼,可以直接修改你的程序
二丶.inc文件格式,和.lib文件的說明
1..inc文件說明
上面我們使用了各種.inc文件,我們看下內部是什么,比如windows.inc
對於.inc文件,有個第三方出的工具,可以自動生成,我們看下(MASM32,會打包)
其中上面畫框的使我們需要的,下面的我們不太關系,如果關心,可以自動嘗試一下(這個工具建議收藏)
我們編寫windows程序的時候,只需要包含一個windows.h即可編寫代碼,是因為windows.h里面有幫我們定義的各種宏,以及函數的聲明,在這里我們使用的.inc也是一樣的,所以像上面的各種宏,和使用的函數,我們都不用定義了
這里主要介紹一下,lib 轉化為.inc文件,首先我們知道,lib文件中存放了各種函數的聲明,參數個數,所以這個工具是提取lib,並且轉化為對應的.inc文件
我們看一下吧,隨便找個lib拷貝過去
拷貝到工具目錄下(tools)
可以看到很多工具,這里 我們使用的是 l2inc 正確的讀法 是 lib to inc ,這里的2代表是to的意思
可以看到也有inc轉化為lib的,自己嘗試
我們拷貝到l2inc文件下
打開CMD,進入當前的路徑,輸入 l2inc lib文件名 回車即可生成
那我們的匯編程序就可以使用了
inc文件中對應的就是函數的聲明,可以看出,參數類型都是DWORD類型的
2.lib文件說明
比如昨天我們編譯的HelloWord程序,就要手動編譯的時候,加上對應的user32.lib,而user32.lib是保存了dll文件中的 名字,還有導出函數,所以加載了這個lib,會找對應的dll和他的導出函數,進而執行我們的程序
這里在文件內部使用的,所以我們連接的時候不用手動去寫了
這里的lib文件是 動態的靜態加載
什么意思:
動態的指的就是dll,靜態的指的就是dll所對應的lib,這個lib保存了dll的路徑信息,還有導出函數信息,當我們連接到EXE中的時候,會從lib中拷貝dll的路徑,以及導出函數,然后放到exe當中,
當我們調用的時候,會根據dll的路徑,找到對應的dll,根據導出函數,調用dll的導出函數(比如昨天的HELLO信息框)
靜態加載:
靜態加載則是直接把lib連接到exe當中,(這個lib中放的都是代碼),相當於把代碼拷貝到exe中,這樣調用的時候,直接執行代碼,而不從dll中去執行這個API了. 確定點是文件大,不容易維護,優點,這個程序任何windows平台上,都能運行,不管你有沒有dll
關於靜態加載,和動態加載,在下面的調用C庫函數中講解
三丶動態和靜態的使用C庫函數
1.首先是動態的使用
動態的使用我們需要加上 msvcrt.inc然后還需要msvcrt.lib
.inc 我們知道存的是函數的聲明, 而.lib則是存放的dll的路徑,以及導出函數
例子:
.386 .model FLAT,stdcall option casemap:none ;__UNICODE__ equ include msvcrt.inc includelib msvcrt.lib ;crt_ 動態使用 .data g_SzBuff db 100 dup(0) ;使用Strcpy,拷貝到這里面 g_SiTile db "Hello",0 ;把Hello拷貝到szBuff里面 .const .code START: invoke crt_strcpy ,offset g_SzBuff,offset g_SiTile ;拷貝字符串,為什么使用crt開頭,因為調用約定是C,作者 ;調用約定是C,那么會有名稱粉碎,每次比如strcpy,則在前邊加上 ;_開頭,如果是std調用約定,則在后面加上@符號,所以作者為了省事 ;在_strcpy加上了crt,這樣簡單 ret end START
看下編譯出的程序,使用OD調試查看
我們要拷貝字符串,則看下是否成功拷貝
拷貝后
然后我們 ALT + E 看下模塊表,可以找到我們的MSVCRT
可以看出調用的是這個.dll的內容
看下Call
Call后面則不一樣,表明調用的是Dll中,然后看下面的代碼,有個 add ESP,0X8,則表明strcpy是一個C調用約定
因為C調用約定必須外面平棧
2.靜態的使用
靜態的使用,則用libc.lib,這里面存放了代碼,但是需要注意一下,我們提供的工具 MASM32有這個,
而VC++6.0中也有,VS系列也有,至於使用那個版本,就看環境變量誰在前邊了,(最好不用MASM32的)
MASM32的libC不全,會導致我們編寫代碼出錯,我們可以從其他位置拷貝一個,放到MASM32的lib文件夾中
(因為我的環境變量他在最前邊,所以優先找他,所以我要拷貝,或者你直接拷貝到根目錄下)
靜態使用分為兩步
1.包含lib includelib libc.lib
2.對你使用的函數聲明一下,因為沒有inc文件了,所以都要自己聲明
例子:
.386 .model FLAT,stdcall option casemap:none ;__UNICODE__ equ ; include msvcrt.inc ; includelib msvcrt.lib ;crt_ 動態使用 includelib libc.lib ;靜態使用 strcpy proto c, :dword, :dword ;聲明函數 .data g_SzBuff db 100 dup(0) ;使用Strcpy,拷貝到這里面 g_SiTile db "Hello",0 ;把Hello拷貝到szBuff里面 .const .code START: ; invoke crt_strcpy ,offset g_SzBuff,offset g_SiTile ;拷貝字符串,為什么使用crt開頭,因為調用約定是C,作者 ; ;調用約定是C,那么會有名稱粉碎,每次比如strcpy,則在前邊加上 ; ;_開頭,如果是std調用約定,則在后面加上@符號,所以作者為了省事 ; ;在_strcpy加上了crt,這樣簡單 ;靜態使用 invoke strcpy, offset g_SzBuff,offset g_SiTile ret end START
看下OD調試(對於編譯連接,這里不說了,很常用了,不會的自己多敲幾遍,對於以后新增加編譯選項則會對應的講解一下)
我們可以看到,CALL直接成為了地址了,因為代碼就在我么我們的EXE文件中,所以直接在對應的地址找到代碼的執行位置執行即可.
第二講資料:
鏈接:http://pan.baidu.com/s/1qXIW2sc 密碼:zpq1
32匯編所有資料鏈接請看所有資料分享的總博客,里面集合了鏈接