您的位置:首页 > 其它

开发键盘过滤驱动实现模拟按键过程中遇到的问题

2006-11-24 17:34 375 查看
如何动态御载键盘过滤驱动

最近写个键盘过滤驱动,遇到的问题是动态御载后再有按键操作就会蓝屏,看了些资料终于明白了原因,写出来供大家参考,免得后来的朋友再重复这个郁闷的过程。

要做到动态御载键盘过滤驱动,明白其工作运行的原理是很重要的。首先必须要知道键盘过滤驱动是工作在异步模式下的,这一点很重要。为了得到一个按键操作,首先需要发送一个IRP_MJ_READ到驱动的设备栈,驱动收到这个irp会做什么样的处理呢?它会一直保持这个irp为pending未确定状态,因为其实现在一直没有按键操作,直到一个键被真正的按下,驱动此时就会立刻完成这个irp,并将刚按下的键的相关数据做为该irp的返回值。在该irp带着对应的数据返回后,操作系统将这些值传递给对应的事件系统来处理,然后做什么呢??系统紧接着又会立刻发送一个IRP_MJ_READ请求,等待下次的按键操作,重复以上的步骤。也就是说,任何时候设备栈底都会有一个键盘的IRP_MJ_READ请求处于pending未确定状态。这意味着只有在该irp完成返回,并却新的irp请求还未发送到的时候才会有一个很短暂的时间。由此我们想到,我们按照一般的方式动态御载键盘过滤驱动的时候,基本都是有IRP_MJ_READ请求处于pending未确定状态,而我们却御载了驱动,以后按键的时候需要处理这个irp却找不到对应的驱动当然会蓝屏。

以上分析了动态御载键盘过滤驱动蓝屏的原因,分析到了中间存在一个短暂的时间栈底是没有irp的,那么让我们想办法来解决它。

网上一份e文资料显示,只有使用IoAttachDevice挂接/Device/KeyboardClass0(or others)才可以动态御载,而加载到UpperFilters的却不能。以下是该段的原文:

This problem occurs in most of the keyloggers based on the sysinternals Ctrl2cap model, which has been widely adopted and adapted. It applies to the earlier versions of the filter, which manually attach to /Device/KeyboardClass0 (or others) using IoAttachDevice. (If you install your filter by adding it to the UpperFilters value of HKCR/CCS/Control/Class/{4D36E96B-E325-11CE-BFC1-08002BE10318} key as more recent versions of Ctrl2cap do, you just can't unload: your filter is wedged into the stack for as long as the system is running and can't get out. But if you wedge by IoAttachDevice, unloading is still possible in theory.)。

让我们再深入分析下蓝屏的原因,栈底有irp为什么我们的驱动御载就会有问题呢?这是由于IRM_MJ_READ是异步的,对于异步的请求,基本上我们会关心这个异步请求的结果,如何得到完成后的数据呢?大家一定想到了,设置完成例程。对,就是这样,由于我们给IRP_MJ_READ设置了完成例程,该irp完成后会调用我们的完成例程,使我们有处理返回数据的机会。在这样的情况下,我们动态御载了键盘过滤驱动,也就是说完成例程已经被我们御载掉了,而以后的再次按键在完成这个irp后会调用这个根本已经不存在了的东东,结果蓝屏就可想而知了。

那是否是不设置完成例程就不会有问题了呢?答案是肯定的。可是没有完成例程我们就没有办法处理到返回的数据,也就在很大程度上失去了键盘过滤驱动的作用了。如何做到既能设置完成例程来处理数据又可以实现动态的御载呢?这里我们这样想,当有IRP_MJ_READ到来的时候,我不为这个irp设置完成例程,也不将该irp向下传递,而是创建一个我自己的irp,并参考前面的IRP_MJ_READ做对应的设置,然后为我自己的这个irp设置完成例程后将我的irp向下传递,并设置原来的IRP_MJ_READ为pending状态。当有按键操作时,我的irp返回触发为它设置的完成例程,在这里取得返回的数据填充前面的IRP_MJ_READ后将该IRP_MJ_READ完成返回。相当于我们使用了一个代理,而这一切都是透明的。到这里我们实现了完成例程,也就是有了处理数据的机会。下面该说到重点了,那就是这样处理后又如何实现动态的御载呢?

假设现在我们收到御载的请求,让我们看看当前所有的irp处于何种状态:
(1)一个我们保存的原本的IRP_MJ_READ处于pending,注意它并没向下传递,也未设置完成例程。
(2)一个我们自己构造的irp处于栈底,并注意我们为自己的这个irp设置了完成例程。
基本就这2个irp,由于我们自己的irp有完成例程,所以直接御载会出现和上面一样的情况,导致蓝屏。如何处理呢?这里注意到是我们自己构造的irp,所以我们可以将其取消,这样并不会有太大的影响。可是取消后栈底本来该有的IRP_MJ_READ就没有了,注意到(1),我们不是还有原来的IRP_MJ_READ吗?对,就是将原来的这个IRP_MJ_READ向下传递,这里千万注意,我们自己的驱动马上要御载,所以我们传递原来的IRP_MJ_READ的时候不要给它设置完成例程。向下传递后御载我们的驱动。哈哈,成功!!!



当然,这里还有更简单的办法,使用计数器也可以实现,还简单的多:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: