文章作者:thebutterfly
熟悉逆向工程和破解的朋友都知道,调用函数是要遵循一定的调用约定的.常见的调用约定有C调用约定(__cdecl),标准调用约定(__stdcall),Pascal调用约定,快速调用约定(__fastcall)等几种.在这些调用约定中,MASM对前三者都有很好的支持,唯独对__fastcall调用约定不支持,这不能不说是一种遗憾.正因为这个,MASM在处理__fastcall类型的函数时显得相当不便,例如在写内核模式驱动程序时有一个相当常见的函数IofCompleteRequest,普通方法还真拿它没辙:首先函数原型声明就是一个大问题,普通的声明方法结果都是"Errorxxxx:Cannotresolveexternalsymbolyyyyy";其次是调用的语法,invoke显然是行不通的,它只会把参数压栈然后一个call了事,而这不是__fastcall的形式.也同样是因为不支持__fastcall,才使得汇编和其他语言(例如C)混合编程时一旦涉及__fastcall就举步维艰.
穷则思变,我们都希望采取某些策略,使MASM支持__fastcall调用方式,换句话说就是使MASM能够定义和调用__fastcall类型的函数.
要达到这个目标,我们首先要了解(标准)__fastcall调用的特点.
__fastcall的特点是:第1个参数放入ecx寄存器,第2个参数放入edx寄存器,如果还有参数则自右向左依次压栈;由被调者负责维护堆栈平衡(清除压入的参数);如果函数有返回值则把返回值存放在eax寄存器中.函数名的修饰特点是:在函数名前加@,后面加@并跟4*参数个数.例如有两个参数的IofCompleteRequest被修饰为:"@IofCompleteRequest@8".
我们先看看如何在汇编语言内部处理__fastcall函数.假设我们要用汇编定义并调用这样一个函数:
int__fastcallAddNum(intx,inty,intz){
returnx+y+z;
}
如何定义这个函数?容易看出有3个参数,根据__fastcall调用的特点,x应当存入ecx寄存器中,y则进edx,还有一个参数z则采用堆栈传递,函数执行完毕应当自己清理压入的参数(retn4返回),不难得到我们的初始想法如下:
AddNumPROCz;这里x,y不见了,因为它们都跑寄存器里去了
moveax,ecx;ecx即为x参数
addeax,edx;edx即为y参数,这儿计算x+y
addeax,z;最后加上z,此时eax中为返回值x+y+z
retn4;自己清除z参数
AddNumENDP
如何调用它?前面已经说过,invoke是肯定不行的,因为它只会把参数一股脑儿全部压栈,不符合__fastcall的特点.我们只能采用原始的方式,也就是写一堆处理参数的代码然后一个callxxx.根据__fastcall的特点,我们得到调用代码如下:
.....;其他代码
movecx,实参x;ecx中存放实参x(第1参数)
movedx,实参y;edx中存放实参y(第2参数)
push实参z;根据调用特点,z应当通过栈传递
callAddNum;调用
.....;其他代码,如处理返回值等.调用者自己不必操心堆栈的平衡
经过试验,这似乎是可以的,不仅没有语法错误,没有破坏堆栈,而且反汇编和调用的结果也十分正确,问题似乎已经解决了.
然而,进一步的考察打碎了我们的迷梦,我们做这样一个试验:把我们这个函数去和C语言做混合编程,在C程序里如下调用:
extrn"C"int__fastcallAddNum(intx,inty,intz);//声明外部函数
...
...
intresult=AddNum(1,2,3);//调用
...
编译没问题,连接时却不对劲了,提示Errorxxxx:Anunresolvedsymbol"@AddNum@12"
奇怪?!我们明明定义的是名为AddNum的函数,为什么会提示找不到@AddNum@12呢?
再做一个试验,这次由汇编语言来调用C写的AddNum函数,代码如下:
AddNumPROTOz:DWORD;声明原型,因为x,y都进了寄存器,这儿只有一个参数z
...
movecx,实参x;这几句不多说了,和上面一模一样,关键看结果
movedx,实参y
push实参z
callAddNum
...
结果,提示错误如下:Errorxxxx:Cannotresolveexternalsymbol_AddNum@4
还是奇怪?!明明声明的是AddNum函数却提示找不到_AddNum@4!
事实使我们意识到,我们上面所做的并没有完全达到我们的目标,解决了"内政"却没有解决好"外交"问题.
静下心来分析一下为什么连接会出错.每次我们定义AddNum函数时,连接器总提示找不到xxxAddNumxxx形式的符号名,这是为什么?
对,符号名修饰!这是问题的关键!
(这里顺便扯几句:大部分所谓"混合编程",都要注意三个问题,一个是函数调用方式,第二个是obj文件格式,最后一个就是符号修饰了.那些所谓的"VB和VC混合编程","VB和汇编混合编程"能够实现的重要原因是:它们使用的都是同一个link.exe程序!更本质的原因是:虽然使用的是不同的编译器,但是编译出来的obj文件是清一色的coff格式,正是因为格式是统一的,才使混合编程成为可能.而如果想把tasm和VC++混合编程就麻烦多了,因为两种obj的格式是不同的)
根据调用类型的不同,符号名的修饰方式是不同的.我们在写Win32汇编程序时都会写一句:
.modelflat,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函数的修饰名,这使得我们无法调用外部的__fastcall函数,也使得外部无法调用我们写的函数.那么似乎要解决这个问题,唯一的办法是自己生成符号修饰名!这又要注意的是,函数修饰名是和函数的语言类型(调用方式)相关的,前面提到,我们自己写的函数由于那句.model声明都自动变为stdcall类型,编译时会被编译器自动修饰,如果我们写@AddNum@12PROCz,编译时会被自动修饰为_@AddNum@12@4(前面加了下划线,后面多了@4),这就违反了我们的本意了.所以我们要找一种调用方式,满足以下3个条件,作为fastcall的替代
1.不对函数名进行修饰(便于我们自己修饰)
2.参数由右向左压栈(否则无法直接引用参数,只能用[ebp+8}之类的操作符引用)
3.由函数清理入栈参数
这样的调用方式有吗?有的!就是SYSCALL调用方式!
所以我们的函数可以修改如下:
@AddNum@12PROCSYSCALLz;注意不同了吗?函数名被修饰了,而且加上了SYSCALL类型
moveax,ecx;这几句相同
addeax,edx
addeax,z
retn4
@AddNum@12ENDP;照应开头,这里同样修饰了
再去做试验1,成功了!!!
同理,试验2的call语句和PROTO语句要修改为
@AddNum@12PROTOSYSCALL:DWORD
和
call@AddNum@12
这样一来,试验2也成功了.问题终于被彻底解决.
总结如下:
1.如果是自己定义函数,函数名一定要按照fastcall的规则修饰,函数类型采用SYSCALL.
2.如果是调用其他模块的函数,call和proto的函数名也要修饰
简单吧,只不过做的时候麻烦点.
最后补充一句,如果要调用其他模块的函数,除了用proto进行声明外,用externdef声明也是可以的,象这样
EXTERNDEFSYSCALL@AddNum@12:PROC
具体采用哪种做法是个人偏好的问题
另外,如果嫌总是写修饰名比较烦,用TEXTEQU定义一个文本宏也是不错的注意,象这样:
AddNumTEXTEQU<@AddNum@12>
这样程序里就可以直接用AddNum来调用了
********************************************************************************
********************************************************************************
为了方便定义和调用fastcall的函数,我写了一组宏,下面简单说一下使用方法
定义一个fastcall函数:
BeginfcProc(函数名,参数个数[,距离])[其他如Uses寄存器列表以及其余参数等}
示例:BeginfcProc(AddNum,3)usesebxediesiz:DWORD
结束定义:
EndfcProc(函数名,参数个数)
示例:EndfcProc(AddNum,3)
上面两个宏必须成对使用
调用一个fastcall的函数:
fastcall函数名[,参数1][,参数2][.......]
参数支持ADDR和OFFSET操作符,这个宏和invoke宏的语法基本相同
示例:fastcallExampleProc,ADDRdwNum,OFFSETszString,NULL
定义一个fastcall函数:
FcProto(函数名,参数个数)
这个宏采取externdef定义
示例:FcProto(AddNum,3)
********************************************************************************
以下是宏的内容,粘贴下来保存为asm或inc文件就可以直接用了,转载请保持完整
如果有问题欢迎大家批评指正!
********************************************************************************
COMMENT>---------------------------------------------------------------------------------------
FastCallMacrosVersion1.0
WrittenByCloud,NJU
CopyrightbyCloud,2006
这个头文件提供了对快速调用(FastCall)的支持
------------------------------------------------------------------------------------------------>
IFNDEF_FASTCALL_M_
_FASTCALL_M_=-1
;;------------------------------------------------------------------------------------
;;(MA)高级函数返回宏
;;
;;用法:return返回值[,堆栈平衡数]
;;
;;参数:返回值,函数的返回值
;;平衡数,函数返回自动清栈时的参数(有几个压栈参数就写多少),仅fastcall必需,其他情况不要
;;
;;影响的寄存器:eax(必然)
;;
;;------------------------------------------------------------------------------------
IFNDEFreturn
returnMACROarg,argsize
LOCALnum,reax
reax=0
IFB
numTEXTEQU<>
ELSE
numTEXTEQU%(&argsize&*4)
ENDIF
IFB
retnum
ELSE
IF@SizeStr(arg)GE8
IFIDNI@SubStr(arg,1,7),
moveax,arg
reax=-1
ENDIF
ENDIF
IF@SizeStr(arg)GE10
IFIDNI@SubStr(arg,1,9),
moveax,arg
reax=-1
ENDIF
ENDIF
IFNOTreax
IF(OPATTR(arg))AND00010000b;;Isaregistervalue
IFDIFI,;;donotmoveeaxontoitself
moveax,arg
retnum
reax=-1
ELSE
retnum
ENDIF
ELSEIF(OPATTR(arg))AND00000100b;;Isanimmediatevalue
IF(OPATTR(arg))AND00000001b
moveax,arg
retnum
ELSE
IFargEQ0
xoreax,eax
retnum
ELSEIFargEQ1
xoreax,eax
inceax
retnum
ELSEIFargEQ-1
oreax,NOT0
retnum
ELSE
moveax,arg
retnum
ENDIF
ENDIF
reax=-1
ELSE
moveax,arg
retnum
reax=-1
ENDIF
ENDIF
ENDIF
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MF)翻转参数列表
;;
;;用法:$ArgRev(参数表)
;;
;;参数:原参数列表(VARARG)
;
;;返回值:翻转后的参数列表
;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF$ArgRev
$ArgRevMACROargs:VARARG
LOCALarg,y
yTEXTEQU<>
FORarg,<&args>
yCATSTR,,y
ENDM
ySUBSTRy,1,@SizeStr(%y)-1
EXITM@CatStr(,y,> )
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MA)快速调用
;;
;;用法:fastcall函数名,参数1,参数2,....
;;
;;参数:函数名+参数列表
;;
;;影响的寄存器:eax,ecx,edx(必须改变)
;;
;;------------------------------------------------------------------------------------
IFNDEFfastcall
fastcallMACROapi:REQ,p1,p2,px:VARARG
LOCALarg,line,recx,reax,redx
recx=0
reax=0
redx=0
IFNB
%FORarg,$ArgRev()
IF@SizeStr( )GE6
IFIDNI@SubStr(,1,5),
leaeax,@SubStr(,6)
pusheax
reax=-1
ELSE
pusharg
ENDIF
ELSE
IFIDNI,;;Donotoverwriteeax
IFreax
lineTEXTEQU%@Line
%ECHO@FileCur(line):ERROR!EAXregistervalueoverwrittenbyfastcallmacro.
.ERR
ELSE
pusharg
ENDIF
ELSE
pusharg
ENDIF
ENDIF
ENDM
ENDIF
IFNB
IF@SizeStr( )GE6
IFIDNI@SubStr(,1,5),
%leaecx,@SubStr(,6)
recx=-1
ENDIF
ENDIF
IF@SizeStr( )GE8
IFIDNI@SubStr(,1,7),
movecx,p1
recx=-1
ENDIF
ENDIF
IF@SizeStr( )GE10
IFIDNI@SubStr(,1,9),
movecx,p1
recx=-1
ENDIF
ENDIF
IF(NOTrecx)
IF(OPATTR(p1))AND00000100b;;Isanimmediatevalue
IF(OPATTR(p1))AND00000001b
movecx,p1
ELSE
IFp1EQ0
xorecx,ecx
ELSEIFp1EQ1
xorecx,ecx
incecx
ELSEIFp1EQ-1
orecx,-1
ELSE
movecx,p1
ENDIF
ENDIF
recx=-1
ELSEIF(OPATTR(p1))AND00010000b;;Isaregistervalue
IFDIFI,
IFIDNI,
IFreax
lineTEXTEQU%@Line
%ECHO@FileCur(line):ERROR!EAXregistervaluehaschanged.
.ERR
ENDIF
ENDIF
movecx,p1
recx=-1;;nomoreecx
ENDIF
ELSE
movecx,p1
recx=-1
ENDIF
ENDIF
ENDIF
IFNB
IF@SizeStr( )GE6
IFIDNI@SubStr(,1,5),
%leaedx,@SubStr(,6)
redx=-1
ENDIF
ENDIF
IF@SizeStr( )GE8
IFIDNI@SubStr(,1,7),
movedx,p2
redx=-1
ENDIF
ENDIF
IF@SizeStr( )GE10
IFIDNI@SubStr(,1,9),
movedx,p2
redx=-1
ENDIF
ENDIF
IF(NOTredx)
IF(OPATTR(p2))AND00010000b;;Isaregistervalue
IFDIFI,;;donotmoveedxontoitself
IFIDNI,
IFreax
lineTEXTEQU%@Line
%ECHO@FileCur(line):ERROR!EAXregistervaluehaschanged.
.ERR
ENDIF
ENDIF
IFIDNI,
IFrecx;;ifecxwasusedreporterror
lineTEXTEQU%@Line
%ECHO@FileCur(line):ERROR!ECXregistervaluehaschanged.
.ERR
ENDIF
ENDIF
movedx,p2
redx=-1
ENDIF
ELSEIF(OPATTR(p2))AND00000100b;;Isanimmediatevalue
IF(OPATTR(p2))AND00000001b
movecx,p2
ELSE
IFp2EQ0
xoredx,edx
ELSEIFp2EQ1
xoredx,edx
incedx
ELSEIFp2EQ-1
oredx,-1
ELSE
movedx,p2
ENDIF
ENDIF
redx=-1
ELSE
movedx,p2
redx=-1
ENDIF
ENDIF
ENDIF
callapi
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MF)快速调用宏函数
;;
;;用法:返回值=$fastcall(函数名,参数1,参数2...)
;;
;;参数:同fastcall
;;
;;返回值:该函数的返回值(eax)
;;
;;影响的寄存器:同fastcall
;;
;;------------------------------------------------------------------------------------
IFNDEF$fastcall
$fastcallMACROapi:REQ,p1,p2,px:VARARG
fastcallapi,p1,p2,px
EXITM
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MF)快速调用函数名修饰宏
;;
;;用法:$FcallFuncNameGen(函数名,参数个数)
;;
;;参数:函数名,该函数参数个数
;;
;;返回值:函数的修饰名
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF$FcallFuncNameGen
$FcallFuncNameGenMACROfname:REQ,argsize:REQ
LOCALnum
numTEXTEQU%(&argsize&*4)
EXITM@CatStr(<@>,,<@>,%num)
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MF)定义一个快速调用的函数头
;;
;;用法:[$]BeginfcProc(函数名,参数个数[,距离])Uses寄存器列表除第1,2参数外其他参数
;;
;;参数:不用多说
;;
;;返回值:无需操心
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF$BeginfcProc
$BeginfcProcMACROfname:REQ,argsize:REQ,distance
fnameTEXTEQU$FcallFuncNameGen(fname,argsize)
IFB
EXITM<$FcallFuncNameGen(fname,argsize)PROCSYSCALL>
ELSE
EXITM<$FcallFuncNameGen(fname,argsize)PROC&distance&SYSCALL>
ENDIF
ENDM
BeginfcProcTEXTEQU<$BeginfcProc>
ENDIF
;;------------------------------------------------------------------------------------
;;(MF)快速调用的函数结尾,必须和$BeginfcProc成对使用
;;
;;用法:$EndfcProc(函数名,参数个数)
;;
;;参数:不用多讲
;;
;;返回值:无需操心
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF$EndfcProc
$EndfcProcMACROfname:REQ,argsize:REQ
EXITM<$FcallFuncNameGen(fname,argsize)ENDP>
ENDM
EndfcProcTEXTEQU<$EndfcProc>
ENDIF
;;------------------------------------------------------------------------------------
;;(MA)快速调用函数的专用返回宏
;;
;;
;;用法:fcret返回值,参数个数
;;
;;参数:同return,会自动修正清栈参数
;;
;;
;;影响的寄存器:eax(必须)
;;
;;------------------------------------------------------------------------------------
IFNDEFfcret
fcretMACROretv,argsize:REQ
LOCALnum
IFargsizeGT2
numTEXTEQU%(&argsize&-2)
ELSE
numTEXTEQU<0>
ENDIF
returnretv,num
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;用于声明一个外部的fastcall函数
;;
;;用法:$FcProto(函数名,参数个数)
;;
;;参数:不用多说
;;
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF$FcProto
$FcProtoMACROfname:REQ,argsize:REQ
fnameTEXTEQU$FcallFuncNameGen(fname,argsize)
EXITM@CatStr(,<$FcallFuncNameGen(fname,argsize)>,<:PROC> )
ENDM
FcProtoTEXTEQU<$FcProto>
ENDIF
;;EndofFile
ENDIF;;FastCall.inc