手动加载 DLL 中的异常处理

Exception handling in manually loaded DLL

目前我们正在研究在 x64 平台上在进程虚拟地址 space 的较低 4Gbs 中手动加载 DLL 的主题。它是必需的,因为 DLL 是在任何地方都明确使用 32 位类型编写的,并且不能被重写。所以我们要在项目中使用这个特性,我们要确保 DLL 只使用较低的 4Gbs 来保持工作。

网络上有一些实现手动 DLL 加载的链接,我们以此为基础:link

此变体有效。目前只检测到一些问题:

  1. 现在无法使用源进行调试,OS 只是看不到这个模块,它只是一个内存区域,没有别的,所以没有加载 PDB。
  2. 我们的项目是以这样一种方式实现的,即 DLL 从某些有效负载所在的外部框架调用函数。然后在框架中引发异常(故意的,不是偶尔的),这就是问题发生的地方。此异常仍未处理,但框架代码中存在处理程序。

当 DLL 通过 x86 或 x64 上的 LoadLibrary 加载时(我们很幸运它加载在较低的 4Gbs 区域)一切正常。我们可以看到整个 SEH 链(例如在 WinDbg 中)并且异常处理得很好。

手动加载 DLL 时,WinDbg 显示如下:

>!exchain
Frame 0x01: MSVCR120D!__ExceptionPtr::_RethrowException+0x1e1 (000007fe`d9cf4281)
ehandler MSVCR120D!__GSHandlerCheck (000007fe`d9e11eb0)
Frame 0x0b: error getting module for 000000000214daa1
Frame 0x0c: error getting module for 0000000000000003
Frame 0x0d: error getting module for 0000000100000000
Frame 0x0e: error getting module for 0000000002ffa420
Frame 0x0f: error getting module for 0000000100000000
Frame 0x10: error getting module for 0000000000000004

我们尝试关闭 /SafeSEH 选项,但结果相同。我们这样做是因为有人猜测 OS 可以拒绝处理不在受保护模块中的异常处理程序。

目前猜测它发生的原因是 OS 需要这样说内部可见模块(一些内核对象在通过合法系统函数 LoadLibrary 加载 DLL 的过程中创建)异常链可以通过。

您如何看待这个问题的可能解决方案?

编辑:回答如下。

实际上我们已经找到了 "legal" OS 函数的解决方案。

请记住,我们尝试加载的 DLL 实际上是一个 x64 模块,但它的编写方式与 x86 模块类似。它只能处理整个虚拟地址的低 4 GB space,因为它可以显式转换为 32 位类型。

所以如果你想在指定地址加载一个DLL,你需要做以下事情:

  1. 检查是否有足够的可用内存作为 DLL 的块以适应那里(使用 VirtualQueryEx 函数在其中循环)。
  2. 将图像基址对齐到 0x10000(否则 ERROR_BAD_EXE_FORMAT 尝试加载此类模块后会出现错误)。
  3. 用新值覆盖默认图像库(执行链接器 /BASE 选项的操作)。
  4. 补丁重定位(它们将在 LoadLibrary 调用后遍历,偏移量从原始基地址计算,而不是显式设置。所以我们要修补这些偏移量 OS 以正确加载图像,具有新的图像基础和相应计算的重定位)。如果不这样做,那么 ERROR_NOACCESS 尝试加载这样的模块后将出现错误。
  5. 重置 ASLR 标志,允许 DLL 在动态映像库中加载,尽管有任何预设(执行链接器 /DYNAMICBASE:NO 选项的操作)。
  6. 呼叫LoadLibrary.

完成此算法中的步骤后,我们可以像往常一样使用加载的模块,它与任何其他加载的 DLL 没有区别,可以使用源代码进行调试,并且正在正确处理引发的异常。

您可以存储一个 unsigned int 并使用全局 std::map,而不是存储 32 位指针。 这将使您的代码使用映射到 void* 的 32 位 "pointer"。可能您还需要 void* 到 unsigned int 的反向映射。此代码适用于两个操作系统。您还需要包含免费 ID 的 std::set unsigned int。