编写基于Shellcode的Microsoft Fastcall 64位汇编函数时遇到的参数异常问题
64位Fastcall(Windows x64)Shellcode函数干扰问题分析与修复
看起来你踩了Windows x64调用约定和32位stdcall最关键的差异坑——栈清理责任,这也是导致你的函数干扰其他调用、参数失效的核心原因。我们一步步拆解问题:
核心问题:错误的栈清理指令
你在汇编结尾用了RETN 0x18,这是32位stdcall的标准写法(被调用者负责清理参数栈空间),但64位Windows Fastcall(官方标准的x64调用约定)是调用者清理栈,被调用者只需要普通的RETN即可。
为什么这会引发异常?
- 你的函数原型是3个参数,全部通过寄存器(RCX、RDX、R8)传递,调用者根本不会把这三个参数压到栈上。
- 当你执行
RETN 0x18时,函数返回会额外弹出24字节的栈空间,直接破坏了调用者的栈帧结构。后续的函数调用(比如你第二次执行的printf)会因为栈指针错位,读取到错误的内存位置——这就是为什么你看到RAX的值变成了Shellcode分配地址,本质是printf的参数被栈破坏后的垃圾数据覆盖了。
其他需要检查的细节
除了栈清理,还有两个点可能影响稳定性:
- Shadow Space(影子空间):Windows x64调用约定要求调用者在调用函数前,必须预留32字节的影子空间(哪怕参数全是寄存器传递)。调用者需要执行
sub rsp, 32来预留空间,调用完成后再add rsp, 32恢复。如果你的调用代码没做这一步,也可能导致栈访问异常或参数错乱。 - 非volatile寄存器保护:你提到RAX、R10、R11是可随意使用的volatile寄存器,这部分是对的,但要确保你没有意外修改了非volatile寄存器(比如RBX、RSI、RDI、RBP、RSP、R12-R15)——这些寄存器需要在函数开头用
PUSH保存,结尾用POP恢复,否则会破坏调用者的上下文。
修正后的汇编框架
把栈清理指令改成普通RETN,同时确保栈帧操作正确:
PUSH RBP MOV RBP, RSP ; 若使用了非volatile寄存器,在这里先保存,比如 PUSH RBX / PUSH R12 等 MOV RAX, RCX MOV R10, RDX MOV R11, R8 ; 你的核心逻辑代码... ; 若之前保存了非volatile寄存器,在这里恢复 MOV RSP, RBP POP RBP RETN ; 去掉0x18,让调用者负责栈清理
调用代码的注意事项
确保调用者正确预留影子空间:
// 调用前预留32字节影子空间 __asm { sub rsp, 32 } function((U64)mem2,(U64)mem,1); __asm { add rsp, 32 }
为什么32位stdcall没问题?
因为32位stdcall约定是被调用者负责清理参数栈,所以你当时的RETN n写法是正确的。但64位调用约定彻底改变了栈清理的责任,这是跨位移植时最容易忽略的差异。
内容的提问来源于stack exchange,提问作者Carol Victor
相关产品推荐
相关产品推荐

