C++:从64位进程中注入32位目标

6
我最近用C++编写了一个DLL注入器,其要求如下:
- 注入进程(以下简称“注入器”)和要注入的DLL(Injection)都有64位和32位版本。根据目标进程的情况,尝试注入匹配版本的注入器。 - 即使注入器以64位运行,也必须能够注入32位(WOW64)的目标进程。
很快我发现,在注入器中调用GetProcAddress("LoadLibraryA")会返回一个“无法使用”的句柄,因为32位目标进程加载了另一个kernel32.dll,而函数的地址也不同,因此注入失败(远程线程无法使用返回的地址/句柄启动)。此外,32位进程加载kernel32.dll的基址也不同,这使得创建远程线程更加困难。
具体来说,以下情况会发生:
- 注入器加载了kernel32.dll的64位版本,位于0x12340000 - 注入器从该kernel32.dll中检索出了LoadLibraryA句柄0x00005678 - 目标进程加载了kernel32.dll的32位版本,位于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) - 此时,我们知道了32位目标进程中kernel32.dll的加载位置和该DLL的函数地址。 - 64位注入器在32位目标进程中启动远程线程,使用ImageBase + Function RVA,此处是神奇的0xABCDEFAB。
这种方法实际上效果很好,但我无法摆脱它是完全开销的想法,并且必须有更简单的解决方案来从64位注入器中注入32位目标。
以下是我非常感谢您能在这里回答的两个问题:
1. 是否有更简单的方法来实现这种注入?
2. 我采取的方法是否存在可能尚未考虑的问题?
非常感谢任何答案!感谢!
编辑:哦天哪…我刚刚意识到,在我的初稿中,我描述了错误的情况。注入器为64位,目标为32位(最初是相反的,但我已经纠正了)。Ben Voigt下面的评论是完全正确的,调用EnumProcessModulesEx将失败。对于造成的混乱和困惑,我十分抱歉。

啊,这样就清楚多了。 - Ben Voigt
3个回答

6

我在寻找解决相同问题的方法时偶然发现了这个帖子。

目前,我倾向于使用另一种更简单的解决方案。为了获取32位内核进程地址,64位进程可以执行一个32位程序来帮我们查找进程地址:

#include <Windows.h>

int main(int argc, const char**)
{
    if(argc > 1)
        return (int) LoadLibraryA;
    else
        return (int) GetProcAddress;
}

这基本上是在用户模式下完成的唯一方法。实际上,在MacOS上也是一样的。无论如何,除非你从内核模块进行注入,否则你将不得不处理这个问题,因为32位进程将同时加载64位和32位的kernel32.dll,除非你能够访问PEB,否则你将很难获得你正在寻找的地址。 - E.T

0

这个答案针对的是早期版本的问题,对于64位注入器的情况基本上不相关。


你是说这种方法有效吗?因为根据文档,你无法从WOW64获取有关64位进程的信息:

如果32位应用程序在WOW64下调用此函数,则忽略dwFilterFlag选项,并且该函数提供与EnumProcessModules函数相同的结果。

(EnumProcessModules 进一步解释了限制)

如果从在WOW64上运行的32位应用程序中调用此函数,则仅可以枚举32位进程的模块。 如果进程是64位进程,则此函数将失败,最后一个错误代码为ERROR_PARTIAL_COPY(299)。

但你真的需要找到kernel32.dll加载的基地址,因为涉及ASLR


是的,它完美地工作。EnumProcessModules(根据文档,*Ex变体在WOW64上的行为)对于该过程来说已经足够完美了。我仍然在我的代码中使用它,因为如果注入器的64位版本正在运行,我会明确检索32/64模块句柄以使其更加“干净”,但在32位版本中实际上并没有伤害。我可能会获取所有句柄,但模块列表中只有一个kernel32.dll,这始终是正确的。 - PuerNoctis
@PuerNoctis:我也没有看到任何CreateRemoteThread64函数,那么你是如何将LoadLibrary的64位地址作为线程过程传递的呢? - Ben Voigt
@PuerNoctis:如果 kernel32.dll 总是在前 2G 的地址空间内加载,那么这很可能是为什么一切都正常工作的原因。但它可能在未来的 Windows 版本上无法工作。 - Ben Voigt
嗯,那么我猜当它不再工作时就要考虑这个问题了。有没有其他建议,如何(以及是否)可以通过其他方式从32位进程注入64位目标? - PuerNoctis
@E.T.:你所提到的部分回答是考虑了不同位数之间的注入。但问题后来发生了变化。 - Ben Voigt
显示剩余4条评论

0

我认为你可以使用调试符号API来避免解析PE头和导出表。这条路线应该能够产生32位注入器所需的信息;对于64位目标情况,也应该是如此,尽管我仍然不知道你如何将64位地址传递给CreateRemoteThread

通常,这些调试符号函数需要.pdb或.sym文件才能操作,但我非常确定它们也会从DLL导出表中获取信息(只是根据我在没有符号的文件上调试器显示的经验而言)。


谢谢你的提示,我明天会尝试使用这些函数并在之后发布结果。 - PuerNoctis
关于64位地址:从x64->WOW64没有问题,因为指针将被正确截断。但是从 WOW64->x64,目前我会依赖于大多数情况下64位函数的地址(包括已加载模块的基址)未加载到0x80000000或更高位置这一事实。除非加载器(或程序员)决定这样做,否则一切都应该没问题(FYI,我的机器上64位kernel32.dll在默认情况下的映像基址为0x0000000078D20000)。但我能看出来这有点...容易出错。希望我的观点讲得通。 - PuerNoctis
通过使用 SymFromNameImageRvaToVa,我能够获得与自己解析 PE 条目时相同的结果 - 谢谢,这极大地简化了我的解决方案! :) - PuerNoctis
1
@PuerNoctis 我也遇到了同样的问题,能否请您为我提供一段代码片段呢?我需要知道如何使用SymFromName和IMageRvaToVa来获取LoadLibraryA的RVA :) 谢谢 - Simone Margaritelli

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