翻译《The Old New Thing》 - LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed

LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20050214-00/?p=36463

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 在根本上是有缺陷的,应该避免使用。它之所以继续存在,仅仅是为了保持向后兼容性。

相关推荐

  1. 翻译prompt

    2024-05-02 18:58:01       29 阅读
  2. 翻译翻译!AI是什么?

    2024-05-02 18:58:01       59 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-02 18:58:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-02 18:58:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-02 18:58:01       87 阅读
  4. Python语言-面向对象

    2024-05-02 18:58:01       96 阅读

热门阅读

  1. HTML_CSS学习:CSSLearning

    2024-05-02 18:58:01       41 阅读
  2. JPA 如何修改 联表查询返回的Map

    2024-05-02 18:58:01       37 阅读
  3. 4月26日划分字母区间+合并区间

    2024-05-02 18:58:01       40 阅读
  4. Element-UI快速入门

    2024-05-02 18:58:01       42 阅读
  5. 两分钟“手撕”Object类

    2024-05-02 18:58:01       38 阅读
  6. Docker Compose部署项目flask+mysql + redis

    2024-05-02 18:58:01       32 阅读
  7. 爬虫学习--2.urllib 库

    2024-05-02 18:58:01       27 阅读