Raymond Chen 2005年2月14日
LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) 在根本上是有缺陷的
LoadLibraryEx
函数提供了一个名为 DONT_RESOLVE_DLL_REFERENCES
的标志。官方文档中提到:
如果使用了这个值,并且可执行模块是一个 DLL,系统将不会调用 DllMain 进行进程和线程的初始化与终止。同时,系统也不会加载由指定模块引用的其他可执行模块。
文档中还建议,如果计划仅访问 DLL 中的数据或资源,最好使用 LOAD_LIBRARY_AS_DATAFILE
标志。
在我看来,文档中对于使用 LOAD_LIBRARY_AS_DATAFILE
标志的“建议”还不够有力。
DONT_RESOLVE_DLL_REFERENCES
实际上是一个定时炸弹。
让我们仔细审视一下这个标志的作用与局限。模块虽然被加载到内存中,但其初始化函数并未被调用,也没有加载任何依赖的 DLL。
因此,你无法从这个 DLL 运行代码。(更准确地说,如果尝试运行,将因为 DLL 尚未初始化且其对 DLLs 的所有导入未被解析而导致崩溃。)
然而,与 LOAD_LIBRARY_AS_DATAFILE
标志不同,使用 DONT_RESOLVE_DLL_REFERENCES
加载的 DLL 可以被 GetModuleHandle
找到,并且可以通过 GetProcAddress
使用。
显然,对于使用 DONT_RESOLVE_DLL_REFERENCES
加载的 DLL 来说,使用 GetProcAddress
获取函数地址并不明智,因为你无法从 DLL 调用任何代码。
获取 DLL 的过程地址,却无法调用它,这又有什么意义呢?
GetModuleHandle
的行为触发了这个定时炸弹。
常见的情况是,开发者会调用 GetModuleHandle
来检查 DLL 是否已加载,如果是,再使用 GetProcAddress
获取函数地址并调用它。如果 DLL 是用 DONT_RESOLVE_DLL_REFERENCES
加载的,GetModuleHandle
会成功返回,但调用返回的函数时将会崩溃。
执行此操作的代码并不知道 DLL 是用 DONT_RESOLVE_DLL_REFERENCES
加载的;它没有自我保护的手段。
请注意,无论哪种方式,此类代码都是不安全的,因为最初加载 DLL 的代码可能决定在另一个线程上调用
FreeLibrary
,导致代码在第一个线程下被突然移除。这个问题可以通过使用GetModuleHandleEx
“修复”,它可以增加 DLL 的引用计数,但这并不能解决根本问题。
即使你使用 LoadLibrary
加载 DLL 并将该句柄传递给 GetProcAddress
,你仍然会遇到崩溃,因为 LoadLibrary
会发现 DLL 已经被加载,并且只会增加引用计数。
#include <windows.h>
typedef HINSTANCE (WINAPI *SXA)(HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, int);
int __cdecl main(int argc, char* argv[]) {
if (argc > 1) // 设置定时炸弹
LoadLibraryEx("shell32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
// 受害者代码在这里运行
HINSTANCE h = LoadLibrary("shell32.dll");
if (h) {
SXA f = (SXA)GetProcAddress(h, "ShellExecuteA");
if (f) {
f(NULL, NULL, "notepad.exe", NULL, NULL, SW_SHOWNORMAL);
}
FreeLibrary(h);
}
}
如果没有任何命令行参数运行这个程序,一切都会正常工作:记事本将顺利启动。
然而,如果你传递了一个命令行参数,这将触发定时炸弹,调用 ShellExecuteA
会因为 shell32.dll 被加载而没有解析其 DLL 引用而崩溃。
换句话说,DONT_RESOLVE_DLL_REFERENCES
在根本上是有缺陷的,应该避免使用。它之所以继续存在,仅仅是为了保持向后兼容性。