HintInject 是一款 Shellcode 嵌入与加载工具,是我在探索 PE 文件与导入目录表结构时开发的。它会接收原始 Shellcode 文件,将 Shellcode 分块放入 Hint/Name 表条目中,这些条目通过加载程序可执行文件上伪造的 DLL 导入项中的导入查找表(Import Lookup Table)可被访问。加载程序随后将这些分块合并起来执行 Shellcode。我不确定这算不算新颖的技术,但分享出来只是为了好玩。
在解释 HintInject 的工作原理之前,我想先参考 Stack Overflow 上的一篇文章简要说明一下 DLL 加载过程。
要获取所需 DLL 及函数/导入的信息,首先需要查看 导入目录表(Import Directory Table)。导入目录表是一系列条目,每个条目对应一个导入的 DLL。这些条目包含指向导入 DLL 名称的指针、指向 导入查找表(Import Lookup Table) 的指针、指向 导入地址表(Import Address Table) 的指针,以及其他信息的字段。
简而言之,导入查找表指向导入信息,导入地址表指向导入的地址。但是,当可执行文件位于磁盘上时,或者就在 DLL 加载过程开始之前,导入地址表与导入查找表的内容是相同的。导入地址表的内容会在 DLL 加载过程中被覆盖为导入的地址。
不深入细节的话,我们可以通过下面的示意图来看这三个表之间的关系。
再稍微深入一点,导入查找表并不直接保存导入的名称。对于按名称导入的函数,它保存的是 Hint/Name 表 条目的 RVA。这些条目将函数名称存储为以 NULL 结尾的 ASCII 字符串。因此,Hint/Name 表条目的结构定义如下:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
这里的 Hint 字段实际上是 DLL 导出名称指针表的索引,用于加速定位该导入的位置。因此,我们可以用下图总结导入查找表与 Hint/Name 表之间的关系。
总结一下,要查找某个 DLL 及其导入,需要遵循以下步骤:
最近,在研究 x86matthew 分享的 ImportDLLInjection 技术时,我看到了如何向内存中已加载的 DLL 的导入目录中添加一个伪造条目。之后,我为了乐趣并巩固知识,想开发一个小项目,直接在磁盘上的二进制文件的导入表中添加一个伪造条目。在复习 DLL 加载过程时,我注意到了该过程中使用的 Hint/Name 表条目中的 Hint 字段。
根据 MSDN 文档,我了解到该字段被 Windows 加载器用于直接从 DLL 的导出名称表中查找该导入(按名称导入)的地址。然而,同一文档中也提到:如果通过该字段无法找到函数,则会在 DLL 的导出名称表中通过二分查找操作进行搜索。基于这句话,我认为给这个字段设置一个不正确的值不会破坏 DLL 加载过程。
如上所述,每个要导入的函数都对应一个 Hint/Name 表条目。换句话说,每个导入有 2 个字节可供使用。通过组合多个导入,可以获得足够多的 Hint 字段来存储恶意 Shellcode。因此,可以利用一个加载程序二进制文件将 Shellcode 嵌入到伪造的导入 DLL 条目的 Hint/Name 条目中。该加载程序在运行时可以访问这些条目以便合并并执行 Shellcode。
HintInject 可用于创建这样一个加载程序,它将 Shellcode 保存在自己的 Hint/Name 表中。它首先创建一个名为 .rrdata 的新节,并将当前的导入目录复制到这个节中。然后,它追加一个新的伪造条目,该条目的导入将用于存放输入的 Shellcode。该节的剩余字节用于存储新伪造条目的导入查找表、导入地址表、DLL 名称以及 Hint/Name 表。最后一步,HintInject 使用导入的 Hint 字段来存放输入 Shellcode 的分块。
加载程序二进制文件示例的导入:
HintInject.exe 来创建 Shellcode 加载程序:HintInject.exe <Shellcode File> <Output Name>Loader.exe <PID>HintInjectLoader/Main.cpp 文件中的 InjectShellcode 函数。已在 Windows 10 19044 和 Visual Studio 2019 上测试:
DllNamesForFakeImports.h 文件中的 dllNames 数组中。DllNamesForFakeImports.h 文件中的 dllNames 数组。默认情况下,HintInject 会根据数组顺序从 DLL 中选择函数。也就是说,该工具首先从 user32.dll 中选择导出函数,如果 Shellcode 大小大于 2 * user32.dll 导出函数数量,它也会开始使用 advapi32.dll 及其他 DLL 的导出。static LPCSTR dllNames[] = {"user32.dll","advapi32.dll","gdi32.dll","wininet.dll","comctl32.dll","shell32.dll","wsock32.dll","oleaut32.dll","ws2_32.dll","urlmon.dll"};
仅限授权安全测试使用。未经明确许可,将此工具用于他人系统属于违法行为。