C++:从 64 位进程注入 32 位目标
我最近用 C++ 编写了一个 DLL 注入器,其要求如下:
- 注入过程(我们称之为“注入器”)以及要注入的 DLL (注入)存在于 64 位和 32 位变体中。根据目标,尝试注入匹配的注入版本。
- 即使注入器运行在 64 位中,也必须能够注入 32 位(WOW64)的目标进程
我很快就注意到,注入器中调用 GetProcAddress("LoadLibraryA")返回一个“不可用”句柄,因为 32 位目标加载了另一个 kernel32.dll 并且该函数的地址不同,因此注入失败(无法使用返回的远程线程启动)地址/句柄)。此外,32 位进程将 kernel32.dll 加载到不同的基地址,这使得创建远程线程更加不可能。
为了明确我的意思,会发生以下情况:
- 注入器在 0x12340000 处加载了 64 位版本的 kernel32.dll
- 注入器从该 kernel32.dll 检索 LoadLibraryA 0x00005678 的
- 句柄 目标具有32 位版本的 kernel32.dll 加载于 0xABCD0000
- 的句柄此 kernel32.dll 的 LoadLibrary 预计为 0x0000EFAB
- 注入器尝试使用函数 0x12345678 启动目标中的远程线程,但预计为 0xABCDEFAB
当从 64 位进程注入 64 位进程时,从 32 位进程注入32 位,通常没有问题,因为 kernel32.dll(很可能)加载到相同的基地址并且可以使用相同的函数地址 - 这是我到目前为止的理解。然而,在这种情况下,条件有所不同。
为了解决这个问题,我执行了以下步骤:
- 64 位注入器使用 EnumProcessModulesEx() 检索 32 位目标加载的 kernel32.dll 的地址(应该是 0xABCD000)
- 获取该 kernel32.dll 的文件名,解析PE头,得到LoadLibraryA的RVA(应该是0x000EFAB)
- 此时一点,我们知道 kernel32.dll 在 32 位目标中加载的位置以及该 DLL 中函数的地址。
- 64 位注入器使用 ImageBase + Function RVA 在 32 位目标中启动远程线程,在本例中是神奇的 0xABCDEFAB
这种方法实际上效果很好,但我无法摆脱这就是全部的想法开销,并且必须有一个更简单的解决方案来从 64 位注入器注入 32 位目标。
我有两个问题,如果能在这里得到解答,我将非常感激:
- 是否有更简单的方法来实现这种注入?
- 我所采取的方法是否存在我没有考虑过的问题?
非常感谢任何答案,谢谢!
编辑:天哪...我刚刚意识到,我在最初的帖子中描述的情况是错误的。 INJECTOR 是 64 位,TARGET 是 32 位(最初是相反的,但我已经更正了)。 Ben Voigt 下面的评论完全正确,对 EnumProcessModulesEx 的调用将会失败。对于这种混乱,非常抱歉:(
I have written a DLL-Injector in C++ recently, for which the requirements were the following
- The INJECTING PROCESS (let's call it the 'Injector') as well as the DLL TO BE INJECTED (Injection) exist in 64 and 32 bit variants. Depending on the target, the matching version of the injection is tried to be injected.
- It must be possible to inject target processes that are 32 bit (WOW64) even with the Injector running in 64 bit
It came quickly to my notice, that the call of GetProcAddress("LoadLibraryA") in the Injector returns an "unusable" handle as the 32 bit target has another kernel32.dll loaded and the address of the function there is different, so injection fails (The remote thread can not be started using the returned address/handle). Furthermore, the 32 bit process has the kernel32.dll loaded at a different base address, which makes creation of the remote thread even more impossible.
To make clear what I mean, the following happens:
- Injector has 64 bit version of kernel32.dll loaded at 0x12340000
- Injector retrieves handle for LoadLibraryA 0x00005678 from this kernel32.dll
- Target has 32 bit version of kernel32.dll loaded at 0xABCD0000
- The handle for LoadLibrary of this kernel32.dll is expected to be 0x0000EFAB
- Injector tries to start remote thread in target with function 0x12345678, but 0xABCDEFAB is expected
When injecting a 64 bit process from a 64 bit process, and 32 bit from 32 bit, there is usually no problem, as the kernel32.dll is (most likely) loaded at the same base address and the same function address can be used - that's my unterstanding so far. In this case however the conditions differ.
To overcome the problem I did the following steps:
- 64 bit Injector retrieves address of kernel32.dll loaded by 32 bit target using EnumProcessModulesEx() (should be 0xABCD000)
- Get filename of that kernel32.dll, parse the PE header and get the RVA of LoadLibraryA (should be 0x000EFAB)
- At this point, we know where kernel32.dll is loaded in the 32 bit target and the address of the function from this DLL.
- 64 bit Injector starts remote thread in 32 bit target with ImageBase + Function RVA, in this case the magical 0xABCDEFAB
This approach actually works very well, but I can't get rid of the thought that this is total overhead and there must be a more simpler solution to inject 32 bit targets from 64 bit injectors.
I have two questions, for which I am very grateful if they could be answered here:
- Is there a more simple way to achieve this kind of injection?
- Are there possible problems with the approach I've been taking that I haven't thought about?
Any answers are very much appreciated, thanks!
EDIT: Oh my gosh... I just realized, that I described the situation wrong in my initial post. The INJECTOR is 64 bit, and the TARGET is 32 bit (Initially it was the other way around, but I already corrected it). Ben Voigt's comments down below are totally true, the call to EnumProcessModulesEx would fail. A big big BIG sorry for that confusion :(
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我偶然发现这个线程正在寻找同一问题的解决方案。
到目前为止,我倾向于使用另一种更简单的解决方案。要获得 32 位内核 proc 地址,64 位进程只需执行一个 32 位程序即可为我们查找 proc 地址:
I stumbled upon this thread looking for a solution for the same problem.
So far I'm inclined to use another simpler solution. To obtain a 32-bit kernel proc address, the 64-bit process can just execute a 32-bit program that will look up the proc addresses for us:
我认为您可以使用调试符号 API 来节省自己解析 PE 标头和导出表的时间。该路由应该产生 32 位注入器所需的信息; 64 位目标情况也是如此,尽管我仍然不知道如何将 64 位地址传递给
CreateRemoteThread
。通常这些调试符号函数需要 .pdb 或 .sym文件来操作,但是我很确定他们也从 DLL 导出表中获取信息(只是根据调试器显示的没有符号的文件的经验)。
I think you could use the debug symbols API to save yourself parsing the PE header and export table. This route should yield the required information for the 32-bit injector; 64-bit target case as well, although I still don't see how you're going to pass a 64-bit address to
CreateRemoteThread
.Normally these debug symbol functions require a .pdb or .sym file to operate, however I'm pretty sure they also get information from a DLL export table (just going from experience of what a debugger shows for files where I don't have symbols present).
这个答案解决了问题的早期版本,它与 64 位注入器的情况基本上无关。
你说这个方法有效吗?因为根据文档,您无法获得WOW64 中有关 64 位进程的信息:
(
EnumProcessModules
进一步解释了该限制)但您确实需要找到加载
kernel32.dll
的基址,因为This answer addresses an earlier version of the question, it is mostly irrelevant to the case of a 64-bit injector.
Are you saying that approach works? Because according to the documentation, you can't get information about 64-bit processes from WOW64:
(
EnumProcessModules
explains the restriction further)But you really do need to find the base address where
kernel32.dll
loaded, because of ASLR.