Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
ExportHider — ExportHider:在运行时生成导出表,以隐藏 DLL 文件中的导出函数。 | Kitploit
工具/GitHubGitHub/frkngksl/exporthider
动态代码分析 (DAST)漏洞利用逆向工程恶意软件分析二进制分析红队Payload 开发
GitHubfrkngksl/exporthider

ExportHider

ExportHider:在运行时生成导出表,以隐藏 DLL 文件中的导出函数。

查看仓库
3324个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

ExportHider

ExportHider 生成一个 C++ DLL 模板,其中包含一个代码存根,允许你在文件系统上隐藏 DLL 导出目录中的导出函数。在编写函数定义并编译文件后,你将无法通过 CFF Explorer 等 PE 文件查看器看到隐藏的导出函数。然而,由于模板中的代码存根在运行时重新创建导出目录,合法的 GetProcAddress 调用仍能成功执行。此方法仅适用于动态 DLL 加载或自定义 DLL 加载器场景。

工作原理?

通常,当你希望在 DLL 文件(C 或 C++)中定义导出函数时,只需在函数名称前加上 __declspec(dllexport) 关键字,或创建一个 .def 文件。编译后,编译器会创建一个称为导出目录的特定表,用于保存与导出函数相关的信息。导出目录的结构如下所示:

当一个进程想要使用 DLL 文件中的函数时,Windows 加载器会解析该结构,并通过 AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals 数组导入所请求的函数。

有关导入函数的具体例程,ferreirasc 的博客文章 中有详细说明,但简而言之,对于按名称导入的函数,加载器会遍历 AddressOfNames 数组(该数组的值仅为 RVA 值)并搜索给定的名称。一旦加载器在“i”位置找到匹配项,它将引用 AddressOfNameOrdinals 数组的第 i 个索引,并获取与该函数关联的序号。获得序号后,加载器将引用 AddressOfFunctions 中序号值位置处的值,最终获得与导入函数关联的 RVA。

关键点在于,当调用 LoadLibrary 时,Windows 加载器执行的所有这些搜索和访问操作都是在 DLL 被映射到进程地址空间之后完成的。在 DLL 映射期间,整个 DLL 文件(包括其 PE 头)都会写入内存,加载器解析内存中的头以访问导出目录。这意味着,如果 DLL 本身能够在附加到进程后覆盖其内存中的 PE 头以更改导出目录地址(即 DataDirectory 的第 0 个索引),那么加载器可以在一个任意的导出目录中查找要导入的函数,而不是在编译器添加的导出目录中。

命令行参数

root@kitploit:~

   __                       _          _     _
  /__\_  ___ __   ___  _ __| |_  /\  /(_) __| | ___ _ __
 /_\ \ \/ / '_ \ / _ \| '__| __|/ /_/ / |/ _` |/ _ \ '__|
//__  >  <| |_) | (_) | |  | |_/ __  /| | (_| |  __/ |
\__/ /_/\_\ .__/ \___/|_|   \__\/ /_/ |_|\__,_|\___|_|
          |_|
                     by @R0h1rr1m

Usage of C:\Users\Public\DLLDemo\ExportHider.exe:

    -h | --help                                 显示帮助信息。
    -i | --input <输入路径>                     要隐藏的函数名称列表的输入文件路径。(必填)
    -o | --output <输出路径>                     DLL 模板的输出路径。(必填)
    -n | --name <DLL 名称>                       DLL 导出目录的名称。(必填)
    -c | --count <其他函数数量>                   不隐藏的其他导出函数的数量。

关于 -i | --input <输入路径> 参数,你需要指定一个输入文件的路径,该文件逐行存储要隐藏的函数名称。示例输入文件的内容如下:

root@kitploit:~
TestFunction1
TestFunction2
TestFunction3

关于 -c | --count 参数,如果你不想隐藏所有导出的函数(即有些函数通过 __declspec(dllexport) 或 .def 文件导出,并且它们会通过 PE 文件查看器出现在文件系统上的 DLL 文件的导出目录中),只需通过此参数指定它们的数量,因为工具在进行内存计算时需要此信息。

快速演示视频

解决方法

如果你想要尝试这种技术,在开发该项目过程中我遇到了两个有趣的点。在修改项目之前,你可能需要了解它们:

  • 如果你使用名称调用 GetProcAddress 函数,Windows 加载器会使用一种类似二分查找的算法来查找导出的函数。因此,所有导出函数(包括隐藏的)的名称必须在 AddressOfNames 数组中排序。否则,GetProcAddress 函数将返回 NULL。因此,我使用了冒泡排序算法来对该数组成员的顺序进行排序。
  • 如上所述,AddressOfNames 数组、AddressOfFunctions 数组、DataDirectory 数组以及其他一些字段需要以相对虚拟地址(RVA)形式存储值。此外,它们将这些 RVA 值存储在 DWORD 大小的字段中。当你使用动态内存分配函数(如 VirtualAlloc 或 HeapAlloc)为新的任意导出目录分配内存区域时,给定的地址会远离 DLL 映射的区域,并且 RVA 值无法放入 DWORD 大小的字段中,从而导致整数溢出。这就是为什么我在 DLL 模板中使用字节数组类型的全局变量来满足内存需求。

静态导入 DLL(即 DLL 侧加载)场景

创建此项目时,我的首要目标是生成一个缺少导出(或完全没有导出表)但新创建的进程仍能成功加载的 DLL。我认为这种行为可能会为 DLL 侧加载载荷带来新的发挥空间。然而,我未能找到任何函数或方法,使得导出目录修复存根能够在 Windows 加载器检查 DLL 导出函数之前运行。

dll_timing_problem (1)

更具体地说,我在 NTDLL 中与 Windows 加载器相关的代码部分注意到了以下流程和函数调用(来自我的旧笔记;可能存在一些错误,因为我不是逆向工程专家):

root@kitploit:~
1. LdrpMapDll - 在此处将 DLL 映射到进程地址空间。所有满足 DLL 名称条件的 DLL 都直接放入内存,不对文件系统版本进行预检查。
2. LdrpSnapModule - 在此处 Windows 加载器开始解析导入。对于每个导入描述符,它解析 PE 结构,检查导出表,对导入函数进行二分查找,计算其 RVA,并将其地址写入相应调用者进程的导入地址表条目。
3. LdrpDoPostSnapWork - 如果第二步对每个导入函数都成功,则在此函数中完成内存保护、TLS 初始化、CFG 启用等操作。
4. LdrpInitializeNode - 如果第三步成功,则在此步骤中进行模块链接函数。
5. LdrpCallTlsInitializers - 在此处调用 TLS 回调(在调用 DllMain 之前)。
6. LdrpCallInitRoutine - 在此处首次为导入的 DLL 调用 DllMain 本身。在原始解决方案中,此函数修复导出表为时已晚。

当你运行一个导入 DLL 的可执行文件时,如果 Windows 加载器在 DLL 的导出表中找不到所需的函数名称,它会停止执行,并且不会执行 LdrpSnapModule 之后的函数。

对于 DLL 侧加载情况,我们无法修改调用者进程;因此,动态修复导出表的唯一机会是在 LdrpMapDll 和 LdrpSnapModule 函数之间找到代码执行的机会,因为加载器在 LdrpSnapModule 函数检查期间会立即停止。我尝试了 TLS 回调、二次 DLL 加载、转发导出以及其他一些方法,但都没有找到这样的位置。因此,遗憾的是,这种方法不直接适用于 DLL 侧加载或静态导入的 DLL。如果你发现这个问题的解决方案或可行的变通方法,我将非常乐意进一步探讨——无论是讨论想法、共同思考方法,还是实现它。任何这方面的贡献都将不胜感激。

一种可能用于 DLL 侧加载场景的方法是,将整个工作拆分为两个 DLL,即代理 DLL 和载荷 DLL。代理 DLL 使用 EXE 期望的名称,并具有满足加载器静态导入检查的可见导出,而载荷 DLL 包含真正的隐藏功能,缺少导出,并在 DllMain 中重建其导出表。我不认为这是该问题的良好变通方案,因此我没有实现它。

参考

  • https://rioasmara.com/2021/10/10/analyze-dll-export-with-pe-bear/
  • https://ferreirasc.github.io/PE-Export-Address-Table/

免责声明

仅用于授权的安全测试。未经明确许可使用此工具对他人的系统进行滥用是违法的。

下载工具