在 Hint 或名称表中嵌入 shellcode 的 PoC 工程 —— HintInject
项目地址:https://github.com/frkngksl/HintInjectHintInject 是我在使用 PE 文件中导入表结构时开发的 shellcode 嵌入器和加载器。原理是获取一个原始的 shellcode 文件并将 shellcode 分块放入 Hint/Name 表条目中,这些条目可通过加载程序可执行文件上的假导入 DLL 条目的导入查找表访问。然后加载器合并这些块以执行 shellcode。我不知道这是否是一种新颖的技术,但我想分享它的乐趣。
DLL 加载过程
在解释 HintInject 的工作原理之前,我想通过 Stack Overflow 中的一篇文章来简要描述下 DLL 加载过程。
要获取有关所需 DLL 的函数/导入信息,首先应查看导入表。导入表是一个 entry 表,每个导入的 DLL 都有一个 entry。 这些 entry 包含指向导入的 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;
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
这里的 Hint 字段实际上是 DLL 的导出名称指针表的索引。它用于加速查找该导入的位置。因此,可以用下图总结 Import Lookup Table 和 Hint/Name 表之间的关系。
总而言之,为了找到一个 DLL 及其导入,应该遵循以下步骤:
[*]从 Optional Header 的 DataDirectory 数组中转到 Import Directory Table
[*]通过检查其 Name 字段来遍历导入目录表条目以查找所需的 DLL
[*]找到正确的条目后,使用 OriginalFirstThunk 字段转到其导入查找表
[*]遍历导入查找表中的所有条目,这些条目是指向 Hint/Name 表条目的指针
[*]检查 Hint/Name 表条目的名称字段中的导入名称
HintInject 是如何工作的
最近在研究 x86matthew 共享的 ImportDLLInjection 技术时,我看到了如何在内存中加载的 DLL 的导入目录中添加一个虚假条目。之后,我想开发一个小项目,将虚假条目直接添加到磁盘上二进制文件的导入表中,既是为了好玩又是为了更新我的知识。在回顾开发此项目的 DLL 加载过程时,此过程中使用的 Hint/Name 表条目中的 Hint 字段引起了我的注意。
根据 MSDN 文档得知,Windows Loader 使用该字段直接从其所在的 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 块。新段的大概内存布局如下:
加载器二进制文件的示例导入:
使用
[*]使用 Release 选项构建项目
[*]使用原始格式的 shellcode 和输出路径执行 HintInject.exe 以创建 shellcode 加载器:HintInject.exe <Shellcode File> <Output Name>
[*]运行 loader 直接执行 shellcode,也可以给一个 PID 注入 shellcode:Loader.exe <PID>
[*]如果需要,可以通过修改 HintInjectLoader/Main.cpp 文件中的 InjectShellcodefunction 来更改 shellcode 注入方法
使用 Visual Studio 2019 在 Windows 10 19044 上测试:
限制
[*]关于执行 shellcode 的加载器上的虚假 DLL 条目,DLL 和导入的函数名都必须实际存在。否则,它会在启动加载程序可执行文件时给出 Missing DLL 错误。因此,我在项目中使用了一些 System32 下的合法 dll
[*]如果你的 shellcode 较大并且无法为你的 shellcode 创建加载器,可以将其他 dll 添加到 DllNamesForFakeImports.h 文件中的 dllNames 数组中
[*]对于每个创建的加载器,导入的函数都是随机选择的。但是,如果想更改用于创建虚假条目的 dll 的顺序,也应该编辑 DllNamesForFakeImports.h 文件中的 dllNames 数组。默认情况下,HintInject 会根据数组顺序从 dlss 中选择函数。也就是说,该工具首先从 user32.dll 中选择导出的函数,如果 shellcode 大小大于 2 * user32.dll 导出的数量,它也会开始使用 advapi32.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"};
页:
[1]