如何通过模块枚举和特征比对识别程序非法注入DLL行为?
- 内容介绍
- 文章标签
- 相关推荐
本文共计943个文字,预计阅读时间需要4分钟。
直接提出结论,不涉及图形解释、冗余和超过100字:
用 EnumProcessModules 获取当前进程所有已加载模块
Windows 不保证 EnumProcessModules 返回顺序,且可能跳过某些特殊映射(如反射式注入的内存 DLL)。必须配合 GetModuleFileNameEx 和 GetModuleInformation 获取基址、大小、路径三要素。
- 若
GetModuleFileNameEx返回空字符串或路径为"\??\C:\Windows\System32\ntdll.dll"类似格式,需进一步查证——某些注入会伪造路径但实际未映射磁盘文件 - 64 位进程调用时,
HMODULE是 8 字节指针,传给GetModuleInformation的lpmodinfo必须指向MODULEINFO结构体,不能误用 32 位偏移计算 - 常见错误:未检查
EnumProcessModules返回值是否为 0,导致后续读取越界或崩溃
识别无文件模块(fileless module)的关键判断点
合法系统 DLL 几乎都映射自磁盘,而反射式注入、直接内存加载的 DLL 往往不关联真实路径。但注意:GetModuleFileNameEx 失败 ≠ 一定非法——.NET Core 的原生 AOT 模块也可能无路径。
- 优先检查
GetMappedFileName:它能返回内核视角下的映射来源,比GetModuleFileNameEx更底层、更难伪造 - 若
GetMappedFileName返回"DeviceHarddiskVolumeX..."以外的路径(如"DevicePhysicalMemory"或空),高度可疑 - 不要只看扩展名:攻击者常把恶意 DLL 命名为
winhttp.dll或msvcp140.dll,必须校验数字签名与导出函数数量
导出表 + 签名双重验证防绕过
只验证签名会被无签名但结构合规的恶意 DLL 绕过;只看导出表又会漏掉以 DllMain 为唯一导出的精简注入体。二者必须结合。
立即学习“C++免费学习笔记(深入)”;
- 用
ImageNtHeader定位 PE 头后,检查OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress是否非零且落在节区内 - 再读取
IMAGE_EXPORT_DIRECTORY,确认NumberOfFunctions > 0—— 有些壳会保留导出表头但清空函数地址数组,此时AddressOfFunctions指向全零内存 - 调用
WinVerifyTrust验证签名时,传入WINTRUST_ACTION_GENERIC_VERIFY_V2,而非旧版WINTRUST_ACTION_TRUSTPROVIDER_TEST,否则无法识别 EV 证书
为什么不能只依赖 LoadLibraryA 地址扫描?
远程线程注入常用 LoadLibraryA,但现代绕过手段(如 APC 注入、线程劫持)完全不调用它。扫描该函数地址只能捕获一部分注入行为,且易被 inline hook 或 IAT 替换干扰。
- APC 注入中,恶意代码通过
QueueUserAPC注入到目标线程 APC 队列,执行时不经过LoadLibraryA入口 - 线程劫持直接覆写目标线程上下文中的
RIP,跳转到内存中 shellcode,全程无 API 调用痕迹 - 真正可靠的依据永远是:模块是否在内存中、是否有合法来源、是否具备 DLL 基本结构特征——而不是它“怎么进来”的
最易被忽略的一点:32/64 位混合场景下,EnumProcessModules 在 Wow64 进程中可能只返回 32 位模块视图,需显式调用 EnumProcessModulesEx 并传 LIST_MODULES_32BIT | LIST_MODULES_64BIT 标志,否则会彻底漏掉跨架构注入的模块。
本文共计943个文字,预计阅读时间需要4分钟。
直接提出结论,不涉及图形解释、冗余和超过100字:
用 EnumProcessModules 获取当前进程所有已加载模块
Windows 不保证 EnumProcessModules 返回顺序,且可能跳过某些特殊映射(如反射式注入的内存 DLL)。必须配合 GetModuleFileNameEx 和 GetModuleInformation 获取基址、大小、路径三要素。
- 若
GetModuleFileNameEx返回空字符串或路径为"\??\C:\Windows\System32\ntdll.dll"类似格式,需进一步查证——某些注入会伪造路径但实际未映射磁盘文件 - 64 位进程调用时,
HMODULE是 8 字节指针,传给GetModuleInformation的lpmodinfo必须指向MODULEINFO结构体,不能误用 32 位偏移计算 - 常见错误:未检查
EnumProcessModules返回值是否为 0,导致后续读取越界或崩溃
识别无文件模块(fileless module)的关键判断点
合法系统 DLL 几乎都映射自磁盘,而反射式注入、直接内存加载的 DLL 往往不关联真实路径。但注意:GetModuleFileNameEx 失败 ≠ 一定非法——.NET Core 的原生 AOT 模块也可能无路径。
- 优先检查
GetMappedFileName:它能返回内核视角下的映射来源,比GetModuleFileNameEx更底层、更难伪造 - 若
GetMappedFileName返回"DeviceHarddiskVolumeX..."以外的路径(如"DevicePhysicalMemory"或空),高度可疑 - 不要只看扩展名:攻击者常把恶意 DLL 命名为
winhttp.dll或msvcp140.dll,必须校验数字签名与导出函数数量
导出表 + 签名双重验证防绕过
只验证签名会被无签名但结构合规的恶意 DLL 绕过;只看导出表又会漏掉以 DllMain 为唯一导出的精简注入体。二者必须结合。
立即学习“C++免费学习笔记(深入)”;
- 用
ImageNtHeader定位 PE 头后,检查OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress是否非零且落在节区内 - 再读取
IMAGE_EXPORT_DIRECTORY,确认NumberOfFunctions > 0—— 有些壳会保留导出表头但清空函数地址数组,此时AddressOfFunctions指向全零内存 - 调用
WinVerifyTrust验证签名时,传入WINTRUST_ACTION_GENERIC_VERIFY_V2,而非旧版WINTRUST_ACTION_TRUSTPROVIDER_TEST,否则无法识别 EV 证书
为什么不能只依赖 LoadLibraryA 地址扫描?
远程线程注入常用 LoadLibraryA,但现代绕过手段(如 APC 注入、线程劫持)完全不调用它。扫描该函数地址只能捕获一部分注入行为,且易被 inline hook 或 IAT 替换干扰。
- APC 注入中,恶意代码通过
QueueUserAPC注入到目标线程 APC 队列,执行时不经过LoadLibraryA入口 - 线程劫持直接覆写目标线程上下文中的
RIP,跳转到内存中 shellcode,全程无 API 调用痕迹 - 真正可靠的依据永远是:模块是否在内存中、是否有合法来源、是否具备 DLL 基本结构特征——而不是它“怎么进来”的
最易被忽略的一点:32/64 位混合场景下,EnumProcessModules 在 Wow64 进程中可能只返回 32 位模块视图,需显式调用 EnumProcessModulesEx 并传 LIST_MODULES_32BIT | LIST_MODULES_64BIT 标志,否则会彻底漏掉跨架构注入的模块。

