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