x64匯編基礎知識


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來進行參數的傳遞,這種方法也更加好算,更加穩定,我以后就用這種辦法了。
在使用前同樣我需要在紙上先算一下棧空間的分配;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM