4

我意识到标题有点令人费解,所以让我解释一下我要做什么:

我刚刚完成了一个简单的 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 方面,该函数将做两件事之一(我不太确定我的第一个想法是否可行,考虑到跨线程内存访问的安全限制,所以我把第二个想法放在一起,如果首先不成功。)

  1. RewriteHProcess 将打开先前挂起的线程进行读写,并使用ReadProcessMemory,它将在进程的内存中搜索我们的注入器进程的 HANDLE。(我们假设父进程当前正在使用WaitForSingleObject阻止进一步执行功能。我知道命令提示符至少可以做到,这是我目前的重点)DLL 然后调用一个内部函数来创建我们想要的子进程,关闭旧句柄,并用我们的新句柄覆盖内存子进程。在这一点上,它会清理它可以清理的东西,然后返回。然后注入器将执行它需要的任何剩余清理,恢复挂起的线程,关闭进程和线程的句柄,然后退出,让父进程在等待新的子进程结束时继续阻塞。

  2. 如果这条路线不可行,我的后备方案是暂停注入器的阻塞线程,在注入的 DLL 中创建新的子进程,清理并退出注入器,然后在 DLL 中等待,直到子进程完成。此时,DLL 将清理、恢复挂起的线程并自行卸载。(不过,这条路线的缺点是父进程从注入器返回的返回码可能与我们的目标子进程的返回码不同)

4

2 回答 2

3

用于VirtualAllocEx()在目标进程内分配一块可执行内存,然后根据WriteProcessMemory()需要将 x86 或 x64 机器指令写入该内存块。让这些指令根据需要调用导出LoadLibrary()GetProcAddress()DLL 函数。然后用于CreateRemoteThread()执行内存块。如果您的注入器在单独的进程中运行,则无法直接调用导出的 DLL 函数。导出的函数必须在目标进程的上下文中加载和调用。并且不要从 的返回值中减去 的返回LoadLibrary()GetProcAddress()GetProcAddress()返回指向函数的直接内存指针,因此可以直接调用它。

更新:这种方法的一个变体是将所有注入的代码放在 DLL 的入口点内(或让入口点产生一个线程来运行代码),当它被调用时,DLL_ATTACH_PROCESS原因是。因此无需从 DLL 中导出任何函数。然后就可以使用VirtualAllocEx()andWriteProcessMemory()把DLL的路径存入目标进程,然后直接使用CreateRemoteThread()调用LoadLibrary()。内核函数在进程之间始终具有相同的内存地址,因此您的注入进程可以GetProcAddress()在自己的地址空间内调用以获取地址,LoadLibrary()然后将该指针传递给lpStartAddress参数CreateRemoteThread()。这样,您就不必担心编写任何 x86/x64 汇编代码。

此技术在本文的第 3 节中有更详细的描述:

将代码注入另一个进程的三种方法

于 2012-11-09T00:41:21.053 回答
1

如果有人发现这个问题并且在阅读评论后仍然感到困惑,你可以在这里找到一个(公认丑陋的)概念证明。假设您已经知道 DLL 注入的常规策略,这里解释了您需要做的不同的事情。(带有相关代码):


定位我们的出口( injdll32_64.c )

通常我们可以选择这LoadLibrary/GetProcAddress条路线,但由于这两个模块针对不同的处理器,我们需要以不同的方式做事。正如Remy Lebeau 在他的回答中建议的那样,我们可以完全在装配方面做到这一点。然而,从我的角度来看,编写程序集来定位 的基地址kernel32.dll,找到导出表,然后定位LoadLibraryGetProcAddress这似乎是一件令人头疼的事情。相反,我在 C 端通过读取每个 DLL、搜索IMAGE_EXPORT_DIRECTORY必要的导出并存储它们的 RVA 以供以后使用来处理它。


通过 RVA 调用我们的导出injdll32.cx86.final.asm

出于我的目的,我需要让注入的 DLL 在目标应用程序的主线程中执行。为此,我暂停了目标应用程序的主线程,为我的预组装机器代码分配内存,使用适当的导出 RVA 填充占位符,通过调用更改目标应用程序的 EIP GetThreadContext/SetThreadContext,并恢复暂停的线程. (不一定按这个顺序)不幸的是,我从来没有写过一种机制来释放我们在目标应用程序中虚拟分配的内存,但这样做的解决方案之一是实现一种机制,用于在目标进程返回其原始进程时通知注入器电子工业园。那时,注入器可以安全地 VirtualFree 分配的内存。(或者,你总能找到一个合适的代码洞,甚至消除释放内存的担忧)

于 2013-09-22T02:51:34.080 回答