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






4.84/5 (18投票s)
尝试深度 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”模式,寄存器窗口将显示 EAX
、EBX
等而不是 RAX
、RBX
,并且可以进行 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 位可执行文件。Library
和FasmDLL
:两个将被 Executable 加载的 32 位 DLL。TestLoad32
:一些辅助程序,用于测试MemoryModule
的功能。
历史
- 2022 年 7 月 1 日:首次发布