请教一下:
1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类型(参数要通过解析才知道个数和形式),如何动态调用这个函数?例如有一个字符串,前4个字节是函数地址,后面每个字节指示一个参数的形式(可以查表对应得知,也可能其中第5个字节指示返回值类型),
就结束。 1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类
1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类型(参数要通过解析才知道个数和形式),如何动态调用这个函数?例如有一个字符串,前4个字节是函数地址,后面每个字节指示一个参数的形式(可以查表对应得知,也可能其中第5个字节指示返回值类型),
请教一下:
1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类型(参数要通过解析才知道个数和形式),如何动态调用这个函数?例如有一个字符串,前4个字节是函数地址,后面每个字节指示一个参数的形式(可以查表对应得知,也可能其中第5个字节指示返回值类型),\0就结束。
2.附带一个问题:以我的经验,一般函数地址都是4字节的,也不知道是不是所有的都是这样的。结构的指针也是4字节,类指针好象也是。这个也请大家指点指点。
对问题1还有些补充,我发过一个关于求教可变参数个数的函数,例如int fun(int first,…)的帖子,https://topic.csdn.net/u/20070808/11/ad55faef-0411-4f90-a183-201e9c755b28.html,如果用指针形式调用似乎就解决了;另一个我想到的可能的解决方案是内嵌asm;(可是这两门工夫我都不会,……)所以还请各位高手出手相助。
1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类型(参数要通过解析才知道个数和形式),如何动态调用这个函数?例如有一个字符串,前4个字节是函数地址,后面每个字节指示一个参数的形式(可以查表对应得知,也可能其中第5个字节指示返回值类型),\0就结束。
2.附带一个问题:以我的经验,一般函数地址都是4字节的,也不知道是不是所有的都是这样的。结构的指针也是4字节,类指针好象也是。这个也请大家指点指点。
对问题1还有些补充,我发过一个关于求教可变参数个数的函数,例如int fun(int first,…)的帖子,https://topic.csdn.net/u/20070808/11/ad55faef-0411-4f90-a183-201e9c755b28.html,如果用指针形式调用似乎就解决了;另一个我想到的可能的解决方案是内嵌asm;(可是这两门工夫我都不会,……)所以还请各位高手出手相助。
19 个解决方案
#1
2.附带一个问题:以我的经验,一般函数地址都是4字节的,也不知道是不是所有的都是这样的。结构的指针也是4字节,类指针好象也是。这个也请大家指点指点。
===32位寻址,地址自然都是4字节。到64位机上,就不是这样子了。
===32位寻址,地址自然都是4字节。到64位机上,就不是这样子了。
#2
先说第2个问题,32位程序的地址是32位,所以指针是4字节,如果是64位程序,则是8字节。
第1个问题,这个问题只能用汇编语言来解决,首先要知道函数的参数传递约定,例如C/C++默认使用__cdecl方式,从最后面开始,逐个参数push到栈里面,然后call函数地址,函数返回后,再自己恢复esp,你可以这样做:(例如有A、B、C三个参数)
push ebp
mov ebp, esp
push C
push B
push A
call 函数地址
mov esp, ebp
pop ebp
第1个问题,这个问题只能用汇编语言来解决,首先要知道函数的参数传递约定,例如C/C++默认使用__cdecl方式,从最后面开始,逐个参数push到栈里面,然后call函数地址,函数返回后,再自己恢复esp,你可以这样做:(例如有A、B、C三个参数)
push ebp
mov ebp, esp
push C
push B
push A
call 函数地址
mov esp, ebp
pop ebp
#3
方法有两种:
1。定义函数指针,调用约定为__stdcall
typedef int (__stdcall * MYFUN)(int);
MYFUN fun = (MYFUN)0x123;
//0x123为函数地址
//打印返回值
printf("%d",fun(10));
2。汇编,调用约定为__stdcall
int nRet;//这个是返回值
int nData;//这个是参数
__asm
{
mov eax,nData//参数入栈
push eax
mov eax,0x123//函数地址为0x123
call eax//调用函数地址
mov nRet,eax//保存返回值
}
printf("%d",nRet);//打印返回值
1。定义函数指针,调用约定为__stdcall
typedef int (__stdcall * MYFUN)(int);
MYFUN fun = (MYFUN)0x123;
//0x123为函数地址
//打印返回值
printf("%d",fun(10));
2。汇编,调用约定为__stdcall
int nRet;//这个是返回值
int nData;//这个是参数
__asm
{
mov eax,nData//参数入栈
push eax
mov eax,0x123//函数地址为0x123
call eax//调用函数地址
mov nRet,eax//保存返回值
}
printf("%d",nRet);//打印返回值
#4
如果你能正确解析参数个数类型和返回值类型,就简单的定义该类型的函数指针。
不知道函数名?那怎么知道函数地址的?在本进程么?
不同进程下的地址不能混用。
知道函数名就简单得GetProcAddress
不知道函数名?那怎么知道函数地址的?在本进程么?
不同进程下的地址不能混用。
知道函数名就简单得GetProcAddress
#5
出手的都是高手啊,一共7个星了,呵呵。
能详细解释一下吗?32位系统为4字节这个好理解,但是返回值类型之类的信息不用保存在指针里吗,这些信息也要占空间的啊,难道是编译阶段静态搞定的吗?随便问下怎么能知道系统是多少位的啊?
ls的高手能写个示例代码吗,不胜感激啊,偶只会8086汇编,还多年不用了,更不会内嵌这门工夫,但是写出来估计就能看懂了,自己再改改大概也就能用了。
能详细解释一下吗?32位系统为4字节这个好理解,但是返回值类型之类的信息不用保存在指针里吗,这些信息也要占空间的啊,难道是编译阶段静态搞定的吗?随便问下怎么能知道系统是多少位的啊?
ls的高手能写个示例代码吗,不胜感激啊,偶只会8086汇编,还多年不用了,更不会内嵌这门工夫,但是写出来估计就能看懂了,自己再改改大概也就能用了。
#6
返回值通常是在eax里面,如果返回值是64位,就用eax和edx两个寄存器,高32位放在edx里面。
#7
看来LZ还没接触过“逆向工程”:)。
#8
ls的说的正是,偶很少接触反编译跟踪破解之类的技术
3楼的汇编codes不错,看懂了,不过第一种方式肯定不行了(我在解析出参数形式之前根本就不知道可能调用的函数是怎样的,静态MYFUN fun没法做到)
顺便问下,能指点下__cdecl、__stdcall以及平时能接触到的调用方式多个参数的入栈顺序是怎样的啊?
如果是多位的参数是高位先入栈还是低位的啊?
函数返回后原来入栈传入的参数需要出栈吗,还是系统帮我搞了?
3楼的汇编codes不错,看懂了,不过第一种方式肯定不行了(我在解析出参数形式之前根本就不知道可能调用的函数是怎样的,静态MYFUN fun没法做到)
顺便问下,能指点下__cdecl、__stdcall以及平时能接触到的调用方式多个参数的入栈顺序是怎样的啊?
如果是多位的参数是高位先入栈还是低位的啊?
函数返回后原来入栈传入的参数需要出栈吗,还是系统帮我搞了?
#9
1:LZ不是可以解析出参数形式吗?自然可以使用
void * fun(void * p...)等形式.(枚举N个..)然后根据解析选用..虽然有点累,,呵呵..
也可以使用function<void(void *p)> f;
2:光嵌入汇编是不够的,代码是静态的.
例如:
int n = 1;
int m = 2;
你不可能在运行的时候在两句话中插一语句.
可以采用动态构造代码的方式进行调用
先明白一些道理(假设目标函数是__stdcall).如果我说得不对,大家请指正
1:)参数入栈是从右到左,指令是push
2:)每个参数是4字节对齐的,即使只有1个char
3:)返回值存放在eax,清栈是由被调函数解决的.因此__stdcall无法使用可变参数.
下面是大概的汇编代码,假设有N个参数
push 1
push 2
....
push N
call adr//调用函数
mov nret,eax//保存返回值到eax中
但是很明显一个问题是我们不知道到底要push多少个参数..
那么我们可以在缓冲区动态构造这段调用代码.
简单来说,我们获得参数的个数后,向一段缓冲区写入N个push参数的2进制代码.(2进制代码怎么构造?请别问我,汇编和2进制代码是一样的,请查手册).最后我们只需要一个jmp到这个缓冲上...即可..
-----
沉重哀悼地震中逝去的同胞!
void * fun(void * p...)等形式.(枚举N个..)然后根据解析选用..虽然有点累,,呵呵..
也可以使用function<void(void *p)> f;
2:光嵌入汇编是不够的,代码是静态的.
例如:
int n = 1;
int m = 2;
你不可能在运行的时候在两句话中插一语句.
可以采用动态构造代码的方式进行调用
先明白一些道理(假设目标函数是__stdcall).如果我说得不对,大家请指正
1:)参数入栈是从右到左,指令是push
2:)每个参数是4字节对齐的,即使只有1个char
3:)返回值存放在eax,清栈是由被调函数解决的.因此__stdcall无法使用可变参数.
下面是大概的汇编代码,假设有N个参数
push 1
push 2
....
push N
call adr//调用函数
mov nret,eax//保存返回值到eax中
但是很明显一个问题是我们不知道到底要push多少个参数..
那么我们可以在缓冲区动态构造这段调用代码.
简单来说,我们获得参数的个数后,向一段缓冲区写入N个push参数的2进制代码.(2进制代码怎么构造?请别问我,汇编和2进制代码是一样的,请查手册).最后我们只需要一个jmp到这个缓冲上...即可..
-----
沉重哀悼地震中逝去的同胞!
#10
打错了..很抱歉..
mov nret,eax//保存返回值到eax中
应该是保存到nret中..
我在3L给的汇编是在VC里面嵌入的.
mov nret,eax//保存返回值到eax中
应该是保存到nret中..
我在3L给的汇编是在VC里面嵌入的.
#11
不错,看了KeSummer的回贴更清晰了
枚举N个调用形式或者使用模版的确可以,不过代码长度和效率可能太残,所以来才发这个帖子求教,那么搞太累了,坚决不用.
"我们获得参数的个数后,向一段缓冲区写入N个push参数的2进制代码.(2进制代码怎么构造?请别问我,汇编和2进制代码是一样的,请查手册).最后我们只需要一个jmp到这个缓冲上...即可.."
看懂了,动态生成代码,真高明,头一次接触,很绝啊.不过请问我这么行不行啊,伪代:
for(;n;n--)//n为需要入栈的参数个数,解析出来的
{
/*下面这句还得查点资料反正是按顺序强制转化指针指向的变量大小,num这个字符串里的值是解析生成的,标志每个参数的字节数大小*/
nData=*((BYTE *(num[n]))pChar)//pChar是个字符串,里面按顺序存着参数,ndata为4字节的
pChar+=num[n];
_asm{
mov eax,nData
push eax//参数入栈
}
}
"但是很明显一个问题是我们不知道到底要push多少个参数",这个好象不对吧,调用的时候知道有多少参数(怎么也得解析出来才能去调用啊),但是被调用函数自己可能不知道调用者要传给自己多少参数(比如int fun(type first,...)这种参数个数可变的函数,但是这种情况很少,一般也是都知道的)
(我提问时提到参数个数可变的函数是作为一种可能的解决"方案"提出来的,和问题本身关系关系不大,当时我想要是把函数地址强制转化成这种形式,然后调用可能就解决了,不过如何传入参数仍然是关键)
枚举N个调用形式或者使用模版的确可以,不过代码长度和效率可能太残,所以来才发这个帖子求教,那么搞太累了,坚决不用.
"我们获得参数的个数后,向一段缓冲区写入N个push参数的2进制代码.(2进制代码怎么构造?请别问我,汇编和2进制代码是一样的,请查手册).最后我们只需要一个jmp到这个缓冲上...即可.."
看懂了,动态生成代码,真高明,头一次接触,很绝啊.不过请问我这么行不行啊,伪代:
for(;n;n--)//n为需要入栈的参数个数,解析出来的
{
/*下面这句还得查点资料反正是按顺序强制转化指针指向的变量大小,num这个字符串里的值是解析生成的,标志每个参数的字节数大小*/
nData=*((BYTE *(num[n]))pChar)//pChar是个字符串,里面按顺序存着参数,ndata为4字节的
pChar+=num[n];
_asm{
mov eax,nData
push eax//参数入栈
}
}
"但是很明显一个问题是我们不知道到底要push多少个参数",这个好象不对吧,调用的时候知道有多少参数(怎么也得解析出来才能去调用啊),但是被调用函数自己可能不知道调用者要传给自己多少参数(比如int fun(type first,...)这种参数个数可变的函数,但是这种情况很少,一般也是都知道的)
(我提问时提到参数个数可变的函数是作为一种可能的解决"方案"提出来的,和问题本身关系关系不大,当时我想要是把函数地址强制转化成这种形式,然后调用可能就解决了,不过如何传入参数仍然是关键)
#12
for(;n;n--)//n为需要入栈的参数个数,解析出来的
{
/*下面这句还得查点资料反正是按顺序强制转化指针指向的变量大小,num这个字符串里的值是解析生成的,标志每个参数的字节数大小*/
nData=*((BYTE *(num[n]))pChar)//pChar是个字符串,里面按顺序存着参数,ndata为4字节的
pChar+=num[n];
_asm{
mov eax,nData
push eax//参数入栈
}
}
-----
你应该先看看栈有没被毁掉!
"但是很明显一个问题是我们不知道到底要push多少个参数",
----------
我的意思是说在解析出来之前不知道多少个参数。。
{
/*下面这句还得查点资料反正是按顺序强制转化指针指向的变量大小,num这个字符串里的值是解析生成的,标志每个参数的字节数大小*/
nData=*((BYTE *(num[n]))pChar)//pChar是个字符串,里面按顺序存着参数,ndata为4字节的
pChar+=num[n];
_asm{
mov eax,nData
push eax//参数入栈
}
}
-----
你应该先看看栈有没被毁掉!
"但是很明显一个问题是我们不知道到底要push多少个参数",
----------
我的意思是说在解析出来之前不知道多少个参数。。
#13
恩,思路都差不多了,我开始调试了,顺便请教下怎么能看到函数被翻译成汇编语言的中间状态啊(就是在vc6里看程序被翻译成汇编的代码,应该怎么设置啊?)
#14
调试, 然后按alt+8 可看到汇编
#15
学习
#16
如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类型(参数要通过解析才知道个数和形式),如何动态调用这个函数?
肯定 可以!
肯定 可以!
#17
又找了点资料,但是找不到原来的联接了:
作者:星轨(oRbIt
E_Mail:[email protected]
转载请注明原作者,否则请勿转载
使用C/C++语言开发软件的程序员经常碰到这样的问题:有时候是程序编译没有问题,但是链接的时候总是报告函数不存在(经典的LNK 2001错误),有时候是程序编译和链接都没有错误,但是只要调用库中的函数就会出现堆栈异常。这些现象通常是出现在C和C++的代码混合使用的情况下或在C++程序中使用第三方的库的情况下(不是用C++语言开发的),其实这都是函数调用约定(Calling Convention)和函数名修饰(Decorated Name)规则惹的祸。函数调用方式决定了函数参数入栈的顺序,是由调用者函数还是被调用函数负责清除栈中的参数等问题,而函数名修饰规则决定了编译器使用何种名字修饰方式来区分不同的函数,如果函数之间的调用约定不匹配或者名字修饰不匹配就会产生以上的问题。本文分别对C和C++这两种编程语言的函数调用约定和函数名修饰规则进行详细的解释,比较了它们的异同之处,并举例说明了以上问题出现的原因。
函数调用约定(Calling Convention)
函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持naked call方式。这么多函数调用约定常常令许多程序员很迷惑,到底它们是怎么回事,都是在什么情况下使用呢?下面就分别介绍这几种函数调用约定。
1.__cdecl
编译器的命令行参数是/Gd。__cdecl方式是C/C++编译器默认的函数调用约定,所有非C++成员函数和那些没有用__stdcall或__fastcall声明的函数都默认是__cdecl方式,它使用C函数调用方式,函数参数按照从右向左的顺序入栈,函数调用者负责清除栈中的参数,由于每次函数调用都要由编译器产生清除(还原)堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是__cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf和windows的API wsprintf就是__cdecl调用方式。对于C函数,__cdecl方式的名字修饰约定是在函数名称前添加一个下划线;对于C++函数,除非特别使用extern "C",C++函数使用不同的名字修饰方式。
2.__fastcall
编译器的命令行参数是/Gr。__fastcall函数调用约定在可能的情况下使用寄存器传递参数,通常是前两个 DWORD类型的参数或较小的参数使用ECX和EDX寄存器传递,其余参数按照从右向左的顺序入栈,被调用函数在返回之前负责清除栈中的参数。编译器使用两个@修饰函数名字,后跟十进制数表示的函数参数列表大小,例如:@function_name@number。需要注意的是__fastcall函数调用约定在不同的编译器上可能有不同的实现,比如16位的编译器和32位的编译器,另外,在使用内嵌汇编代码时,还要注意不能和编译器使用的寄存器有冲突。
3.__stdcall
编译器的命令行参数是/Gz,__stdcall是Pascal程序的缺省调用方式,大多数Windows的API也是__stdcall调用约定。__stdcall函数调用约定将函数参数从右向左入栈,除非使用指针或引用类型的参数,所有参数采用传值方式传递,由被调用函数负责清除栈中的参数。对于C函数,__stdcall的名称修饰方式是在函数名字前添加下划线,在函数名字后添加@和函数参数的大小,例如:_functionname@number
4.thiscall
thiscall只用在C++成员函数的调用,函数参数按照从右向左的顺序入栈,类实例的this指针通过ECX寄存器传递。需要注意的是thiscall不是C++的关键字,不能使用thiscall声明函数,它只能由编译器使用。
5.naked call
采用前面几种函数调用约定的函数,编译器会在必要的时候自动在函数开始添加保存ESI,EDI,EBX,EBP寄存器的代码,在退出函数时恢复这些寄存器的内容,使用naked call方式声明的函数不会添加这样的代码,这也就是为什么称其为naked的原因吧。naked call不是类型修饰符,故必须和_declspec共同使用。
VC的编译环境默认是使用__cdecl调用约定,也可以在编译环境的Project Setting...菜单-》C/C++ =》Code Generation项选择设置函数调用约定。也可以直接在函数声明前添加关键字__stdcall、__cdecl或__fastcall等单独确定函数的调用方式。在Windows系统上开发软件常用到WINAPI宏,它可以根据编译设置翻译成适当的函数调用约定,在WIN32中,它被定义为__stdcall。
作者:星轨(oRbIt
E_Mail:[email protected]
转载请注明原作者,否则请勿转载
使用C/C++语言开发软件的程序员经常碰到这样的问题:有时候是程序编译没有问题,但是链接的时候总是报告函数不存在(经典的LNK 2001错误),有时候是程序编译和链接都没有错误,但是只要调用库中的函数就会出现堆栈异常。这些现象通常是出现在C和C++的代码混合使用的情况下或在C++程序中使用第三方的库的情况下(不是用C++语言开发的),其实这都是函数调用约定(Calling Convention)和函数名修饰(Decorated Name)规则惹的祸。函数调用方式决定了函数参数入栈的顺序,是由调用者函数还是被调用函数负责清除栈中的参数等问题,而函数名修饰规则决定了编译器使用何种名字修饰方式来区分不同的函数,如果函数之间的调用约定不匹配或者名字修饰不匹配就会产生以上的问题。本文分别对C和C++这两种编程语言的函数调用约定和函数名修饰规则进行详细的解释,比较了它们的异同之处,并举例说明了以上问题出现的原因。
函数调用约定(Calling Convention)
函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持naked call方式。这么多函数调用约定常常令许多程序员很迷惑,到底它们是怎么回事,都是在什么情况下使用呢?下面就分别介绍这几种函数调用约定。
1.__cdecl
编译器的命令行参数是/Gd。__cdecl方式是C/C++编译器默认的函数调用约定,所有非C++成员函数和那些没有用__stdcall或__fastcall声明的函数都默认是__cdecl方式,它使用C函数调用方式,函数参数按照从右向左的顺序入栈,函数调用者负责清除栈中的参数,由于每次函数调用都要由编译器产生清除(还原)堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是__cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf和windows的API wsprintf就是__cdecl调用方式。对于C函数,__cdecl方式的名字修饰约定是在函数名称前添加一个下划线;对于C++函数,除非特别使用extern "C",C++函数使用不同的名字修饰方式。
2.__fastcall
编译器的命令行参数是/Gr。__fastcall函数调用约定在可能的情况下使用寄存器传递参数,通常是前两个 DWORD类型的参数或较小的参数使用ECX和EDX寄存器传递,其余参数按照从右向左的顺序入栈,被调用函数在返回之前负责清除栈中的参数。编译器使用两个@修饰函数名字,后跟十进制数表示的函数参数列表大小,例如:@function_name@number。需要注意的是__fastcall函数调用约定在不同的编译器上可能有不同的实现,比如16位的编译器和32位的编译器,另外,在使用内嵌汇编代码时,还要注意不能和编译器使用的寄存器有冲突。
3.__stdcall
编译器的命令行参数是/Gz,__stdcall是Pascal程序的缺省调用方式,大多数Windows的API也是__stdcall调用约定。__stdcall函数调用约定将函数参数从右向左入栈,除非使用指针或引用类型的参数,所有参数采用传值方式传递,由被调用函数负责清除栈中的参数。对于C函数,__stdcall的名称修饰方式是在函数名字前添加下划线,在函数名字后添加@和函数参数的大小,例如:_functionname@number
4.thiscall
thiscall只用在C++成员函数的调用,函数参数按照从右向左的顺序入栈,类实例的this指针通过ECX寄存器传递。需要注意的是thiscall不是C++的关键字,不能使用thiscall声明函数,它只能由编译器使用。
5.naked call
采用前面几种函数调用约定的函数,编译器会在必要的时候自动在函数开始添加保存ESI,EDI,EBX,EBP寄存器的代码,在退出函数时恢复这些寄存器的内容,使用naked call方式声明的函数不会添加这样的代码,这也就是为什么称其为naked的原因吧。naked call不是类型修饰符,故必须和_declspec共同使用。
VC的编译环境默认是使用__cdecl调用约定,也可以在编译环境的Project Setting...菜单-》C/C++ =》Code Generation项选择设置函数调用约定。也可以直接在函数声明前添加关键字__stdcall、__cdecl或__fastcall等单独确定函数的调用方式。在Windows系统上开发软件常用到WINAPI宏,它可以根据编译设置翻译成适当的函数调用约定,在WIN32中,它被定义为__stdcall。
#18
1。使用汇编方式调用,push压参,然后直接call,根据函数的调用方式,判断是自己平栈还是函数自己平栈
2。指针数据类型,32下都是4byte
2。指针数据类型,32下都是4byte
#19
mark
就结束。 1.如果只知道函数地址(这个可能是一个void*形式)和参数形式以及返回值类