标 题: 让你的MASM支持__fastcall调用方式
作 者: thebutterfly
时 间: 2006-02-03 01:21
链 接: http://bbs.pediy.com/showthread.php?threadid=20938
详细信息:
让MASM支持__fastcall调用方式作者:thebutterfly(Cloud)熟悉逆向工程和破解的朋友都知道,调用函数是要遵循一定的调用约定的.常见的调用约定有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+yaddeax,z;最后加上z,此时eax中为返回值x+y+zretn4;自己清除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,intz);//声明外部函数......intresult=AddNum(1,2,3);//调用...编译没问题,连接时却不对劲了,提示Errorxxxx:Anunresolvedsymbol"@AddNum@12"奇怪?!我们明明定义的是名为AddNum的函数,为什么会提示找不到@AddNum@12呢?再做一个试验,这次由汇编语言来调用C写的AddNum函数,代码如下:AddNumPROTOz:DWORD;声明原型,因为x,y都进了寄存器,这儿只有一个参数z...movecx,实参x;这几句不多说了,和上面一模一样,关键看结果movedx,实参ypush实参zcallAddNum...结果,@R_502_304@如下: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,edxaddeax,zretn4@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.0WrittenByCloud,NJUCopyrightbyCloud,2006这个头文件提供了对快速调用(FastCall)的支持------------------------------------------------------------------------------------------------>IFNDEF_FASTCALL_M__FASTCALL_M_=-1;;------------------------------------------------------------------------------------;;(MA)高级函数返回宏;;;;用法:return返回值[,堆栈平衡数];;;;参数:返回值,函数的返回值;;平衡数,函数返回自动清栈时的参数(有几个压栈参数就写多少),仅fastcall必需,其他情况不要;;;;影响的寄存器:eax(必然);;;;------------------------------------------------------------------------------------IFNDEFreturnreturnMACROarg,argsizeLOCALnum,reaxreax=0IFB<argsize>numTEXTEQU<>ELSEnumTEXTEQU%(&argsize&*4)ENDIFIFB<arg>retnumELSEIF@SizeStr(arg)GE8IFIDNI@SubStr(arg,1,7),<OFFSET>moveax,argreax=-1ENDIFENDIFIF@SizeStr(arg)GE10IFIDNI@SubStr(arg,9),<LROFFSET>moveax,argreax=-1ENDIFENDIFIFNOTreaxIF(OPATTR(arg))AND00010000b;;IsaregistervalueIFDIFI<arg>,<eax>;;donotmoveeaxontoitselfmoveax,argretnumreax=-1ELSEretnumENDIFELSEIF(OPATTR(arg))AND00000100b;;IsanimmediatevalueIF(OPATTR(arg))AND00000001bmoveax,argretnumELSEIFargEQ0xoreax,eaxretnumELSEIFargEQ1xoreax,eaxinceaxretnumELSEIFargEQ-1oreax,NOT0retnumELSEmoveax,argretnumENDIFENDIFreax=-1ELSEmoveax,argretnumreax=-1ENDIFENDIFENDIFENDMENDIF;;------------------------------------------------------------------------------------;;(MF)翻转参数列表;;;;用法:$ArgRev(参数表);;;;参数:原参数列表(VARARG);;;返回值:翻转后的参数列表;;;影响的寄存器:无;;;;------------------------------------------------------------------------------------IFNDEF$ArgRev$ArgRevMACROargs:VARARGLOCALarg,yyTEXTEQU<>FORarg,<&args>yCATSTR<arg>,<!,>,yENDMySUBSTRy,@SizeStr(%y)-1EXITM@CatStr(<!<>,y,<!>> )ENDMENDIF;;------------------------------------------------------------------------------------;;(MA)快速调用;;;;用法:fastcall函数名,参数1,参数2,....;;;;参数:函数名+参数列表;;;;影响的寄存器:eax,ecx,edx(必须改变);;;;------------------------------------------------------------------------------------IFNDEFfastcallfastcallMACROapi:REQ,p1,p2,px:VARARGLOCALarg,line,recx,reax,redxrecx=0reax=0redx=0IFNB<px>%FORarg,$ArgRev(<px>)IF@SizeStr(<arg> )GE6IFIDNI@SubStr(<arg>,5),<ADDR>leaeax,@SubStr(<arg>,6)pusheaxreax=-1ELSEpushargENDIFELSEIFIDNI<arg>,<eax>;;DonotoverwriteeaxIFreaxlineTEXTEQU%@Line%ECHO@FileCur(line):ERROR!EAXregistervalueoverwrittenbyfastcallmacro..ERRELSEpushargENDIFELSEpushargENDIFENDIFENDMENDIFIFNB<p1>IF@SizeStr(<p1> )GE6IFIDNI@SubStr(<p1>,<ADDR>%leaecx,@SubStr(<p1>,6)recx=-1ENDIFENDIFIF@SizeStr(<p1> )GE8IFIDNI@SubStr(<p1>,<OFFSET>movecx,p1recx=-1ENDIFENDIFIF@SizeStr(<p1> )GE10IFIDNI@SubStr(<p1>,<LROFFSET>movecx,p1recx=-1ENDIFENDIFIF(NOTrecx)IF(OPATTR(p1))AND00000100b;;IsanimmediatevalueIF(OPATTR(p1))AND00000001bmovecx,p1ELSEIFp1EQ0xorecx,ecxELSEIFp1EQ1xorecx,ecxincecxELSEIFp1EQ-1orecx,-1ELSEmovecx,p1ENDIFENDIFrecx=-1ELSEIF(OPATTR(p1))AND00010000b;;IsaregistervalueIFDIFI<p1>,<ecx>IFIDNI<p1>,<eax>IFreaxlineTEXTEQU%@Line%ECHO@FileCur(line):ERROR!EAXregistervaluehaschanged..ERRENDIFENDIFmovecx,p1recx=-1;;nomoreecxENDIFELSEmovecx,p1recx=-1ENDIFENDIFENDIFIFNB<p2>IF@SizeStr(<p2> )GE6IFIDNI@SubStr(<p2>,<ADDR>%leaedx,@SubStr(<p2>,6)redx=-1ENDIFENDIFIF@SizeStr(<p2> )GE8IFIDNI@SubStr(<p2>,<OFFSET>movedx,p2redx=-1ENDIFENDIFIF@SizeStr(<p2> )GE10IFIDNI@SubStr(<p2>,<LROFFSET>movedx,p2redx=-1ENDIFENDIFIF(NOTredx)IF(OPATTR(p2))AND00010000b;;IsaregistervalueIFDIFI<p2>,<edx>;;donotmoveedxontoitselfIFIDNI<p2>,<eax>IFreaxlineTEXTEQU%@Line%ECHO@FileCur(line):ERROR!EAXregistervaluehaschanged..ERRENDIFENDIFIFIDNI<p2>,<ecx>IFrecx;;ifecxwasusedreporterrorlineTEXTEQU%@Line%ECHO@FileCur(line):ERROR!ECXregistervaluehaschanged..ERRENDIFENDIFmovedx,p2redx=-1ENDIFELSEIF(OPATTR(p2))AND00000100b;;IsanimmediatevalueIF(OPATTR(p2))AND00000001bmovecx,p2ELSEIFp2EQ0xoredx,edxELSEIFp2EQ1xoredx,edxincedxELSEIFp2EQ-1oredx,-1ELSEmovedx,p2ENDIFENDIFredx=-1ELSEmovedx,p2redx=-1ENDIFENDIFENDIFcallapiENDMENDIF;;------------------------------------------------------------------------------------;;(MF)快速调用宏函数;;;;用法:返回值=$fastcall(函数名,参数2...);;;;参数:同fastcall;;;;返回值:该函数的返回值(eax);;;;影响的寄存器:同fastcall;;;;------------------------------------------------------------------------------------IFNDEF$fastcall$fastcallMACROapi:REQ,px:VARARGfastcallapi,pxEXITM<eax>ENDMENDIF;;------------------------------------------------------------------------------------;;(MF)快速调用函数名修饰宏;;;;用法:$FcallFuncNameGen(函数名,参数个数);;;;参数:函数名,该函数参数个数;;;;返回值:函数的修饰名;;;;影响的寄存器:无;;;;------------------------------------------------------------------------------------IFNDEF$FcallFuncNameGen$FcallFuncNameGenMACROfname:REQ,argsize:REQLOCALnumnumTEXTEQU%(&argsize&*4)EXITM@CatStr(<@>,<fname>,<@>,%num)ENDMENDIF;;------------------------------------------------------------------------------------;;(MF)定义一个快速调用的函数头;;;;用法:[$]BeginfcProc(函数名,距离])Uses寄存器列表除第1,2参数外其他参数;;;;参数:不用多说;;;;返回值:无需操心;;;;影响的寄存器:无;;;;------------------------------------------------------------------------------------IFNDEF$BeginfcProc$BeginfcProcMACROfname:REQ,argsize:REQ,distancefnameTEXTEQU$FcallFuncNameGen(fname,argsize)IFB<distance>EXITM<$FcallFuncNameGen(fname,argsize)PROCSYSCALL>ELSEEXITM<$FcallFuncNameGen(fname,argsize)PROC&distance&SYSCALL>ENDIFENDMBeginfcProcTEXTEQU<$BeginfcProc>ENDIF;;------------------------------------------------------------------------------------;;(MF)快速调用的函数结尾,必须和$BeginfcProc成对使用;;;;用法:$EndfcProc(函数名,参数个数);;;;参数:不用多讲;;;;返回值:无需操心;;;;影响的寄存器:无;;;;------------------------------------------------------------------------------------IFNDEF$EndfcProc$EndfcProcMACROfname:REQ,argsize:REQEXITM<$FcallFuncNameGen(fname,argsize)ENDP>ENDMEndfcProcTEXTEQU<$EndfcProc>ENDIF;;------------------------------------------------------------------------------------;;(MA)快速调用函数的专用返回宏;;;;;;用法:fcret返回值,参数个数;;;;参数:同return,会自动修正清栈参数;;;;;;影响的寄存器:eax(必须);;;;------------------------------------------------------------------------------------IFNDEFfcretfcretMACROretv,argsize:REQLOCALnumIFargsizeGT2numTEXTEQU%(&argsize&-2)ELSEnumTEXTEQU<0>ENDIFreturnretv,numENDMENDIF;;------------------------------------------------------------------------------------;;用于声明一个外部的fastcall函数;;;;用法:$FcProto(函数名,参数个数);;;;参数:不用多说;;;;;;影响的寄存器:无;;;;------------------------------------------------------------------------------------IFNDEF$FcProto$FcProtoMACROfname:REQ,argsize:REQfnameTEXTEQU$FcallFuncNameGen(fname,argsize)EXITM@CatStr(<EXTERNDEFSYSCALL>,<$FcallFuncNameGen(fname,argsize)>,<:PROC> )ENDMFcProtoTEXTEQU<$FcProto>ENDIF;;EndofFileENDIF;;FastCall.inc