You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编写基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 09:17:39