如何通过查看PE文件判断一个exe是静态加载还是动态加载DLLheader?

How to tell if an exe will load a DLL statically or dynamically by looking at the PE file header?

正如标题所说,如何通过查看PE文件判断一个exe是静态加载还是动态加载DLLheader?

换句话说,如何判断 DLL 是可执行文件的一部分,还是会被加载程序调用?

谢谢

让我先澄清一些术语以避免混淆。

在 DLL 中执行的任何东西 根据定义都是动态的。但是,DLL 可以静态绑定或动态绑定到 executable.

使用静态绑定,EXE links 针对 DLL 的导入库(实际上是与 DLL 一起构建的 .LIB 文件)。 header 文件中相应的 DLL 函数原型通常使用 __declspec(dllimport) 声明。这会导致 EXE 被 Windows 加载程序在运行时填充的每个 DLL 符号的存根填充。最终的 EXE 在其 PE headers 中有一个导入部分结构,列出了 Windows 加载程序在运行时解析的所有 DLL 及其符号名称(例如函数),从而促进了这一点。 Windows 然后在 EXE 在其入口点开始执行主线程之前,执行所有脏工作来查找和加载这些 DLL 和引用的符号地址。如果 Windows 找不到任何 DLL 或引用的符号地址,EXE 将不会启动。

通过动态绑定,EXE 显式调用代码来加载 DLL 并解析符号地址。这是使用两个 KERNEL32 API 函数完成的:LoadLibrary() 和 GetProcAddress()。如果一个 EXE 这样做,将没有相关的导入部分描述这个 DLL 及其符号,并且 Windows 加载器将愉快地加载 EXE,而不会知道任何 DLL。然后由应用程序定义如何处理 LoadLibrary() 和/或 GetProcAddress() 的成功或失败。

此时值得注意的是,像C-Runtime这样的库可能以DLL形式(动态库)或静态形式提供(静态库)。如果您 link 这些库之一 静态 ,在构建的 EXE 的 PE header 中将没有 DLL 导入部分,并且没有函数存根要在运行时解析那个图书馆。这些符号(函数 and/or 数据变量)不再是存根,而是成为 EXE 的一部分。静态库函数 and/or 数据被复制到 EXE 中,并由 linker 显式分配相对地址;与这些符号直接由 EXE 实现没有什么不同。此外,在这些函数的代码中将没有隐式(通过 Windows 加载程序)或显式地解析 LoadLibrary() 或 GetProcAddress(),因为它们将直接存在并且 self-contained 在最终的 EXE 中。作为 side-note,在这种情况下可以使用调试符号来尝试区分 EXE 实现的函数和库实现的函数(如果您关心的话),但这在很大程度上取决于用于构建 EXE 和静态文件的设置图书馆。

术语解释清楚后,让我尝试回答您的问题! :) 我还要补充一点,我不打算详细讨论模块导入部分的绑定和未绑定导入符号的细节,因为这种区别与原始问题无关,而更多地与加快 Windows装载机。但是,如果您对这些细节感兴趣,可以阅读 Microsoft 的 PE COFF Specification.

要查看 EXE 是否静态绑定 到 DLL,您可以自己解析 PE header 以定位 DLL 导入部分或使用以下方法之一数十种工具可以为您完成此操作,例如 Dependency Walker。例如,如果您在 Dependency Walker 中加载 EXE,您将在 EXE 本身下方的 top-left 窗格中看到所有静态绑定 DLL 的列表。如果在运行时没有找到这些 DLL 中的任何一个,程序将无法加载。在右窗格中,顶部 table,您将看到在所选 DLL 的 EXE 中引用的符号(例如函数)。必须另外找到所有这些符号才能加载 EXE。下方的 table 仅显示 DLL 导出的所有符号,无论是否引用。

如果 EXE 对给定的 DLL 使用 动态绑定(也称为 手动绑定),则不会有导入部分该 DLL,因此您不会在 Dependency Walker 等工具中看到它被引用。 但是你可以点击Dependency Walker中的KERNEL32.DLL(所有EXE都会有这个依赖,虽然这个规则有例外我不会进入此处)并找到对 LoadLibrary() 和 GetProcAddress() 的引用。不幸的是,大多数 EXE 在 C-Runtime 等样板代码中引用了这些函数,所以这不会告诉你太多。

如果您想更深入地了解哪些 DLL 是由应用程序手动加载的,首先要尝试的是通过在 EXE 中搜索 DLL 名称来找到该 DLL 名称字符串。请注意,DLL 名称字符串不必以“.DLL”结尾,因为如果未提供,LoadLibrary() 会自动采用此扩展名。在二进制模块中搜索字符串的标准工具是 Sysinternals Strings。这对于不试图隐藏它们正在做的事情的模块非常有用。

有也就是说,混淆代码(在解包程序、病毒等中找到)可能混淆或加密 DLL 名称以及引用的函数。代码甚至可以选择在运行时解析 LoadLibrary() 和 GetProcAddress() 以进一步阻碍弄清楚它们在做什么的努力。在这些情况下,您最好的选择是使用 Sysinternals Process Monitor 之类的工具或启用了详细日志记录的调试器来观察程序运行时加载的 DLL。您还可以使用反汇编程序(例如 IDA)来尝试拼凑代码的作用。要找出正在使用的 DLL 符号,您可以在调试器中启动 EXE 并等待 entry-point 处的初始中断。然后在KERNEL32.GetProcAddress中的第一条指令上添加断点。 运行 程序。当遇到该断点时,堆栈参数将包含试图解析的符号。

如您所见,如果应用程序手动解析 DLL 符号(动态绑定),则确定正在引用哪些 DLL 的过程并不那么简单。