最可靠的方式是枚举所有加载模块并比对路径、签名与导出表三要素;需用EnumProcessModulesEx配合GetMappedFileName(而非GetModuleFileNameEx),传LIST_MODULES_ALL标志,结合GetModuleInformation防越界,双验导出表与签名,四条件叠加(权限异常+无磁盘路径+导出异常+签名失败)才拦截。

最可靠的方式是枚举当前进程所有加载模块,再逐个比对路径、签名与导出表三要素;仅靠检查内存页权限或文件名匹配,基本等于没检测。
EnumProcessModulesEx 必须配合 GetMappedFileName 用
GetModuleFileNameEx 返回的路径容易被伪造(比如显示为 \??\C:\Windows\System32\kernel32.dll),而 GetMappedFileName 从内核视角读取映射来源,更难绕过。若它返回 DevicePhysicalMemory、DeviceNull 或空字符串,大概率是反射式注入或内存加载的无文件 DLL。
- 必须传入
LIST_MODULES_ALL标志,否则 64 位系统下会漏掉某些驱动级映射模块 - 对每个
HMODULE调用前,先用GetModuleInformation拿到基址和大小,避免后续读取越界 - 若
GetMappedFileName失败,不要直接判为非法——.NET Core AOT 模块也可能无映射路径,需结合其他特征判断
导出表 + 数字签名必须双验,缺一不可
只校验签名会被无签名但结构合规的恶意 DLL 绕过;只看导出表又会漏掉以 DllMain 为唯一导出的精简注入体。二者必须同时成立才认为模块可信。
- 用
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 证书验证
别信 PAGE_EXECUTE_READWRITE 就是恶意模块
很多合法场景也会申请可执行可写内存(如 JIT 编译、热更新、调试器断点插桩),单靠内存页属性标记“可疑”误报率极高。
立即学习“C++免费学习笔记(深入)”;
- 系统 DLL 如
ntdll.dll在某些启动阶段也会短暂拥有PAGE_EXECUTE_READWRITE权限 - 真正危险的是:权限 + 无磁盘路径 + 导出表异常 + 签名验证失败,这四者叠加才值得拦截
- 用
VirtualQueryEx查内存属性时,注意目标地址必须是模块基址,不是随便一个指针
最容易被忽略的一点:EnumProcessModules 不保证返回顺序,也不保证覆盖所有模块(尤其反射式注入的内存 DLL)。你写的检测逻辑如果依赖“第 N 个模块一定是 xxx”,那第一轮就失效了。


















