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