2012-10-27 44 views
1

要简短的问题是这样的。我正在编写一个内核模式的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标题包含很多几十年来不再使用的历史字段。

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

在此先感谢。

+0

你为什么不使用蹦床? –

+0

这听起来像一个rootkit - 不像我说的那样。 – Linuxios

+0

@Linuxios:希望不是:) – valdo

回答

2

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

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

另一个容易实现的就是使用PE头。一个非常安全的区域是DOS存根。问题在于不能保证PE头与入口例程位于同一部分(Microsoft链接器将它放在同一节中,但不知道GNU或其他)。

另一个简单但只适用于系统DLL的工作是做'热修补',并在每个函数前面重复使用15个字节设置为'nop','mov edi,edi'指令。所有与Windows一起发布的DLL都支持Hot Patching。

可靠但很难的选择是做@David Heffeman的建议。这种技术被称为“登陆功能”,你可以将前12个字节复制到登陆功能中,然后跳转到原来的功能。

简单而可靠的选择是使用MS Detour。 Microsoft Detour是微软研究院的一款产品,它的确如此,并且效果很好,并且得到了支持,并且它处理了可能会出现的一系列角落案例和竞争条件(以及其他内容),并且其x86版本已打开资源。缺点是商业用途非常昂贵 - 上次我检查它是10k。