邪恶八进制信息安全团队技术讨论组's Archiver

pub!1c 2006-2-5 13:01

[转载]让你的MASM支持__fastcall调用方式

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

页: [1]
© 1999-2008 EvilOctal Security Team