我正在阅读有关转储进程内存的进程部分的讨论,有人建议使用 DLL 注入来执行此操作。老实说,我真的不明白。DLL 注入是如何工作的,你可以用它做什么类型的逆向任务?
什么是 DLL 注入以及它如何用于逆向?
DCoder 的回答是一个很好的。为了稍微扩展一下,我最常在强制现有进程通过 CreateRemoteThread 加载 DLL 的上下文中使用 DLL 注入。从那里,DLL 的入口点将在加载后由操作系统执行。在入口点中,我将调用一个例程,该例程对原始二进制文件中我感兴趣的所有位置执行内存中修补,并通过各种修改将它们的执行重定向到我的 DLL 中。如果我有兴趣修改或观察进程与某些导入函数的交互,那么我将覆盖该函数的 IAT(导入地址表)条目,并将其替换为指向我控制的内容的指针。如果我想对二进制文件中存在的某个函数做同样的事情,我会在函数的开头做一些绕道式的补丁。我什至可以在任意位置做非常手术和有针对性的钩子,类似于老式的字节修补。我的 DLL 在各个钩子内完成其业务,然后被编程为将控制重定向回原始进程。
DLL 注入为操纵正在运行的进程的执行提供了一个平台。它非常常用于在逆向工程时记录信息。例如,您可以为给定的导入操作系统库函数挂钩 IAT 条目,然后将函数参数记录到磁盘上。这为您提供了一个数据源,可以帮助您快速对目标进行逆向工程。
不过,DLL 注入不仅限于日志记录。鉴于您可以自由地在进程的地址空间内执行任何您想要的代码,您可以按照您选择的任何方式修改程序。这种技术在游戏黑客世界中经常用于对机器人进行编码。
你可以用字节补丁做的任何事情,你都可以用 DLL 注入来做。除了 DLL 注入可能会更容易和更快,因为您可以用 C 而不是汇编语言编写补丁,并且不必费力对二进制文件及其 PE 结构进行手动修改,查找代码漏洞等。 DLL 注入几乎在修改二进制文件时完全不需要使用汇编语言;唯一需要的汇编语言将是靠近特定钩子入口和出口的小段代码,以保存和恢复寄存器/标志的值。它还使二进制修改变得快速而简单,并且不会改变您正在修补的可执行文件的加密签名。(关于加密签名的评论适用于磁盘上的可执行文件,而不是内存中的;当然,
DLL 注入可用于解决非常重要的逆向工程问题。由于保密协议,以下示例在某些方面必然含糊不清。
我对一个更新非常频繁(有时每天多次)的程序产生了反复的兴趣。该程序中有许多部分在编译后在磁盘上加密,并且必须在运行时解密。该软件包括一个执行运行时加密/解密的内核模块。为了请求对给定部分进行加密或解密,该程序附带了一个 DLL,该 DLL 导出了一个函数,该函数将部分的编号和一个指示该部分是否应该加密或解密的布尔值作为参数。所有组件都经过数字签名。
我采用了一个基于 DLL 注入的解决方案,其工作方式如下:
- 创建进程挂起。
- 注入 DLL。
- DLL 在程序的 IAT 中挂钩 GetProcAddress。
- GetProcAddress 钩子等待提供特定的字符串,然后返回该函数自己的钩子版本。
- 钩子函数检查堆栈上两帧的返回地址,以确定调用它的函数的起始地址(称为 Func)。
- 挂钩函数然后为每个加密部分调用 Func,指示它解密每个部分。为了使这项工作起作用,挂钩函数必须将调用传递给 DLL 中的正确函数以进行这些调用。
- 这样做之后,对于钩子函数的每次后续调用,它只是简单地返回 1,就好像调用成功一样。
- 解密所有部分后,DLL 现在将进程的映像转储到磁盘上并重建导入信息。
- 之后,它会做一些其他的事情来抵消其他保护。
最初,我是为每个新构建手动完成所有这些工作。那太乏味了。一个我编写了 DLL 注入版本的代码,我再也不需要承担大量的手动工作。
DLL 注入并不广为人知,也没有在游戏黑客之外的逆向工程中使用。这是非常不幸的,因为它是一种极其强大、灵活且简单的技术,应该成为每个人的必修课的一部分。我已经使用它几十次了,它似乎在我所有的动态项目中都找到了一个角色。当我的任务变得太麻烦而无法使用调试器脚本时,我切换到 DLL 注入。
在逆向工程技术的范围内,动态二进制检测 (DBI) 工具也提供了 DLL 注入的每一项功能,而且 DBI 仍然更强大。但是,DBI并不隐蔽,并且会在内存消耗和可能的性能方面产生严重的开销。在切换到 DBI 之前,我总是尝试使用 DLL 注入。
DLL 注入的工作原理是欺骗/强制目标进程加载您选择的 DLL。之后,该 DLL 中的代码将作为目标进程的一部分执行,并且能够执行进程本身可以执行的任何操作。有趣的部分是弄清楚如何让目标进程调用您的代码。
DLL 可以通过以下方式注入:
- 只需将您的 DLL 替换为进程通常使用的 DLL - 例如,如果您将 DLL 命名为 DLL
ddraw.dll
,许多游戏会很乐意加载它,而不是真正的 Direct Draw DLL。我已经看到这样做是为了强制游戏仅在软件仿真模式下使用 Direct Draw,以在特定 GPU 上加速它。 - 欺骗加载器从不同的文件夹加载已知的 DLL - 请参阅旧的新事物。
- 用加载 DLL 的指令替换某些进程代码。
- 使用很多其他方式。
下一步是让您的 DLL 代码实际执行。但是如果你想做一些有意义的事情,这将很难——你需要知道这个过程做什么,它使用什么数据结构等,所以你很可能需要反汇编它。
- 您可以在目标进程中创建一个新线程来调用 DLL 中的函数。首先挂起现有线程以保持理智并避免时髦的多线程错误。
- 如果您用自己的 DLL 替换了已知的 DLL,该过程将期望您的 DLL 响应特定的函数调用 - 您最好知道这些函数是什么,并在 DLL 中提供它们的替换。
- 如果您更改可执行文件以调用已知 DLL 之外的 DLL,则您必须已经将可执行文件拆开。现在去寻找一些感兴趣的地方,并在那里插入对 DLL 函数的调用。见代码洞。
我通过将目标进程作为被调试进程启动来执行 DLL 注入,使用调用 的自定义代码序列覆盖其启动代码中的一些字节LoadLibrary("mydll.dll"); GetProcAddress(myLib, "myFunc");
,并重写可执行文件中的一些代码以跳转到 DLL 中的函数。
使用这种方法,我和一些朋友为命令与征服:红色警戒 2编写了一个相当大的非官方错误修复/增强 DLL - 现在该 DLL 的大小约为原始游戏可执行文件的 15%。结果,后来的游戏官方更新仅限于他们的员工可以在不重新编译二进制文件的情况下做的事情,这对 EA 来说是一反常态的好。