【问题标题】:Delay loaded DLLs are loaded too far away in memory from each other延迟加载的 DLL 在内存中加载的距离太远
【发布时间】:2018-07-03 23:35:17
【问题描述】:

我有一个 x64 Windows 应用程序,它有时会在我无法控制的 DLL 库中崩溃。这个库实际上是一大组相互依赖的 DLL(其中约 40 个)。崩溃并不总是在同一个地方,但这里有一个正在发生的例子:

Exception thrown at 0x0000018393916B72 (foo.dll) in bar.exe: 0xC0000005: Access violation reading location 0x000001831C1F95A0.

在这个位置,Visual Studio 中的反汇编显示如下:

0000018393916B72  cmp         dword ptr [1831C1F95A0h],3  

起初我觉得这很奇怪,因为这意味着 DLL 将被硬编码以在错误的位置加载值。于是,我查看了实际的指令内存,发现如下:

83 3d 27 2a 8e 88 03

具体反汇编为:

cmp    DWORD PTR [rip+0xffffffff888e2a27],0x3

这里发生的事情是在 DLL 中生成了一条指令,上面写着“将位于该指令之后 X 字节的值与 3 进行比较”。此内存驻留在与此不同的 DLL 中,并且 X 是一个 32 位值。我假设 X 的值在加载其他 DLL 时被修补。

查看指令的实际字节,很明显它需要 rip+0x888e2a27,它确实指向该库中的一个 DLL 所在的位置,但不幸的是,这个 32 位值被符号扩展,最终看起来在内存中向后而不是向前。

最终,根本原因是这些 DLL 被加载到内存中的距离超过了 0x7FFFFFFF 字节,因此当它们尝试使用 32 位偏移量相互引用时,它们无法这样做。我不确定为什么生成这些 DLL 的编译器选择使用 RIP+32 位指令而不是 64 位绝对地址,但它似乎经常这样做。可能是一种节省尺寸的措施?

无论哪种方式,我认为解决此问题的最简单方法是强制LoadLibrary 将这些 DLL 彼此相邻加载,或者至少彼此相对接近。我尝试了各种方法来做到这一点,但似乎 Windows 故意随机且不透明地决定加载 DLL 的位置。

有没有办法解决这个问题?以下是我可能的解决方案,但我不知道该怎么做:

  1. 强制LoadLibrary 在特定地址加载 DLL,或者使用某种选项强制这些 DLL 在内存中彼此靠近。

  2. 修改 DLL 本身以删除任何 RIP+(32 位值)指令,并将其替换为支持 64 位地址值的指令。

  3. 如果我可以让供应商重新编译 DLL,请让他们使用特定的编译器标志来防止使用这些 RIP+(32 位值)指令。但是,我不知道这个编译器标志是什么。我假设他们使用的是 MSVC,但我不确定。

【问题讨论】:

  • 最佳猜测:在原始代码中有一些遗留的 32 位指针算法未正确移植到 64 位。由于 ALSR 是 Windows 安全性不可或缺的一部分,您通常无法控制特定模块的加载位置。
  • 这个前提听起来很可疑。很难想象编译器会故意编造对不同 DLL 或可执行文件中的代码或数据的相对引用。我怀疑一个损坏的二进制文件或从错误地址开始的反汇编,或者只是一个普通的旧错误(当 DLL 碰巧加载得更近时,可能会间歇性地被掩盖)。
  • @David Clamage:我从 VS 2017 获得的代码与 Godbolt 在您的示例中显示的代码不同。我有mov rax, qword ptr [__imp_g]; cmp dword ptr [rax],3。 __imp_g 解析为同一模块内的地址,因此其他模块实际加载的位置似乎无关紧要。也许该应用程序是使用已修复的错误版本的 VS 编译的。
  • @AdrianMcCarthy 您能否提供mov rax 指令的实际字节码?我很想知道它是否仍然是 rip+X 形式。
  • mov 是48 8B 05 9B E8 00 00,cmp 是83 38 03。调试器显示mov的源地址在当前模块的地址范围内(推测是导入地址表)。

标签: c++ windows visual-studio dll loadlibrary


【解决方案1】:

无法保证这会起作用,但您可以尝试使用 EDITBIN 命令编辑有问题的 DLL,以便将它们标记为不支持 ASLR。

执行此操作的命令是:

editbin /DYNAMICBASE:NO /HIGHENTROPYVA:NO foo.dll

Editbin 附带您的 msvc / windows sdk 安装。

有关详细信息,请参阅: https://msdn.microsoft.com/en-us/library/d25ddyfc.aspx

如果启用了强制 ASLR,加载程序将从地址空间的底部开始搜索此类 DLL 的基地址。所以也许这意味着他们最终彼此足够接近。

此行为在以下内容中进行了解释:

https://blogs.technet.microsoft.com/srd/2017/11/21/clarifying-the-behavior-of-mandatory-aslr/

最后一点是在执行应用程序时尽可能早地加载 DLL,以便尽可能多地释放地址空间。

祝你好运:-)。

【讨论】:

  • 我在这个库的所有 DLL 上运行了这个命令,除了其中一个之外,所有的 DLL 都没有受到影响。我将使用这个更改后的 DLL 并查看崩溃是否继续发生。
猜你喜欢
  • 2017-02-11
  • 2010-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-06
  • 2013-12-02
  • 1970-01-01
  • 2021-11-23
相关资源
最近更新 更多