【问题标题】:C++: Injecting 32 bit targets from 64 bit processC++:从 64 位进程注入 32 位目标
【发布时间】:2012-02-05 06:43:57
【问题描述】:

我最近用 C++ 写了一个 DLL-Injector,要求如下

  • 注入过程(我们称之为“注入器”)以及要注入的DLL(注入)存在于64 位和32 位变体中。根据目标,尝试注入匹配的注入版本。
  • 必须能够注入 32 位 (WOW64) 的目标进程,即使 Injector 以 64 位运行

我很快注意到,在 Injector 中调用 GetProcAddress("LoadLibraryA") 返回一个“不可用”句柄,因为 32 位目标加载了另一个 kernel32.dll 并且地址功能不同,因此注入失败(无法使用返回的地址/句柄启动远程线程)。此外,32 位进程将 kernel32.dll 加载到不同的基地址,这使得创建远程线程更加不可能。

为了明确我的意思,发生以下情况:

  • Injector 在 0x12340000
  • 加载了 64 位版本的 kernel32.dll
  • Injector 从此 kernel32.dll 检索 LoadLibraryA 0x00005678 的句柄
  • 目标在 0xABCD0000 加载了 32 位版本的 kernel32.dll
  • 此 kernel32.dll 的 LoadLibrary 句柄应为 0x0000EFAB
  • Injector 尝试使用函数 0x12345678 在目标中启动远程线程,但预期为 0xABCDEFAB

当从 64 位进程注入 64 位进程,从 32 位注入 32 位进程时,通常没有问题,因为 kernel32.dll (很可能)加载在相同的基地址和相同的函数地址可以被使用-到目前为止,这是我的理解。然而,在这种情况下,条件有所不同。

为了解决这个问题,我做了以下步骤:

  • 64 位 Injector 使用 EnumProcessModulesEx() 检索 32 位目标加载的 kernel32.dll 的地址(应为 0xABCD000)
  • 获取那个kernel32.dll的文件名,解析PE头并得到LoadLibraryA的RVA(应该是0x000EFAB)
  • 此时,我们知道 kernel32.dll 在 32 位目标中加载的位置以及来自该 DLL 的函数的地址。
  • 64 位 Injector 使用 ImageBase + Function RVA 在 32 位目标中启动远程线程,在本例中为神奇的 0xABCDEFAB

这种方法实际上效果很好,但我无法摆脱这样的想法,即这是总开销,必须有更简单的解决方案来从 64 位注入器注入 32 位目标。

我有两个问题,如果能在这里得到解答,我将非常感激:

  1. 有没有更简单的方法来实现这种注入?
  2. 我一直采取的方法是否存在我没​​有想到的问题?

非常感谢任何答案,谢谢!

编辑:天哪……我刚刚意识到,我在最初的帖子中描述的情况是错误的。 INJECTOR 是 64 位的,TARGET 是 32 位的(最初是相反的,但我已经更正了)。 Ben Voigt 下面的 cmets 完全正确,对 EnumProcessModulesEx 的调用将失败。对这种混乱感到非常抱歉:(

【问题讨论】:

  • 啊,这更有意义。

标签: c++ winapi dll 32bit-64bit code-injection


【解决方案1】:

我偶然发现这个线程正在寻找相同问题的解决方案。

到目前为止,我倾向于使用另一种更简单的解决方案。要获取 32 位内核 proc 地址,64 位进程只需执行一个 32 位程序即可为我们查找 proc 地址:

#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,否则您将很难是时候获取您正在寻找的地址了。
【解决方案2】:

我认为您可以使用调试符号 API 来节省自己解析 PE 标头和导出表的时间。这条路线应该产生 32 位注入器所需的信息; 64 位目标情况也是如此,尽管我仍然不明白您将如何将 64 位地址传递给 CreateRemoteThread

通常,这些调试符号函数需要 .pdb 或 .sym 文件才能运行,但我很确定它们也可以从 DLL 导出表中获取信息(只是从调试器显示的文件中我不显示的内容的经验来看) t 存在符号)。

【讨论】:

  • 感谢您的提示,我明天将尝试使用这些函数并发布结果。
  • 64 位地址:从x64-&gt;WOW64 没有问题,指针会被正确截断。但从WOW64-&gt;x64 到现在,我会依赖这样一个事实,即在大多数情况下,64 位函数的地址(包括加载的模块基址)不会在 0x80000000 或更高处加载。除非加载器(或程序员)决定这样做,否则一切都应该没问题(仅供参考,我机器上的 64 位 kernel32.dll 目前的默认映像库为 0x0000000078D20000)。但我可以看到这有点……容易出错。希望我对事物的看法是有意义的。
  • 使用SymFromNameImageRvaToVa 我能够得到与自己解析PE 条目时相同的结果——谢谢,这极大地简化了我的解决方案! :)
  • @PuerNoctis 我也有同样的问题,能否请你给我一个sn-p的代码?我需要知道如何使用 SymFromName 和 IMageRvaToVa 来获取 LoadLibraryA 的 RVA :) tnx
【解决方案3】:

此答案解决了该问题的早期版本,它与 64 位注入器的情况几乎无关。


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

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

EnumProcessModules 进一步解释了限制)

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

但您确实需要找到加载kernel32.dll 的基地址,因为ASLR

【讨论】:

  • 是的,它运行良好。 EnumProcessModules(根据文档,*Ex 变体在 WOW64 上的行为)对于该过程来说是完全足够的。我仍然在我的代码中使用它,因为如果 64 位版本的 Injector 正在运行,我会显式检索 32/64 模块句柄以更“干净”,但在 32 位版本中它实际上并没有受到伤害。我可能会得到所有句柄,但模块列表中只有一个 kernel32.dll 始终是正确的。
  • 嗯,你的编辑很有趣。我一定看过这段话,说只能检索 32 位进程的句柄,但由于某种原因,它在几台机器上都可以正常工作……很奇怪。至于 kernel32.dll 的基地址:根据我的理解(这可能是错误的,这就是我要问的原因) CreateRemoteThread 函数需要调用进程内存中函数的完整地址。仅 LoadLibrary 的 RVA 是不够的,因此需要有一个偏移量,在这种情况下,它是加载的 kernel32 的基地址。
  • @PuerNoctis:我也没有看到任何类型的CreateRemoteThread64 函数,那么您如何将LoadLibrary 的64 位地址作为线程过程传递?
  • 我将函数的基地址和 RVA 添加为 DWORD 并将其转换为 LPTHREAD_START_ROUTINE。最后,它只是一个数字、一个地址,并且可以保证(我在某处读过它,但我不再确切知道在哪里)WinAPI 提供的任何函数都没有大于 32 位的地址。所以我认为传递这样的地址是有效的。
  • @PuerNoctis:如果kernel32.dll 总是在地址空间的前 2G 内加载,那么这可能就是一切正常的原因。但它可能不适用于未来版本的 Windows。
猜你喜欢
  • 1970-01-01
  • 2017-03-15
  • 1970-01-01
  • 2012-04-04
  • 2014-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
相关资源
最近更新 更多