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来进行参数的传递,这种方法也更加好算,更加稳定,我以后就用这种办法了。
在使用前同样我需要在纸上先算一下栈空间的分配;

版权声明:本文为traceback818原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/traceback818/p/12245177.html