我意识到标题有点令人费解,所以让我解释一下我要做什么:
我刚刚完成了一个简单的 DLL 注入器,用于我正在尝试编写的概念证明。该程序拍摄当前进程的快照,枚举进程树,并将 DLL 注入其直接父进程。现在,在理想条件下,它可以正常工作:32 位版本的注入器可以注入到 32 位父进程中,而 64 位版本的注入器可以注入到 64 位父进程中。
不过,我现在想做的是从 x64 注入器将 32 位 DLL 注入 32 位父进程。注入该 DLL 后,我希望然后注入对注入的 DLL 导出的函数之一的调用。不过,我不确定这是否真的可行。(我已经整理了一些代码来识别父进程是 32 位进程还是 64 位进程,所以这不是问题)
现在,我已经找到了一些似乎通过将预编译的机器代码注入到进程中来完成第一部分的代码。(至少,我认为这就是它正在做的事情)通常,在注入对 LoadLibraryW 的调用之后,我会得到该调用返回的地址,将相对偏移量添加到我想要调用的导出函数,然后注入一个调用功能。但是在这种情况下,我无法将 32 位库加载到我的 64 位注入器中,因此我无法像往常一样使用GetProcAddress找到函数的相对偏移量。我通过执行以下操作解决了这个问题:
由于我无法使用常规方法找到 32 位 DLL 的函数偏移量,因此我目前正在将文件读入缓冲区,使用该缓冲区填充IMAGE_NT_HEADERS32结构,并枚举 IMAGE_EXPORT_DIRECTORY 以查找名称和相对偏移量所有导出的函数。
所以在这一点上,我有以下内容:
- 加载到 32 位进程中的 32 位 DLL
- 在 32 位进程中运行以下代码时等效于 funcAddr 的值:
代码:
HMODULE hInjectedDLL = LoadLibrary("mydll.dll");
DWORD funcAddr = (DWORD)GetProcAddress(hInjectedDLL, "ExportedFunc") - (DWORD)hInjectedDLL;
从理论上讲,我现在只需要 hInjectedDLL 的值,我应该能够调用该函数。不幸的是,尽管如此,我对汇编或机器代码的了解还不够,不知道如何获得该值。
有任何想法吗?
(另外,我知道我可以通过编译两个版本的注入器来为自己节省很多麻烦,并且当父进程的处理器架构不匹配时让一个运行另一个版本。我试图避免去不过这条路线。)
编辑:认为这可能有助于解释我在这个概念证明中实际想要完成的事情。
我正在尝试一个想法,我必须允许在当前控制台中执行子进程,而无需原始进程等待子进程完成。由于在控制台应用程序中没有用于执行此操作的内置 API,因此您通常会遇到一棵进程树,所有进程都在等待各自的子进程完成。为了促进此功能,我想执行以下操作:
注射
DLL 注入器将扮演“执行进程”的角色。(通常必须等到子进程完成的进程)运行时,它确定其父进程的平台,以及父进程是否甚至是基于控制台的应用程序。如果不是,该进程只需使用exec 系列函数来运行所需的子进程,然后立即退出。如果父进程是控制台应用程序,则注入器确定要使用哪个DLL,挂起最初创建注入器进程的线程,然后将DLL注入父进程。
解决我们的功能
一旦 DLL 就位,注入器将确定 DLL 导出的函数的地址。(通常,我会通过调用CreateRemoteThread来进行初始注入,然后在该线程上使用GetExitCodeThread来获取父进程中 DLL 的基地址。一旦有了它,就可以通过简单的算法找到我们导出的函数,然后我可以使用它来注入对该函数的第二次调用。
调用我们的函数
导出的函数将类似于以下内容:
BOOL RewriteHProcess(句柄 hProcess)
注入器将再次使用 CreateRemoteThread 从父进程的上下文中调用此函数,其中 hProcess 是注入器进程的句柄。在 DLL 方面,该函数将做两件事之一(我不太确定我的第一个想法是否可行,考虑到跨线程内存访问的安全限制,所以我把第二个想法放在一起,如果首先不成功。)
RewriteHProcess 将打开先前挂起的线程进行读写,并使用ReadProcessMemory,它将在进程的内存中搜索我们的注入器进程的 HANDLE。(我们假设父进程当前正在使用WaitForSingleObject阻止进一步执行功能。我知道命令提示符至少可以做到,这是我目前的重点)DLL 然后调用一个内部函数来创建我们想要的子进程,关闭旧句柄,并用我们的新句柄覆盖内存子进程。在这一点上,它会清理它可以清理的东西,然后返回。然后注入器将执行它需要的任何剩余清理,恢复挂起的线程,关闭进程和线程的句柄,然后退出,让父进程在等待新的子进程结束时继续阻塞。
如果这条路线不可行,我的后备方案是暂停注入器的阻塞线程,在注入的 DLL 中创建新的子进程,清理并退出注入器,然后在 DLL 中等待,直到子进程完成。此时,DLL 将清理、恢复挂起的线程并自行卸载。(不过,这条路线的缺点是父进程从注入器返回的返回码可能与我们的目标子进程的返回码不同)