最可靠方式是枚举进程模块并比对路径、签名与导出表;需用EnumProcessModulesEx配合GetModuleFileNameEx、GetMappedFileName及ImageNtHeader校验三要素,避免仅依赖内存权限或文件名匹配。

直接枚举进程模块并比对路径、签名、导出表,是检测非法 DLL 注入最可靠的方式。仅靠内存权限(如 PAGE_EXECUTE_READWRITE)或文件名模糊匹配,漏报率极高。
用 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 标志,否则会彻底漏掉跨架构注入的模块。


















