65.9K
CodeProject 正在变化。 阅读更多。
Home

将 x86 DLL 加载到 x64 可执行文件中... 可能实现吗?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (18投票s)

2022 年 7 月 1 日

CPOL

4分钟阅读

viewsIcon

14417

尝试深度 CPU 功能

引言

好了,这很有趣。每个人都希望并且在未来几年内仍然会希望能够将 x86 DLL 加载到 x64 主机中。我的全功能音视频测序器 Turbo Play 可以加载 VST 插件,但其中一些插件只有 x86 版本。因此,我不得不将其编译成 32 位版本,或者通过某种辅助可执行文件创建 64 位<->32 位桥接器。

但直接从 64 位进程执行 32 位代码在 Windows 中似乎是不可能的。或者,是否有办法做到这一点?

是的,有一种方法,虽然目前用处不大,但将来可能会转化为一个有用的解决方案。请继续阅读!

警告:本文不完整。使其达到生产状态需要付出努力。敬请关注,如果您想贡献,请告知我。

您将使用 Visual Studio、WinDbg 和 Flat assembler。

背景

故事开始于我在调试 Windows 11 中的 x64 应用程序时,不小心在 Visual Studio 调试器中打开了“寄存器”窗口。

CS 的值为 **0x33**。任何阅读过我的 Intel 汇编手册 的人都知道,CS 是指向长模式 GDT 的选择器。然而,在我的文章中,我也提到 x86-64 CPU 还有一个“兼容模式”,可以在不进行模拟的情况下运行 32 位程序。确实,当我调试应用程序的 x86 版本时,CS 的值为 **0x23**,这显然是一个 32 位平面段。

现在,我记得我文章中的这句话

64-bit OSs keep jumping from 64-bit to compatibility mode in order to be able to run 
both 64-bit and 32-bit applications.

然后我有了这个想法。为什么**我的应用程序**不能进行跳转,因为我已经拥有了一个 32 位平面代码段?

然后我做到了!

代码段选择器

我将它们视为 0x33(长模式)和 0x23(兼容模式)。在这篇文章中,我将它们硬编码到 source.asm 中。稍后,我将构建一个内核模式驱动程序,该驱动程序可以检查 GDT 并确定应使用哪些值。要找出您 Windows 中的选择器是什么,只需启动一个 x64 和 x86 应用程序并检查 CS 的值(尽管我相信自 Vista 以来它们没有改变。:)

第一次汇编尝试

为了切换到兼容模式,我必须使用带有 0x23 选择器的 RETF 技巧。

push 0x23    
xor rcx,rcx    
mov ecx,Back32  
push rcx
retf 

Back32”是具有 32 位入口点的地址。无论这个入口点做什么,它最终都需要通过长跳转回到 64 位段。

Back32:
USE32
; Do 32-bit stuff
; jump back to x64
USE64
db 0eah
ret_64:
dd 0
dw 0x33
nop 

ret_64”会包含什么?一个地址,在 64 位下,通过 RET 操作码将控制权返回给我们的调用者。

source.asm 中,我用几行代码演示了这一切。

使用 WinDbg 进行调试

您无法使用 Visual Studio 调试切换,但可以使用 WinDbg。一旦 retf 指令执行完毕,WinDbg 将切换到“x86”模式,寄存器窗口将显示 EAXEBX 等而不是 RAXRBX,并且可以进行 32 位代码。

任务管理器也有些困惑。它在某个时候显示了我的应用程序的两个条目。好吧,我很高兴我让他抓狂了。:)

加载 32 位 DLL

这还不能完全工作,但我在努力。

显然,不能使用 LoadLibrary()。但是有一个不错的内存加载库叫做 'MemoryModule' 存在。它将在内存中加载一个 DLL 文件并初始化它。

即便如此,该库也不能直接使用,因为它在为 x64 编译时会使用 64 位结构,而我们需要它在为 x64 编译时使用 **32 位** 结构。因此,我修改了它,使其能够将我的 32 位 DLL 加载到内存中。

手动加载 DLL 也意味着要修补导入地址表 (IAT)。因为主机是 64 位,无法通过简单的 LoadLibrary()GetProcAddress() 来获取值,但我必须创建一个 32 位辅助程序 Get32Imports,它会读取一个包含所需导入的 XML 文件,并在内存中返回它们的指针。

然后,我将使用 PatchIAT 函数将(32 位!)指针写入内存加载的 DLL。

不幸的是,它仍然无法调用任何导入的 API 函数。当我尝试调用像 'MessageBeep' 这样的 API 时,它会抛出一个无效执行异常。也许你能帮忙。

但我对目前为止的进展感到满意。:)

代码

  • Driver:一个不完整的项目,稍后将为我们提供正确的 GDT 值,而不是使用硬编码的选择器。
  • Get32Imports:读取一个包含所需导入的 XML 文件并查找其值。
  • Executable64:一个尝试运行 32 位代码的 64 位可执行文件。
  • LibraryFasmDLL:两个将被 Executable 加载的 32 位 DLL。
  • TestLoad32:一些辅助程序,用于测试 MemoryModule 的功能。

历史

  • 2022 年 7 月 1 日:首次发布
© . All rights reserved.