判断DLL是否可能被劫持,需检查LoadLibrary调用是否使用相对路径、当前工作目录是否可控、是否启用SafeDllSearchMode及KnownDLLs机制,并用Process Monitor验证实际加载路径是否来自预期位置。

怎么判断当前 DLL 加载是否可能被劫持
Windows 默认使用 LoadLibrary 的搜索顺序(当前目录 → 系统目录 → Windows 目录 → PATH 中各目录),只要当前工作目录下存在同名但恶意的 DLL,就可能被优先加载。这不是“漏洞”本身,而是设计行为;真正的问题在于开发者调用 LoadLibrary 时没加防护。
常见高危场景包括:程序从桌面或下载目录启动、双击打开文件触发关联程序、服务以低权限用户身份运行且工作目录可写。
检测思路不是扫描全盘找“可疑 DLL”,而是检查自身加载行为是否可控:
- 用 Process Monitor(Sysinternals)过滤进程名 +
LoadLibrary相关操作,观察实际加载路径是否来自预期位置 - 在调试器中下断点到
LoadLibraryA/LoadLibraryW,看传入的lpLibFileName是不是相对路径(如"sqlite3.dll") - 检查项目中所有
LoadLibrary调用点,确认没有硬编码相对名且未调用SetDllDirectory("")或SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
如何用绝对路径安全加载 DLL
核心原则:不依赖搜索路径,直接指定完整路径。但这需要你知道 DLL 在哪——不能靠猜测,得从可信来源获取。
推荐做法是把 DLL 和主程序放在一起(如 "./libs/mylib.dll"),再用 GetModuleFileName 获取自身路径,拼出绝对路径:
WCHAR szPath[MAX_PATH] = {0};
GetModuleFileName(NULL, szPath, MAX_PATH);
PathRemoveFileSpec(szPath); // 去掉 exe 文件名,只剩目录
PathAppend(szPath, L"\libs\mylib.dll"); // 拼接子路径
HMODULE hMod = LoadLibrary(szPath); // 此时路径绝对,绕过搜索逻辑
注意:PathRemoveFileSpec 和 PathAppend 来自 shlwapi.h,需链接 shlwapi.lib;若不想引入 Shell API,可用 wcsrchr + 手动拼接,但要小心路径分隔符和缓冲区溢出。
关键点:
- 不要用
GetCurrentDirectory—— 它返回的是进程当前工作目录,用户可随意更改 - 避免字符串拼接时硬写
"\",应统一用PATH_SEPARATOR或std::filesystem::path(C++17) - 加载前可先用
GetFileAttributes验证文件存在且非目录,防止路径穿越(如"..\..\evil.dll")
为什么 SetDefaultDllDirectories 不够用
调用 SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32) 确实能禁用当前目录和 PATH 查找,看起来很安全。但它有硬伤:
- 只影响后续的
LoadLibrary调用,对已加载的模块(比如 CRT 或隐式链接的 DLL)无效 - 如果程序用了延迟加载(
/DELAYLOAD),延迟加载助手仍会走旧搜索逻辑,除非你重写DelayLoadFailureHook - 第三方库内部若自己调用
LoadLibrary(如 FFmpeg 解码器、SQLite 的扩展加载),它们不受你的设置控制 - Windows 7 不支持该函数(需 KB2533623),老旧环境无法落地
所以它适合“辅助加固”,不能替代显式绝对路径加载。
隐式链接 DLL 怎么防劫持
如果你的程序通过 #pragma comment(lib, "xxx.lib") 或链接器输入隐式链接 DLL,那么加载完全由系统控制,你无法插手 LoadLibrary 调用时机。这时候唯一可靠的方式是:确保 DLL 就在 EXE 同目录,且程序启动时不切换工作目录。
更稳妥的做法是改用显式链接(LoadLibrary + GetProcAddress),哪怕只是封装一层:
class SafeDllLoader {
HMODULE m_hMod = nullptr;
public:
bool load() {
WCHAR path[MAX_PATH];
if (!GetModuleFileName(NULL, path, MAX_PATH)) return false;
PathRemoveFileSpec(path);
PathAppend(path, L"\sqlite3.dll");
if (GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) return false;
m_hMod = LoadLibrary(path);
return m_hMod != nullptr;
}
// ...
};
隐式链接看似方便,但在安全敏感场景下,它把控制权交给了系统路径逻辑——而这个逻辑恰恰是劫持发生的温床。
真正难的不是写几行代码,而是把所有 DLL 加载点都找出来、统一收口、并验证每个路径是否真的不可篡改。很多团队只修了主流程,却漏掉了日志模块、插件系统、甚至单元测试里的临时加载,结果白忙一场。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











