【发布时间】: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 的位置。
有没有办法解决这个问题?以下是我可能的解决方案,但我不知道该怎么做:
强制
LoadLibrary在特定地址加载 DLL,或者使用某种选项强制这些 DLL 在内存中彼此靠近。修改 DLL 本身以删除任何 RIP+(32 位值)指令,并将其替换为支持 64 位地址值的指令。
如果我可以让供应商重新编译 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