在C++中如何调用另一个地址空间的函数

5
我知道这可能会引起线程问题等安全问题,但我需要知道如何调用给定调用约定的远程地址空间中的函数,并传递参数——最好能恢复远程函数返回的数据,但实际上并不一定需要。
如果我可以在编译时从远程函数的函数原型中获取具体信息,我将能够使此方法工作。我需要知道参数的大小以及参数是否被明确声明为指针,例如(void*、char*、int*等)。
换句话说,如果我定义一个函数原型,类似于:
typedef void (__cdecl *testFunc_t)(int* pData);

我需要在编译时获取参数的大小,并确定哪些是指针。这里我们假设远程函数是一个stdcall或_cdecl调用。
我使用的IDE是Microsoft Visual Studio 2007,如果解决方案针对特定产品,请告知。
我的计划如下:
1.创建一个线程,在要调用的函数的源头使用CreateRemoteThread,但我会将其置于挂起状态。 2.设置堆栈,使返回地址为进程内分配的代码存根,该代码将调用ExitThread(eax),这将以函数的返回值退出线程。然后我将通过使用GetExitCodeThread来恢复此值。 3.我还将把函数调用的参数从我的本地堆栈复制到新创建的线程的堆栈中 - 这就是我需要了解函数参数是否为指针以及参数的大小的原因。 4.恢复线程并等待它退出,然后我将返回带有线程退出代码的调用者。
我知道这应该可以在编译时完成,但编译器是否有我可以使用的方法我不确定。我也知道,所有这些数据都可以很容易地从编译代码后创建的PDB文件中恢复,如果编译器执行优化,则参数的大小可能会发生变化。我无需被告知这有多危险,因为我完全意识到这一点,但这不是商业产品,而是我必须为学校完成的一个小项目。
问题是:如果我有这样的函数原型 typedef void (__cdecl testFunc_t)(int pData);
是否有办法在编译时获取此原型参数的大小(例如,在上面的示例中,参数总大小为sizeof(int*))?如果我有以下函数:
template<typename T> unsigned long getPrototypeArgLength<T>()
{ 
   //would return size of arguments described in the prototype T 
} 

//when called as

getPrototypeArgLength<testFunc>()

我不是很确定你的问题是什么。你已经描述了你想要做什么......那么对你有用吗? - Greg Hewgill
抱歉Greg,我应该更清楚地表达。如果我有一个函数原型,例如 typedef void (__cdecl testFunc_t)(int pData);是否有任何方法可以在编译时获取此原型参数的大小(即在上面的示例中,参数将总和为sizeof(int*))?例如,如果我有一个函数: template<typename T> unsigned long getPrototypeArgLength<T>() { //would return size of arguments described in the prototype T }如果我在这里使用了错误的术语,请原谅。谢谢 :) - Jeremy
具体来说,我需要在第三步中寻求帮助。 - Jeremy
我认为你只能查找函数调用约定的详细信息了。(在32位代码中,它们相当简单,幸运的是。)没有可移植的方法来获取这些信息,因为它不具备可移植性。你可以编写一个带有正确签名的虚拟函数,关闭优化后编译调用它,并查看生成的汇编代码的确切内容。 - Alan Stokes
谢谢Alan - 我已经知道stdcall和cdecl在32位系统上的工作原理(幸运的是它们并不太难理解)。问题在于尝试在编译时确定给定函数原型的参数大小。请看我的编辑,我澄清了我需要帮助的内容,它展示了我想用模板函数实现的内容。感谢您的帮助。谢谢 :) - Jeremy
2个回答

1

这看起来像是一个相当的学校项目...

  1. 对于步骤3,您可以使用ReadProcessMemory / WriteProcessMemory(其中之一)。例如,新线程在创建时可以接收参数的地址(在调用进程上)的地址(开始和结束)。然后,它可以从该区域读取调用进程存储器并将其复制到自己的堆栈。

  2. 您考虑过使用COM来完成整个事情吗?如果您使用专为此设计的机制,则可能可以更轻松地完成任务。


好的,远程进程并没有设计成可以调用其函数的方式 - 但是通过研究,我们发现可以在不中断进程的情况下调用它们。 - Jeremy
我猜你在谈论我提出的第二点。我的意思是让你创建一个线程,启动COM并为该函数公开一个包装器。这将使您能够轻松地扩展到将来可能需要导出的其他函数。 - OSH
啊,我明白了。但是,在构建COM包装器之前,我不需要知道函数参数的大小和类型等信息吗?上面的问题归结为如何从类似于typedef void (__cdecl testFunc_t)(int pData)这样定义的原型函数在编译时获取此信息 - 我不知道您是否看到了我的编辑,我刚刚将其添加到我的问题中。我不想将代码放入注释中,因为它的格式不好。 - Jeremy
我知道你可以用汇编语言来实现(如果我没记错的话,使用BP和SP)。我建议你发另一个问题,具体询问“如何在编译时获取方法的参数数量/大小/?”之类的内容。 - OSH
好的,谢谢Oren。我会让这个程序运行一天,看到哪个步骤之后再创建另一个。如果调用者使用 std\cdecl 调用约定,则可以利用 ESP 和 EBP 之间的差异来获取调用者堆栈帧的大小,但问题是堆栈帧还包括为其他函数分配的空间,例如局部变量和可能的参数。尽管如此,我认为复制我不需要的一些额外数据也不会有任何影响。我会研究一下的。谢谢 :) - Jeremy

1

好的,我发现我可以使用BOOST库在编译时获取大量类型信息。具体来说,我正在使用boost::function_traits,但是,如果您查看boost库,您会发现您可以恢复相当多的信息。这是我编写的一些代码,演示如何获取函数原型的参数数量。

(实际上,我还没有测试下面的代码,它只是我从另一个我制作和测试过的函数中组合而成的东西。)

template<typename T>
unsigned long getArgCount()
{
    return boost::function_traits<boost::remove_pointer<T>::type>::arity;
}

void (*pFunc)(int, int);

2 = getArgCount<BOOST_TYPEOF(pFunc)>();

实际上,我发现检查T是否为函数指针并不是必要的,因为如果它不是函数指针,它就无法编译。 - Jeremy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接