?? windows x64匯編入門(mén)(2).txt
字號(hào):
【原創(chuàng)】Windows X64匯編入門(mén)(2)
--------------------------------------------------------------------------------
標(biāo) 題: 【原創(chuàng)】Windows X64匯編入門(mén)(2)
作 者: tankaiha
時(shí) 間: 2007-05-07,22:51
鏈 接: http://bbs.pediy.com/showthread.php?t=44078
tankaiha
五一長(zhǎng)假就要結(jié)束了,總算有時(shí)間好好睡了幾個(gè)懶覺(jué)。今天醒來(lái)后想到的第一件事就是,該寫(xiě)第二篇了。
64位技術(shù)現(xiàn)在還不成熟,沒(méi)有好調(diào)試器,但是我們搞技術(shù)的總是對(duì)新東西充滿了好奇和熱情。這個(gè)理由就足夠我們現(xiàn)在開(kāi)始學(xué)習(xí)64位匯編了!OK,Let’s go on。
1. 再說(shuō)Calling convention
關(guān)于API的調(diào)用方式,在入門(mén)(1)中說(shuō)了一些,不過(guò)感覺(jué)有必要再講兩點(diǎn)。一是在調(diào)用API時(shí)椎棧的框架,也就是Stack Frame,二是利用反匯編64位C/C++程序來(lái)研究calling convention。
先說(shuō)Stack Frame。圖1是一個(gè)通用的椎棧框架。
在一個(gè)使用STDCALL的32位程序中,stack frame的四項(xiàng)工作:
(1) 傳入?yún)?shù)的調(diào)用;
(2) 在返回caller時(shí),callee要負(fù)責(zé)平衡椎棧;
(3) 給局部變量提供空間;
(4) 保證ebx、esi、edi和ebp四個(gè)寄存器的值不變(這種寄存器被稱(chēng)為non-volatile)。
在64位環(huán)境中,少了一個(gè)平衡椎棧的任務(wù),因?yàn)槠胶庾禇5墓ぷ饔蒫aller負(fù)責(zé)了,因此callee的stack frame只剩下三項(xiàng)工作:
(1) 將寄存器傳入的參數(shù)和其它超過(guò)4個(gè)以上的參數(shù)在椎棧上保存(入棧);
(2) 給局部變量提供空間;
(3) 保證non-volatile寄存器的值不變,包括ebp、ebx、rdi、rsi、r12到r15,xmm6到xmm15。
所以,在一個(gè)函數(shù)的開(kāi)始往往有如下代碼:
MOV [RSP+8h],RCX
MOV [RSP+10h],RDX
MOV [RSP+18h],R8
MOV [RSP+20h],R9
PUSH RBP
MOV RBP,RSP
而在返回時(shí)會(huì)有如下代碼:
LEA RSP,[RBP]
POP RBP
RET
圖2摘自GoASM的幫助文檔,上文描述的情況在圖中一目了然。
如果能在VC中編譯64位C/C++程序,再用IDA反匯編,不是挺好的嗎?正確,這正是我們玩兒逆向工程的人喜歡的方法。Visual Studio 2005的64位開(kāi)發(fā)環(huán)境設(shè)置網(wǎng)上有,這里不多說(shuō)了。以一個(gè)C/C++的代碼為例:
代碼:
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
這段代碼是一個(gè)地球人都知道的窗口消息處理代碼,在編譯為64位程序后,用ida64看一下它的反匯編。這樣,熟悉而又有點(diǎn)陌生的64位匯編代碼就出來(lái)了,包括消息的判斷,EndDialog的調(diào)用等,確實(shí)很方便。
2. 第二個(gè)匯編例子:SMC
在入門(mén)(1)中我們寫(xiě)了第一個(gè)64位的匯編程序,這里我們開(kāi)始寫(xiě)第二個(gè)。當(dāng)然,代碼本身還是有點(diǎn)意思的,這就是Self Modify Code。讓我們?cè)囈辉嘢MC在64位下進(jìn)行的如何?這還牽涉到vista的特性。代碼來(lái)自修改過(guò)的參考資料《About RIP relative addressing》。
DATA SECTION
testzero db 'eax值為0', 0
testnonzero db 'eax值不為0!', 0
testtitle db '測(cè)試or eax,eax指令', 0
oldprotect dd ?
CODE SECTION
Start:
;改變當(dāng)前內(nèi)存頁(yè)的保護(hù)為可寫(xiě)
sub rsp,28h
lea r9, oldprotect ; R9 = lpflOldProtect
mov r8d, 40h ; R8D = flNewProtect
mov rdx, 1 ; RDX = dwSize
lea rcx, modifyhere ; RCX = lpAddress
call VirtualProtect
add rsp,28h
lea rax,modifyhere
inc B[rax]
xor eax,eax
or eax,eax
;如果eax為0,則or指令會(huì)使jz跳轉(zhuǎn)
lea rax, testzero
modifyhere:
jz >.skip ;這個(gè)是GoASM的語(yǔ)法,>號(hào)表示后面代碼中的label
lea rax, testnonzero
.skip ;GoASM中的label這樣定義
; 顯示結(jié)果
sub rsp,28h
mov r9d, 0 ; R9D = UINT uType
lea r8, testtitle ; R8 = LPCTSTR lpCaption
mov rdx, rax ; RDX = LPCSTR lpText
mov rcx, 0 ; RCX = HWND hWnd
call MessageBoxA
mov ecx, eax ; ECX = UINT uExitCode
call ExitProcess
add rsp,28h
ret
代碼的執(zhí)行流程如下:將eax賦0,然后進(jìn)行or eax,eax,如果不修改代碼,則jz處會(huì)跳轉(zhuǎn),結(jié)果會(huì)顯示“eax值為0”。我們的任務(wù)就是把jz改為jnz。jz的十六進(jìn)制編碼為74,jnz為75。
編譯一下:
GoASM /x64 “2.1.asm”
GoLink “2.1.obj” kernel32.dll user32.dll
因?yàn)槲覀兗尤肓讼旅鎯删浯a:
lea rax,modifyhere
inc B[rax]
所以jz為in為jnz了。結(jié)果顯示如下圖所示。注意,GoASM中byte ptr簡(jiǎn)寫(xiě)為B。當(dāng)然,你可以把上面兩句指令刪除,那出來(lái)的就完全是另一個(gè)結(jié)果了。
3. 資源文件
本文的最后一節(jié)來(lái)講下帶資源的程序編譯。由于GoASM有自己的編譯器GoRC,而visual studio中是rc,因此我們將分別用兩種語(yǔ)法編寫(xiě),看一下兩個(gè)編譯器中的相同與不同。
先按下面的代碼建立MainDlg.rc,這個(gè)rc文件是兩個(gè)例子通用的,代碼來(lái)自RadASM的32位默認(rèn)模板代碼,其實(shí)就是一個(gè)對(duì)話框,沒(méi)有添加任何控件:
代碼:
#define IDD_DLG1 1000
IDD_DLG1 DIALOGEX 6,6,194,106
CAPTION "我的第一個(gè)DialogBox"
FONT 8,"MS Sans Serif"
STYLE 0x10CF0000
EXSTYLE 0x00000000
BEGIN
END
來(lái)看一下GoASM語(yǔ)法的文件,其中用了很多GoASM的宏語(yǔ)法,不熟悉的可以看下幫助文件。我們把它保存為2.2.asm。
;##################################################################
; DIALOGAPP
;##################################################################
;暫時(shí)沒(méi)有完整的include文件,我們把要用的自己添加進(jìn)來(lái)
#Define WM_INITDIALOG 00110H
#Define WM_DESTROY 00002H
#Define WM_COMMAND 00111H
#Define WM_CLOSE 00010H
#IFNDEF FALSE
#Define FALSE 0
#ENDIF
#IFNDEF TRUE
#Define TRUE 1
#ENDIF
CONST SECTION
IDD_DLG1 equ 1000
DATA SECTION
hInstance DQ ?
CODE SECTION
Start:
;invoke是GoASM調(diào)用API的宏,用不著我們自己進(jìn)行stack frame了
invoke GetModuleHandleA, 0
mov [hInstance],rax
invoke InitCommonControls
invoke DialogBoxParamA,[hInstance],IDD_DLG1,0,ADDR DlgProc,0
invoke ExitProcess,0
;FRAME是GoASM的宏
DlgProc FRAME hwnd,uMsg,wParam,lParam
mov eax,[uMsg]
cmp eax,WM_INITDIALOG
jne >.WMCOMMAND
jmp >.EXIT
.WMCOMMAND
cmp eax,WM_COMMAND
jne >.WMCLOSE
jmp >.EXIT
.WMCLOSE
cmp eax,WM_CLOSE
jne >.DEFPROC
INVOKE EndDialog,[hwnd],0
.DEFPROC
mov eax,FALSE
ret
.EXIT
mov eax, TRUE
ret
ENDF
編譯時(shí)有個(gè)很奇怪的問(wèn)題,就是要把資源文件編譯成.obj格式才能順利鏈接。命令行如下:
GoRC /machine x64 /o maindlg.rc
GoASM /x64 2.2.asm
GoLink “2.2.obj” maindlg.obj kernel32.dll user32.dll comctl32.dll
生成了2.2.exe后,運(yùn)行,如下圖所示:
下面,看一下ml64的編譯過(guò)程。rc文件不變,把下面的代碼保存為2.3.asm。
;##################################################################
; DIALOGAPP
;##################################################################
extrn GetModuleHandleA : proc
extrn MessageBoxA : proc
extrn InitCommonControls : proc
extrn DialogBoxParamA : proc
extrn DestroyWindow : proc
extrn ExitProcess : proc
extrn EndDialog : proc
.const
WM_INITDIALOG equ 00110H
WM_DESTROY equ 00002H
WM_COMMAND equ 00111H
WM_CLOSE equ 00010H
FALSE equ 0
TRUE equ 1
IDD_DLG1 equ 1000
.data?
hInstance qword ?
.code
Main proc
;invode是GoASM調(diào)用API的宏,用不著我們自己進(jìn)行stack frame了
sub rsp,30h
xor rcx,rcx
call GetModuleHandleA
mov [hInstance],rax
call InitCommonControls
mov rcx,[hInstance]
mov rdx,IDD_DLG1
xor r8,r8
lea r9,DlgProc
push 0
call DialogBoxParamA
xor rcx,rcx
call ExitProcess
add rsp,30h
ret
Main endp
;DlgProc FRAME hwnd,uMsg,wParam,lParam
DlgProc:
mov [rsp+8],rcx
mov [rsp+10h],rdx
mov [rsp+18h],r8
mov [rsp+20h],r9
push rbp
mov rbp,rsp
mov eax,edx
cmp eax,WM_INITDIALOG
jne WMCOMMAND
jmp EXIT
WMCOMMAND:
cmp eax,WM_COMMAND
jne WMCLOSE
jmp EXIT
WMCLOSE:
cmp eax,WM_CLOSE
jne DEFPROC
sub rsp,18h
xor rdx,rdx
;注意,這里第一個(gè)參數(shù)rcx未變。按理應(yīng)該是由rbp定位
mov rcx,[rbp+10h]
call EndDialog
add rsp,18h
DEFPROC:
pop rbp
mov eax,FALSE
ret
EXIT:
pop rbp
mov eax, TRUE
ret
end
編譯命令行為:
rc maindlg.rc
ml64 2.3.asm /link /subsystem:windows /entry:Main kernel32.lib user32.lib comctl32.lib maindlg.res
如果你編譯正確了,應(yīng)該和2.2.exe的運(yùn)行結(jié)果一樣。
和GoASM的宏比起來(lái),ml64的語(yǔ)法顯得更低級(jí),也更基礎(chǔ)。不過(guò)要注意的是,在2.3.asm中,很多語(yǔ)法我并沒(méi)有寫(xiě)得很規(guī)范,要想知道最規(guī)范的代碼,便是逆向高級(jí)語(yǔ)言或GoASM 生成的exe文件。
OK,第二篇就到這里了。五一長(zhǎng)假將在今晚結(jié)束,明天上班嘍。
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -