64位DLL入口点覆盖

时间:2012-10-27 12:52:16

标签: windows 64-bit kernel hook driver

简而言之就是这个问题。我正在编写一个内核模式Windows驱动程序,在加载内核模式DLL(或其他可执行模块)时会收到通知。在某些情况下,我必须拦截DLL入口点例程。也就是说,覆盖它以便首先调用我的例程,然后我可以将控制权传递给原始入口点。

在32位(准确地说是x86)上没有问题。我得到了模块基本映射地址,它实际上以标准PE头(由Windows可执行文件使用)开头。那里有一个DLL入口点的RVA(相对于图像库的地址)。我只是通过我的例程地址减去模块基地址来覆盖它。瞧!

现在,64位的事情变得更复杂了。问题是RVAs仍然是32位整数。这样的RVAs覆盖从图像基地址开始到以4GB偏移结束的地址范围。引用同一个可执行模块中的任何符号都没有问题(假设它不超过4GB大小),但是这会给跨模块拦截带来问题。当然,我的可执行模块和我试图挂钩的模块不必落入相同的4GB范围,因此存在问题。

暂时我通过将无条件的jmp覆盖到我的代码中的原始例程prolog代码来解决这个问题。这在64位平台上占用12个字节。然后,为了从我的例程调用原始代码,我恢复被覆盖的12个字节(意味着 - 我在覆盖之前保存它们)。

到目前为止 - 没问题。但现在事情正在发生变化,我将不得不支持多线程访问入口点例程(请不要问为什么,它与多会话DLL相关,加载到所谓的“用户空间”中,分开对于每个终端会议)。

其中一个解决方案是使用全局锁定,但我想避免这种情况。

我知道所谓的“蹦床功能”,但我也想避免这种情况。这样做需要对函数prolog代码进行运行时解码,以正确识别指令边界和可能的分支。

最近我想到了另一个想法。如果我能找到原始DLL的一些“不需要的”部分,该部分长度至少为12个字节(mov RAX addr + jmp RAX的大小),该怎么办?然后这部分可以被jmp覆盖到我手中。然后可以将入口点RVA设置为此部分!

此操作所需的只是可以覆盖的适当部分。我认为存在这样的可能性,因为PE头包含许多历史字段,这些字段几十年来不再使用。

这个想法值得尝试,还是这是一个众所周知的技术?安迪还有其他建议吗?

提前致谢。

1 个答案:

答案 0 :(得分:2)

您有几种选择。不幸的是,你只能选择这3个中的2个:100%固体;易于实施;便宜。

很有可能在.TEXT部分结束时你会发现未使用的空间。这是因为Windows将图像部分以4k的块的形式映射到内存中,并且.text部分通常不是精确的乘法。

另一个易于实现的是使用PE头。一个非常安全的覆盖区域是DOS存根。问题在于,不能保证PE头与入口例程在同一部分(微软链接器将它放在同一部分,但不了解GNU或其他部分)。

另一个容易但仅适用于系统DLL的工作是执行'Hot Patching'正在做的事情,并在每个函数前面重用15个字节设置为'nop',以及'mov edi,edi'指令。所有与Windows一起发布的DLL都支持Hot Patching。

可靠而艰难的选择是做@David Heffeman的建议。这种技术称为“着陆功能”,您将前12个字节复制到着陆功能中,然后jmp到原始功能。

简单可靠的选择是使用MS Detour。 Microsoft Detour是微软研究院的一款产品,它可以做到这一点并且运行良好,并且受到支持,并且它可以处理可能弹出的一堆极端情况和竞争条件(以及其他内容),并且其x86版本是开放的资源。缺点是商业用途非常昂贵 - 上次我检查它是10k。