x64匯編語言在win32asm上做了較大改進,如果只憑借之前win32asm的只是來試水x64asm,則會有很多意想不到的bug,總的來說x64asm更加自由,更加有趣。
1.對32位寄存器的寫操作和運算操作,則會對相應的64位寄存器的高32位清零。
如在x64dbg上實驗,mov eax, 1和add eax, 1會使rax的高32位清零;
xor eax, eax是對eax的清零運算操作,所以xor rax, rax會被編譯器優化為指令更短的xor eax, eax因為二者在x64匯編中的效果是一樣的;
但是mov ax,1和mov al, 1不會對rax的高32位進行清零的操作。
2.立即數的使用,優先使用32位擴展,64位的立即數使用較少。
push指令和對內存的寫操作只支持4字節的立即數數據,比如push 0x12345678和mov qword ptr [rax], 0x12345678是合法的,但是如果要對長度長於4字節的
立即數使用(比如0x2134567890),就需要分兩步進行,借用寄存器進行操作,如需要將0x1234567890壓棧,應當:mov rax, 0x2134567890; push rax.
3.x64匯編的一些其他的基礎知識
比較常用的通用寄存器:
rax eax ax al
rcx ecx cx cl
rdx edx dx dl
rbx ebx bx bl
rsp esp sp spl
rbp ebp bp bpl
rsi esi si sil
rdi edi di dil
r8 r8d r8w r8b
r9 r9d r9w r9b
r10 r10d r10w r10b
r11 r11d r11w r11b
r12 r12d r12w r12b
r13 r13d r13w r13b
r14 r14d r14w r14b
r15 r15d r15w r15b
此外還有rip, xmm0~xmm15的多媒體用寄存器,rflags。
虛擬地址空間:
00000000`00000000 ~ 00007fff`ffffffff是用戶層代碼(Ring3)空間;
00007fff`ffffffff ~ ffff8000`00000000是不可用地址空間(not valid address);
ffff8000`00000000 ~ ffffffff`ffffffff是內核地址空間;
4.內存尋址優先使用相對便宜尋址,直接尋址指令較少。
5.各種jmp指令的比較(以下指令需要在x64dbg上做實驗增加理解)
幾種常見的jmp指令的opcode:
E8 jmp 2字節長度跳轉
EB FE jmp,常用的死循環跳轉
E9 jmp 4字節跳轉(±2GB地址空間)
FF25 jmp qword ptr[相對地址]
FF2425 jmp qword ptr[絕對地址],貌似用處不是特別廣泛,FF2425后面會接4個字節。
HOOK時候一般用的寄存器跳轉:
mov rax, 0x1234567890 jmp rax
此等同於
mov rax, 0x1234567890 push rax ret
6.x64匯編語言調用約定
x64的調用約定一般沒有特定的指明,__cdecl,__stdcall,__fastcall一般都會被編譯器修飾為__fastcall。
調用方分配和清理參數所需要的棧空間(外平棧),前四個參數使用rcx, rdx, r8, r9傳遞,即使用寄存器傳參,也需要分配棧空間。
比如x64asm程序:
1 option casemap:none 2 3 func Proto 4 5 .code 6 7 asm_fun Proc 8 9 sub rsp, 20h 10 mov rcx, 1 11 mov rdx, 2 12 mov r8, 3 13 mov r9, 4 14 15 call func 16 17 add rsp, 20h 18 ret 19 asm_fun Endp 20 21 END
64位C語言程序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <Windows.h> 4 5 extern void _fastcall asm_fun(); 6 7 ULONG64 x; 8 9 void func(ULONG64 a1, ULONG64 a2, ULONG64 a3, ULONG64 a4) 10 { 11 printf("0x%p\n", a1); 12 printf("0x%p\n", a2); 13 printf("0x%p\n", a3); 14 printf("0x%p\n", a4); 15 } 16 17 18 19 int main(int argc, char *argv[]) 20 { 21 asm_fun(); 22 23 printf("hello world\n"); 24 return 0; 25 }
運行結果:

易變寄存器(易揮發寄存器):rax,rcx,rdx,r8,r9,r10,r11
push和pop指令僅用來保存非異變寄存器,其他棧指針顯式寫rsp實現
進入call之前rsp滿足0x10字節對齊,通常不使用rbp尋址棧內存,所以rsp在函數棧中盡量保持穩定(一次性分配局部變量和參數空間)。
x64中每次調用要手動來平衡棧,要16字節對齊,且call指令還要用8個字節的棧空間來存放它返回的地址;則比如當有4個參數時候,參數需要的棧空間是4*8 = 32,call返回的地址需要8個字節的棧空間,則一共需要32 + 8 = 40個字節的棧空間由於40無法被16整除而需要至少加上8個字節變成48字節,此時可以被16整除,所以此時需要48(0x30)字節,而我們需要手動分配的是40(0x28)字節空間。
小弟實驗了以下幾個例子(C語言內嵌匯編),將在C語言中寫了func函數代替一下常見例子中的messagebox函數,同時為了簡單分析問題,參數全部選了8字節長度的ULONG64變量,傳遞參數為4個參數時候可以參考帖子中的例子。
當傳入5個參數時候可以使用push來壓棧的方法和sub rsp, xxx + mov qword ptr[rsp+xxx],yyy的兩種方法。
首先是push的方法,需要手動在紙上先進行演算rsp所指向的位置和變化:
當使用push方法傳入6個參數時候就不好使了,因為push方法只能將最后一個參數(第六個)傳進去,所以第5個參數要想訪問就比較麻煩了(也可能是我錯了)。
Visual Studio2013中調試64位應用程序可以看到反匯編代碼很少使用push方法傳遞參數的,基本都是使用sub rsp, xxx + mov qword ptr[rsp+xxx],yyy來進行參數的傳遞,這種方法也更加好算,更加穩定,我以后就用這種辦法了。
在使用前同樣我需要在紙上先算一下棧空間的分配;

