让你的MASM支持__fastcall调用方式

让MASM支持__fastcall调用方式
作者:thebutterfly(Cloud)
熟悉逆向工程和破解的朋友都知道, 调用函数是要遵循一定的调用约定的. 常见的调用约定有C调用约定(__cdecl), 标准调用约定(__stdcall), Pascal调用约定, 快速调用约定(__fastcall)等几种. 在这些调用约定中, MASM对前三者都有很好的支持, 唯独对__fastcall调用约定不支持, 这不能不说是一种遗憾. 正因为这个, MASM在处理__fastcall类型的函数时显得相当不便, 例如在写内核模式驱动程序时有一个相当常见的函数IofCompleteRequest, 普通方法还真拿它没辙: 首先函数原型声明就是一个大问题, 普通的声明方法结果都是"Error xxxx:Cannot resolve external symbol yyyyy"; 其次是调用的语法, invoke显然是行不通的, 它只会把参数压栈然后一个call了事, 而这不是__fastcall的形式. 也同样是因为不支持__fastcall, 才使得汇编和其他语言(例如C)混合编程时一旦涉及__fastcall就举步维艰.

穷则思变, 我们都希望采取某些策略, 使MASM支持__fastcall调用方式, 换句话说就是使MASM能够定义和调用__fastcall类型的函数.

要达到这个目标,我们首先要了解(标准)__fastcall调用的特点.

__fastcall的特点是: 第1个参数放入ecx寄存器, 第2个参数放入edx寄存器, 如果还有参数则自右向左依次压栈; 由被调者负责维护堆栈平衡(清除压入的参数);如果函数有返回值则把返回值存放在eax寄存器中.  函数名的修饰特点是: 在函数名前加@, 后面加@并跟4*参数个数.例如有两个参数的IofCompleteRequest被修饰为:"@IofCompleteRequest@8".

我们先看看如何在汇编语言内部处理__fastcall函数. 假设我们要用汇编定义并调用这样一个函数:
int __fastcall AddNum(int x, int y, int z) {
  return x + y + z;
}
如何定义这个函数? 容易看出有3个参数, 根据__fastcall调用的特点, x应当存入ecx寄存器中, y则进edx, 还有一个参数z则采用堆栈传递, 函数执行完毕应当自己清理压入的参数(retn 4返回), 不难得到我们的初始想法如下:

AddNum  PROC  z    ;这里x, y不见了,因为它们都跑寄存器里去了
  mov  eax, ecx  ;ecx即为x参数
  add  eax, edx  ;edx即为y参数, 这儿计算x + y
  add  eax, z    ;最后加上z, 此时eax中为返回值x + y + z
  retn  4    ;自己清除z参数
AddNum  ENDP

如何调用它? 前面已经说过,invoke是肯定不行的, 因为它只会把参数一股脑儿全部压栈, 不符合__fastcall的特点. 我们只能采用原始的方式, 也就是写一堆处理参数的代码然后一个call xxx. 根据__fastcall的特点, 我们得到调用代码如下:

.....      ;其他代码
mov  ecx, 实参x  ;ecx中存放实参x(第1参数)
mov  edx, 实参y  ;edx中存放实参y(第2参数)
push  实参z    ;根据调用特点, z应当通过栈传递
call  AddNum    ;调用
.....      ;其他代码, 如处理返回值等. 调用者自己不必操心堆栈的平衡

经过试验, 这似乎是可以的, 不仅没有语法错误, 没有破坏堆栈, 而且反汇编和调用的结果也十分正确, 问题似乎已经解决了.

然而, 进一步的考察打碎了我们的迷梦, 我们做这样一个试验:把我们这个函数去和C语言做混合编程, 在C程序里如下调用:

extrn "C" int __fastcall AddNum(int x, int y, int z);  //声明外部函数
...
...
int result = AddNum(1, 2, 3);  //调用
...

编译没问题, 连接时却不对劲了, 提示 Error xxxx: An unresolved symbol "@AddNum@12"

奇怪?! 我们明明定义的是名为AddNum的函数,为什么会提示找不到@AddNum@12呢?

再做一个试验, 这次由汇编语言来调用C写的AddNum函数,代码如下:

AddNum  PROTO  z:DWORD;声明原型, 因为x,y都进了寄存器, 这儿只有一个参数z
...
mov  ecx, 实参x  ;这几句不多说了, 和上面一模一样, 关键看结果
mov  edx, 实参y
push  实参z
call  AddNum
...

结果, 提示错误如下: Error xxxx: Cannot resolve external symbol _AddNum@4

还是奇怪?! 明明声明的是AddNum函数却提示找不到 _AddNum@4 !

 事实使我们意识到, 我们上面所做的并没有完全达到我们的目标, 解决了"内政"却没有解决好"外交"问题.

静下心来分析一下为什么连接会出错. 每次我们定义AddNum函数时, 连接器总提示找不到 xxxAddNumxxx 形式的符号名, 这是为什么?

对, 符号名修饰! 这是问题的关键!

(这里顺便扯几句:大部分所谓"混合编程", 都要注意三个问题, 一个是函数调用方式, 第二个是obj文件格式, 最后一个就是符号修饰了. 那些所谓的"VB和VC混合编程", "VB和汇编混合编程"能够实现的重要原因是: 它们使用的都是同一个link.exe程序! 更本质的原因是: 虽然使用的是不同的编译器, 但是编译出来的obj文件是清一色的coff格式, 正是因为格式是统一的, 才使混合编程成为可能. 而如果想把tasm和VC++混合编程就麻烦多了, 因为两种obj的格式是不同的)

根据调用类型的不同, 符号名的修饰方式是不同的. 我们在写Win32汇编程序时都会写一句:
.model  flat, stdcall
这是没有办法的, 因为Masm32头文件里面的函数声明都没有指明语言类型, 没有stdcall声明会出错(依本人愚见, 这是Masm32的一个不足之处. 因为这给Masm和其他语言的混合编程造成了不少麻烦). 这使得我们写的每一个函数都是stdcall类型的, stdcall类型的修饰方法和fastcall类似,只是打头的是下划线_而不是at号@.例如MessageBoxA被修饰为: _MessageBoxA@16 .所以在第二个试验里, 我们自己写的函数被修饰为 _AddNum@4, 而真正的那个AddNum被修饰为 @AddNum@12 , 两者当然是不同的. 同理, 在第一个试验里, 我们写的函数修饰为_AddNum@4, 而连接器却去找@AddNum@12, 当然找不到.

大家可以试一下, 用十六进制编辑器打开编译好的那个obj文件, 查找AddNum字符串, 就知道怎么回事了.

怎么办? 看来无法混合编程的一个重要原因是MASM无法自动生成fastcall函数的修饰名, 这使得我们无法调用外部的

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值