x64汇编环境:只需要在x86基础上对项目属性进行设置,将平台设置为所有平台;
以及在将debug改为x64模式即可:
后续写完代码直接生成项目再使用本地调试器进行运行即可。
fastcall调用约定
在x64架构下,fastcall调用约定是默认的调用约定。它旨在通过寄存器传递参数以提高调用效率。以下是详细的理论说明,涵盖了参数传递、返回值处理、栈管理和寄存器保存规则。
参数传递规则
前四个整数或指针参数通过以下寄存器传递:
第一个参数:
RCX
第二个参数:
RDX
第三个参数:
R8
第四个参数:
R9
超过四个的参数通过栈传递,从右到左的顺序依次入栈;即当参数数量超过四个时,第五个及之后的参数按照从右到左的顺序依次压入栈中。
假设有一个函数foo
,定义如下:
void foo(int a, int b, int c, int d, int e, int f, int g);
在调用foo(1, 2, 3, 4, 5, 6, 7)
时,参数传递顺序如下:
前四个参数通过寄存器传递:
-
a
(1)传递到RCX
-
b
(2)传递到RDX
-
c
(3)传递到R8
-
d
(4)传递到R9
剩余的参数通过栈传递:
-
e
(5) -
f
(6) -
g
(7)
在栈中,参数按照从右到左的顺序依次压入栈中,即最右边的参数(g
)最先压入栈,接着是f
,最后是e
。这样,在栈中的顺序如下(从栈顶到栈底):
栈顶 -> g (7)
f (6)
e (5)
栈底
简单的使用示例
段代码用MASM(Microsoft Assembler)编写的x64汇编代码,包含两个过程(proc
):myadd
和main
;它们实现了一个简单的加法操作,myadd
将接收的多个参数相加,而main
负责设置这些参数并调用myadd
。
.code
myadd proc
xor rax,rax
add rax,qword ptr [rsp + 30h]
add rax,qword ptr [rsp + 28h]
add rax,r9
add rax,r8
add rax,rdx
add rax,rcx
ret
myadd endp
main proc
sub rsp,28h
mov qword ptr [rsp + 28h],6
mov qword ptr [rsp + 20h],5
mov r9,4
mov r8,3
mov rdx,2
mov rcx,1
call myadd
add rsp,28h
ret
main endp
end
代码解释
①main过程
main
过程负责设置参数并调用myadd
过程:
调整栈指针:
-
sub rsp, 28h
:调整栈指针,预留40字节(0x28)的栈空间,以对齐栈并为局部变量提供空间。
设置额外参数:
-
mov qword ptr [rsp + 28h], 6
:将第6个参数6保存到栈上的位置[rsp + 28h]
。 -
mov qword ptr [rsp + 20h], 5
:将第5个参数5保存到栈上的位置[rsp + 20h]
。
设置寄存器参数:
-
mov r9, 4
:将第4个参数4存储到r9
寄存器。 -
mov r8, 3
:将第3个参数3存储到r8
寄存器。 -
mov rdx, 2
:将第2个参数2存储到rdx
寄存器。 -
mov rcx, 1
:将第1个参数1存储到rcx
寄存器。
调用myadd
过程:
-
call myadd
:调用myadd
过程,结果将存储在rax
寄存器中。
恢复栈指针:
-
add rsp, 28h
:恢复栈指针到调用sub rsp, 28h
之前的状态。
返回:
-
ret
:返回,结束程序。
②myadd过程
myadd
过程从寄存器和栈中读取6个参数,并将它们相加。具体步骤如下:
初始化累加器:
-
xor rax, rax
:将rax
寄存器清零,作为累加器。
执行加法操作:
-
add rax, qword ptr [rsp + 30h]
:将参数6(存储在[rsp + 30h]
)加到rax
中。 -
add rax, qword ptr [rsp + 28h]
:将参数5(存储在[rsp + 28h]
)加到rax
中。 -
add rax, r9
:将参数4(存储在r9
寄存器中)加到rax
中。 -
add rax, r8
:将参数3(存储在r8
寄存器中)加到rax
中。 -
add rax, rdx
:将参数2(存储在rdx
寄存器中)加到rax
中。 -
add rax, rcx
:将参数1(存储在rcx
寄存器中)加到rax
中。
返回:
-
ret
:返回,rax
寄存器中的结果作为函数返回值。
程序执行结果:
最后RAX寄存器中的值为15h转化为10进制就是21。
x64dbg查看堆栈变化
使用x64dbg进行调试;将程序拖进x64dbg程序中,以下是x64dbg进行调试的四个区域窗口,分别是反汇编、寄存器、内存和堆栈窗口。
刚进来显示的是ntdll.dll的反汇编调试信息,ntdll.dll
是Windows操作系统中的一个重要动态链接库(DLL),其全称是NT Layer DLL;这个库包含了许多核心的系统服务和API,直接与Windows NT内核交互。此时要想调试自己的程序可以按下f9
单步跳过。
转到自身程序后可以看到两个jmp
跳转指令,一个是跳转程序中的入口main,另一个是跳转至自定义过程myadd,接着可以按下f7步进
继续执行下一步(f7或者f8需要根据自身x64dbg程序的设置使用),进入main入口点,运行指令。
sub rsp,28h
mov qword ptr [rsp + 28h],6
mov qword ptr [rsp + 20h],5
...
寄存器赋值
...
call myadd
接着往下执行,此时我们对rsp进行查看;转到RSP(查看当前栈顶指针的情况);双击堆栈区并且在堆栈区右击,选择转到rsp即可查看当前rsp的情况:
sub rsp, 28h
:调整栈指针,预留40字节(0x28)的栈空间,以对齐栈并为局部变量提供空间;运行此步此时RSP应该在$-28
位置,
mov qword ptr [rsp + 28h], 6
:将第6个参数6保存到栈上的位置[rsp + 28h]
。
mov qword ptr [rsp + 20h], 5
:将第5个参数5保存到栈上的位置[rsp + 20h]
。
call myadd
:调用myadd
过程,结果将存储在rax
寄存器中,至此因为使用了call命令,则此时会将函数的返回地址也压入栈内,所以此时RSP在$-30
的位置。
继续往下运行进入myadd过程:
xor rax, rax
:将rax
寄存器清零,作为累加器。
add rax, qword ptr [rsp + 30h]
:将参数6(存储在[rsp + 30h]
)加到rax
中。
add rax, qword ptr [rsp + 28h]
:将参数5(存储在[rsp + 28h]
)加到rax
中。
...
将rcx、rdx、r8、r9寄存器中的值加入rax中
...
得到最后的结果:
接着进行函数返回,回到入口过程中继续运行;
add rsp, 28h
:恢复栈指针到调用sub rsp, 28h
之前的状态,此时RSP回到最初的位置。
至此dbg结束。