编写 Microsoft Fastcall 64 位汇编函数

问题描述

我在通过 shellcode 编写 64 位 fastcall 函数时遇到了一些问题,问题是它似乎与在它之前和之后调用的其他函数混淆,尽管工作,但在我传递的参数几次之后由于某种原因最终变成了垃圾,而不是我传递给它的东西,这让我觉得我可能误解了这个调用约定的工作原理。

例如,我在调用printf之前调用printf我使用的2个参数,这是第一次运行后它们发生的情况:

  printf("RAX: %p\nR10: %p\n\n",mem2,mem);
  function((U64)mem2,(U64)mem,1);

输出

RAX: 0000000000597D00
R10: 0000000000597B80

RAX: 0000000000190000
R10: 0000000000597D00

*RAX 的第二个值实际上是分配 shellcode 的地址,我不知道它是如何结束的。

附言我使用 stdcall 调用约定在 32 位中编写了相同的函数,并且它运行良好,因此它的主要部分(除了使用的寄存器之外保持不变)正常运行。

所以文档指定前 3 个参数将分别放入 RCX、RDX 和 R8 中,并且在函数内部使用 RAX、R10、R11 等,所以这是我存储参数的地方使用(它们在整个函数的持续时间内被修改)。

这是函数原型:

typedef void(__fastcall*f_proto)(unsigned long long a,unsigned long long b,unsigned long long c);

这是它的样子:

PUSH RBP
MOV RBP,RSP
MOV RAX,RCX
MOV R10,RDX
MOV R11,R8
...
MOV RSP,RBP
POP RBP
RETN 0x18

解决方法

64 位窗口仅使用一种调用约定,称为“Microsoft x64 调用约定”(如果包含 __vectorcall,则为两种)。维基百科分别讨论了 32 位调用约定,然后讨论了单个 64 位微软调用约定。我有一个调用约定 here 的图表。 MDSN page for the calling conventions 正在讨论 x86 调用约定,如果您正在寻找 x64 调用约定,它会将您定向到一个链接,并且您需要注意主空间和 rsp 对齐后打电话。

在维基百科页面上:

Microsoft __fastcall 约定(又名 __msfastcall)传递适合 ECX 和 EDX 的前两个参数(从左到右计算)。剩余的参数从右到左压入堆栈。当编译器针对 IA64 或 AMD64 进行编译时,它会忽略 __fastcall 关键字,而是使用一种 64 位调用约定。

__stdcall MSDN 页面上:

在 ARM 和 x64 处理器上,__stdcall 被编译器接受并忽略

__cdecl MSDN 页面上:

在 ARM 和 x64 处理器上,__cdecl 被接受,但通常被编译器忽略。按照 ARM 和 x64 的约定,参数在可能的情况下在寄存器中传递,随后的参数在堆栈上传递。在 x64 代码中,使用 __cdecl 覆盖 /Gv 编译器选项并使用默认的 x64 调用约定。

只有 __vectorcall 在 x64 上做一些不同的事情