简而言之,问题是这样的。我正在编写内核模式 Windows 驱动程序,当加载内核模式 DLL(或其他可执行模块)时会收到通知。在某些情况下,我必须拦截 DLL 入口点例程。也就是说,覆盖它以便首先调用我的例程,然后我可以将控制权传递给原始入口点。
在 32 位(确切地说是 x86)上,这样做没有问题。我得到了模块基映射地址,它实际上以标准 PE 头(由 Windows 可执行文件使用)开头。DLL 入口点有一个 RVA(相对于映像库的地址)。我只是用我的例程地址减去模块基地址来覆盖它。瞧!
现在,64 位的情况更加复杂。问题是 RVA 仍然是 32 位整数。这样的 RVA 覆盖了从映像基地址开始到 4GB 偏移量结束的地址范围。引用同一个可执行模块中的任何符号都没有问题(假设它不超过 4GB 大小),但这会给跨模块拦截带来问题。自然地,我的可执行模块和我试图挂钩的模块不必落入相同的 4GB 范围内,因此存在问题。
暂时我通过无条件地覆盖原始例程序言代码jmp
到我的代码中来解决这个问题。这在 64 位平台上需要 12 个字节。然后,为了从我的例程中调用原始代码,我恢复了被覆盖的 12 个字节(意味着 - 我在覆盖之前保存它们)。
到目前为止 - 没有问题。但是现在情况正在发生变化,我将不得不支持对入口点例程的多线程访问(请不要问为什么,它与加载到所谓的“用户空间”中的多会话DLL有关,单独对于每个终端会话)。
解决方案之一是使用全局锁,但我想避免这种情况。
我知道所谓的“蹦床功能”,但我也想避免这种情况。这样做需要对函数序言代码进行运行时解码,以正确识别指令边界和可能的分支。
最近我想到了另一个想法。如果我能找到原始 DLL 的一些“不需要的”部分,它的长度至少为 12 个字节(大小为mov RAX addr
+ jmp RAX
)怎么办。然后这部分可能会被jmp
我的手中覆盖。然后可以将入口点 RVA 设置为此部分!
要使其工作,只需要可以覆盖的适当部分。我想有这种可能性,因为 PE 标头包含许多几十年来不再使用的历史字段。
这个想法值得尝试,还是这是一种众所周知的技术?安迪还有其他建议吗?
提前致谢。