在两个进程之间共享内存(C语言,Windows系统)。

34

由于我在此前问过的问题没有找到答案,所以我正在尝试一种不同的方法。

有没有办法在两个进程之间共享内存?

第二个进程从注入中获取信息,因为它是一个不再受支持的遗留程序。

我的想法是在那里注入一些代码,在传递给注入程序的结构体中传递地址(或其他内容)到共享内存,其中包含我需要运行的数据。 一旦我获得数据,就会在注入线程内部填充自己的变量。

这可能吗? 怎么做?

很感谢您提供代码。

编辑:

我认为这并不清楚,所以我将澄清。 我知道如何注入。 我已经这样做了。 这里的问题是传递动态数据到注入中。


什么类型的程序?Windows、GUI 还是控制台? - Chris K
我可以从服务、GUI或控制台中运行。 - wonderer
POCO项目中有一个很好用的C++内存映射文件包装器。http://pocoproject.org/download/index.html 我在反复痛苦地尝试使用Boost库之后找到了它,其他人可能觉得Boost很容易使用,但我发现它难以正确使用。 - Warren P
7个回答

30
尽管Windows支持通过其文件映射API共享内存,但您不能直接轻松地将共享内存映射注入到另一个进程中,因为MapViewOfFileEx不接受进程参数。
但是,您可以通过在另一个进程中使用VirtualAllocExWriteProcessMemory分配内存来注入一些数据。如果您复制一个句柄使用DuplicateHandle,然后注入一个调用MapViewOfFileEx的存根,您可以在另一个进程中建立一个共享内存映射。由于听起来您无论如何都要注入代码,所以这对您应该很有效。
总之,您需要:
  • 通过使用INVALID_HANDLE_VALUE作为hFile和NULL作为lpName调用CreateFileMapping来创建一个匿名共享内存段句柄。
  • 使用DuplicateHandle将此句柄复制到目标进程中。
  • 使用VirtualAllocEx分配一些内存用于代码,flAllocationType = MEM_COMMIT | MEM_RESERVE,flProtect = PAGE_EXECUTE_READWRITE。
  • 使用WriteProcessMemory将您的存根代码写入此内存中。这个存根可能需要用汇编语言编写。通过在此处写入它,将从DuplicateHandle传递HANDLE。
  • 使用CreateRemoteThread执行您的存根。然后,该存根必须使用它获取的HANDLE调用MapViewOfFileEx。进程将拥有一个共同的共享内存段。
你可能会发现,如果你的存根加载外部库会更容易一些——也就是说,让它简单地调用LoadLibrary(找到LoadLibrary的地址由读者自行练习),并从库的dllmain入口点开始工作。在这种情况下,使用命名共享内存比瞎搞DuplicateHandle要简单得多。有关CreateFileMapping的MSDN文章有更多详细信息,但基本上,将INVALID_HANDLE_VALUE用于hFile,将名称用于lpName即可。

编辑:既然你的问题是传递数据而不是实际的代码注入,那么有几个选项。

  1. 使用可变大小的共享内存。您的存根获取共享内存的大小和名称或句柄。如果您只需要交换数据一次,则此选项适合。请注意,共享内存段的大小在创建后不能轻松更改。
  2. 使用命名管道。您的存根获取管道的名称或句柄。然后,您可以使用适当的协议来交换可变大小的块——例如,写入长度的size_t,然后是实际消息。或使用PIPE_TYPE_MESSAGE和PIPE_READMODE_MESSAGE,并注意ERROR_MORE_DATA以确定消息的结束位置。如果需要多次交换数据,则此选项适合。

编辑2:以下是如何为存根实现句柄或指针存储的草图:

.db B8            ;; mov eax, imm32
.dl handle_value  ;; fill this in (located at the start of the image + one byte)
;; handle value is now in eax, do with it as you will
;; more code follows...

你也可以使用一个固定的名称,这可能更简单。


作为一个不熟悉Windows的人,对于这种可能性我应该有些担心吗?是否有类似Unix的进程可以实现这个功能? - Newton Falls
是的,这可以在Unix中完成。使用PTRACE_POKEUSER来调整用于匿名mmap调用的寄存器。一旦调用完成,使用PTRACE_POKEDATA或映射/proc/pid/mem来编写代码。然后让代码做任何事情。 - bdonlan
澄清一下,上述内容是针对Linux的。其他的Unix系统可能会有类似的东西。如果你想要自己的线程,请从你注入的存根中调用libpthread。 - bdonlan
已经完成翻译。我只是在传递句柄到远程线程方面遇到了问题。 - wonderer
使用固定名称代替传递句柄。 - bdonlan
显示剩余3条评论

16

如果您不想一路走到原始的Win32调用,我建议使用POCO项目的预构建测试包装类。http://pocoproject.org/download/index.html 比Boost更轻量级和易读。 - Warren P
1
你能否创建一个内存映射文件,但是该文件从不接触文件系统,而是仅基于内存的文件吗? - Brandon Ros

5

1
共享内存就是内存映射文件。你想要做什么? - bdonlan
抱歉,我是指管道。让我删除那条评论。 - wonderer

1
你尝试过使用管道(用于内存)或者序列化(用于对象)吗?你可以使用文件来管理进程之间的内存。套接字也是在进程之间进行通信的好方法。

1

内存映射是一种好方法,您甚至不需要创建永久的内存空间,当共享它的所有进程关闭时,内存扇区就会超出范围。还有其他方法。从一个C应用程序快速传递数据到另一个应用程序的简单粗暴的方法就是使用操作系统。在命令行中键入app1 | app2。这将导致app2成为app1的输出目标,或者来自app1的printf命令将其发送到app2(这称为管道)。


0

如果你在谈论Windows,主要的障碍是每个进程都生活在自己的虚拟地址空间中。不幸的是,你不能传递普通的内存地址从一个进程到另一个进程,并得到你期望的结果。(然而,线程都生活在同一个地址空间中,这就是为什么线程可以以相同的方式看到内存。)

然而,Windows确实有一个共享内存空间,你必须非常小心地管理它。任何分配共享内存空间的进程都负责显式释放该内存。这与本地内存形成对比,后者在进程死亡时基本上会消失。

请参见此MSDN示例文章,了解如何使用共享内存空间接管世界。嗯,或者与旧软件进行接口。无论你最终做什么,祝你好运!


感谢您的评论。它们更清楚地阐明了情况。不过,那篇文章并没有真正说明要使用哪种API或方法论。 - wonderer
3
在win32中,全局堆是进程本地的。在任何基于NT内核(比如XP及其后续版本)的操作系统上,将它用作共享内存是行不通的。 - bdonlan
1
啊!现在我明白了我的困惑。我完全错误地说了“堆”。我一直在想共享内存空间。那不是堆。:) 谢谢 bdonlan! - Mike
我已经相应地编辑了答案(用共享内存空间替换堆)。 - Assad Ebrahim
@AKE:不,这个答案讨论的是Win16提供的“全局堆”,在Win32中已经不再适用。 - Ben Voigt
显示剩余2条评论

0

您可以尝试使用Boost.Interprocess在两个进程之间进行通信。但是,要将代码注入到先前存在但不受支持的软件中,您可能需要使用@bdonlan的方式,即使用WriteProcessMemory


谢谢。你能详细说明一下吗?我知道如何注入,这不是问题。 - wonderer
我正在使用WriteProcessMemory。我只需要传递有时会更改大小的信息。 - wonderer

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