矛与盾:用VB打造驱动级键盘记录器,能过QQ密码框(源码) 收藏
信息监控与隐私保护永远是一对矛盾,在对付各种信息窃取软件上,新技术总是层出不穷。本文介绍一种古老的键盘记录器技术,确实很古老,DOS时代人们就在用了,但是现在它仍然很有效,在键盘过滤驱动失效的情况下,它仍然可用。但是兼容性不佳是它的一个问题,特别是在Windows Vista操作系统上,我不能保证它是可用的。不过在可用的情况下,它能记录我所找到的各种带保护的密码框,包括QQ2008的密码框。
在此之前你还可以看看我以前的一篇帖子,上面介绍了一些Windows键盘响应机制的内容,地址是:http://topic.csdn.net/t/20061224/14/5252514.html
现在的计算机键盘除了传统的PS/2键盘之外还有USB键盘,本文只介绍普通的PS/2键盘,因此本文的示例代码也只支持PS/2键盘,对USB键盘无效。我们知道计算机要得到键盘的信息,必须与键盘进行通信。计算机与外设的通信都是依靠I/O操作完成的,键盘也不列外,当键盘上一个键被按下后,键盘就需要发送一个信息到主机。主机怎么知道键盘什么时候发送了信息呢?有两种方式:轮询和中断。轮询就是说主机不停的询问键盘是否有新的信息,这是以前I/O操作最传统的方式。中断则是一种比较先进的方式,不是由主机来不停的询问键盘,而是由键盘主动告诉主机:有新的信息来了,这样主机就会执行一段中断处理程序,来处理键盘的数据。好了,那么Windows是用哪种方式处理键盘的呢?当然肯定是以中断方式的,因为这样主机不必不断轮询设备,大大提高了效率。事实上Windows中有一个键盘中断处理程序,一旦键盘中断产生后,该程序就会被执行,然后将处理后的数据提供给更高层次的程序使用。键盘中断处理程序是Ring0下的一段代码,在它之上还有更高层的键盘驱动程序,和其它更抽象的硬件管理类驱动程序,再往上走,到了Ring3层,也就是用户层,会首先由user32.dll处理,这样硬件事件会被编码为windows消息,这些消息再发给相应的应用程序。了解了这样一个层次关系后,你就可以很容易判断出一个键盘记录器处于哪一个层次,也就能知道它到底能多大程度上准确记录键盘操作而不被欺骗。这样看来,键盘记录器当然是越底层越好,可惜的是,越底层做起来也越难,当然是废话 ^_^ 现在我们知道Windows是靠键盘中断来处理输入的,也就是说,所有在Windows上运行的程序要感知到键盘输入,都是靠着那个中断,除非...
其实键盘中断是可以关掉的 ^_^ 如果关掉了的话,当键盘有键被按下,键盘仍然会将数据送到主机,但是中断不会被触发,也就是说,键盘中断处理程序不会执行,那么也就没人理会键盘了。所以尽管键盘仍然很尽责的传输按键数据,但是Windows说:谁理你啊...
我理,我理~! 该我们的代码上场了,前面说过了,除了中断,还可以用古老的轮询方式来获取键盘数据,所以,此时虽然中断被关掉,我们仍然可以轮询键盘的按键,这样我们就知道哪些键被按下了。而且很棒的是,因为此时中断已被关掉,你再也不用担心键盘中断处理程序会跟你抢数据,一个也不会漏掉~(当然,实际操作的时候也不能一直在那里轮询,不然cpu占用会暴走到100%的)!但是有个问题可能你已经注意到了,虽然我们的程序可以轮询键盘获得输入,但是其它的程序不行啊,它们依然眼巴巴的盼着Windows送来消息,所以结果是,虽然我们自己的程序现在可以感知键盘了,但是Windows的其它程序(包括内核里依赖键盘中断的其它驱动程序)都感知不到键盘,看上去好像键盘被我们的程序独占了一样 - - 天啦,这叫什么键盘记录器,太烂了...
额,等等,刚才你说什么来的? “看上去好像键盘被我们的程序独占了一样”?让我想一想,哈哈,这东东好像有不错的用处~~~ 看,安全密码框!有效防御键盘记录器!“国际先进”技术~ (就像QQ的键盘加密保护吹的那样- -) 让我试一下它的强度怎样吧,GetKeyState--无效(有效才怪);全局键盘钩子(消息钩子)--无效;DirectX--无效;键盘过滤驱动--无效;站在你背后偷窥-- 汗-_-| 哈哈,还不错吧,为什么键盘过滤驱动也会无效呢(那可是驱动级的记录器),因为过滤驱动位于键盘中断处理程序的上层,既然中断都关掉了,我想它也没什么好“过滤”了的吧。你会问,好吧,这个密码框还算安全,但是如果此时系统中有个记录器也是依靠轮询键盘来工作的呢,那不就都记下来啦。嗯嗯,这是一个问题,我们来解决它。怎么做呢,好办,当我们在轮询中取走键盘数据后,将键盘控制器的缓冲区清空,这样别的记录器就轮询不到啦,哈哈。
(在一边看了N久的)键盘记录器制作者说话了:别高兴地太早了,要知道键盘还支持一个指令&HFE(键盘指令),该指令可以让键盘重新发送上一次的数据给主机,本来是用于接收中出现错误时重传数据的。但我也可以利用该指令在你清空缓冲区后让键盘重传数据,这样你的保护就失效啦 (我得意的笑,我得意的笑~)
安全密码框制作者:难不倒我,键盘控制器还支持一条环回指令&HD2(控制器指令),该指令可以直接将发送给键盘的数据回传到输入缓冲器(意思就是说,可以将输出缓冲器的数据“复制”到输入缓冲器。再简单点就是可以发送一个数据到控制器,然后控制器将这个数据模拟成是从键盘发出的。网上那么多通过IO操作来模拟键盘按键的方法,其实就是利用了这条指令:通过该指令可以将一个数据模拟成是从键盘发出的,也就是模拟按键啦。鼠标也支持一条环回指令&HD3,可以模拟鼠标操作),我在取走键盘端口的数据后,再用&HD2指令回传一条值为0的数据,这样键盘“上一次”发送给主机的数据也就变成了0,再使用&HFE重发数据也没用啦... (记录器大叫一声,昏倒在地,良久不醒...)
到这里,下面将要给出的示例代码之一的“安全密码框”的原理也就基本上明了了。先关闭键盘中断,然后采用轮询的办法获得键盘输入,在每一次轮询中一旦取走了键盘数据立刻向键盘控制器发送&HD2指令清除上一次的按键信息,这样就可以有效防止别的记录器也采用轮询的办法来记录键盘。同时,由于关闭了键盘中断,Windows中所的程序都无法感知到键盘输入,无论全局HOOK还是别的什么,统统失效。所以,这个密码框的保护强度还是很不错的。从键盘的角度说,QQ的键盘加密保护也是采用了类似的方法,不过又稍有不同。QQ的键盘加密技术并没有关闭键盘中断来轮询,而是采用了另一个办法:替换掉系统默认的键盘中断处理程序,换成了QQ自己的键盘中断处理,这个QQ的中断程序并不会把键盘消息发送给Windows的上层服务,而是私自处理了这些消息,将用户的键盘输入加密,然后将密文直接传递给了QQ,因此任何位于上层的键盘记录器都记录不了QQ的密码输入啦。QQ2008早期版本的键盘加密驱动在取走键盘端口的数据后,同样会有一个清空键盘输入缓冲区的操作,让别的程序即使轮询也轮询不了。但是那个版本的兼容性却不太好,特别是导致使用USB键盘的用户经常蓝屏,腾讯后来改进了保护方式。在QQ2008的后期版本中,键盘加密的稳定性提高了,但是保护强度却降低了,并且也没有了清空输入缓冲区的动作,而是改用另一个思路:通过不断注入垃圾字符来对抗键盘记录器。但是这样的话使用普通轮询的方法也可以截获到用户的输入了,这也许是腾讯在保护强度和稳定性之间做出的一个折中。好啦,还有个问题,你用上述原理做了个安全密码框,在密码框中倒是可以输入了,但是你关了程序以后,发现键盘失效啦,Windows所有程序都对键盘一点反应也没有 - - 哈哈,刚才说啦,键盘中断不是被关闭了吗,Windows靠中断来感知键盘,既然关掉了,当然键盘就不灵了,所以密码框在实际制作的时候应该这样:当密码框获得输入焦点的时候才关闭键盘中断,当密码框失去焦点时应该立刻重新打开键盘中断,当窗体失去焦点时也应该开中断,当登录完成或程序结束时都要打开中断,不然别的程序就不能感知键盘了。另外,轮询键盘端口得到的数据都是键盘扫描码,你还需要转换成虚拟键码和对应的ASCII字符才行的。
说完了安全密码框,现在也来说说利用这个原理怎么制作键盘记录器,好在事物都是两面的。我的思路是这样:可以简单轮询,就是设置一个定时器,每30毫秒读取一次键盘数据端口,就可以知道有没有键被按下了。这个方法对大多数程序有效,对现在版本的QQ2008也有效(上面说过现在它的保护强度降低了嘛)。但是对于早期版本的QQ2008是无效的,因为那时的QQ键盘加密有个动作,就是取完了数据后会将键盘端口清零。而且键盘中断的反应速度永远比用轮询的方法快(原因很明显,中断是由设备通知的,总是在“第一时间”被激活,而轮询则不是),所以轮询到的数据永远是被QQ清空后的零。而且简单轮询还有个缺点,你需要自己来通过别的寄存器状态识别轮询到的是键盘数据还是鼠标数据。因为,你大概已经知道,在PS/2设备中,键盘控制器和鼠标控制器是共用的,而且它们的输入缓冲区也是共用的,也就是说,键盘数据和鼠标数据是会混合在一起的,如果你只是简单轮询&H60端口的话,你就需要靠别的办法来区分键盘和鼠标数据了(在示例代码中,你将可以看到如何进行简单轮询)。现在我们使用一种改进的办法来轮询,它可以解决上述问题,并对早期版本的QQ2008也有效。思路是:首先关闭键盘中断,这时候QQ密码框也不会获得任何输入了,然后用轮询的方法判断键盘有没有输入。因为此时中断已被关掉,再也不会有谁跟我们抢数据了,所以获得的键盘输入总是“第一手的”。在本文的开头已经介绍了这个思路。下面的问题是,轮询完毕后,怎样让QQ也能获得输入(注意此时键盘中断已被关闭)?解决方法是打开中断,然后利用上面的&HFE指令或者&HD2指令。我们可以在每次轮询结束后,如果发现有按键消息,则打开键盘中断,然后用&HFE指令让键盘重发上一次的数据,或者用&HD2指令将我们轮询到的数据塞回输入缓冲区。注意实际测试时发现&HFE指令似乎不能再次激发键盘中断,所以我用&HD2来将我们取走的数据重新塞回去,这个操作可以同时激发一次键盘中断,由于此时中断已经打开,中断处理程序被激活,会取走我们塞回去的数据,这样QQ或者其他任何程序都能接收到键盘输入了。注意中断处理程序取走的可是我们塞回去的数据哦,实际上我们想塞进去几都可以,比如我轮询到的是A键被按下,但是我完全可以欺骗系统是B键被按下。所以,这个思路还有另一个用途,修改实际按键,而且该修改是很底层的,足以欺骗键盘中断处理程序和任何位于它上层的程序。好了,现在中断处理程序已经处理完毕,系统已经收到了按键消息(如果是QQ密码框,那么这个密码框也收到了按键),注意它们都是在我们的程序之后(并且经过我们程序的同意)才收到这一消息的,因此可以认为,键盘已经被我们的程序从较底层给HOOK了,接下来,再次关闭键盘中断,然后等待下一次轮询。可以看到,在中断关闭期间其它程序都得不到键盘消息,而中断打开时我们的程序已经处理完键盘数据,所以可以保证我们的程序总在“最前面”得到按键信息。你可以设置一个定时器,然后轮训间隔为30毫秒左右,时间不能太长,否则键盘记录器反应很慢,也不能太短,否则太占系统资源。你要问了,按照这个思路的话,每一秒钟键盘中断都要打开关闭好多次,对性能有影响吗?答案是肯定的,这个记录器确实会在一定程度上降低键盘的反应速度,不过它的性能比我预想的要好得多,实际测试时,对性能并没有太大影响,甚至不太感到它的存在 ^_^
最后,还有一些重要的东西没说到呢。比如怎样轮询键盘,这样关闭键盘中断?在这里我试图尝试一种只需要通过I/O操作就能完成上述任务的方法。轮询键盘,其实就是读取键盘控制器的数据,通过两个I/O端口来完成:&H64(控制端口)和&H60(数据端口)。从&H60端口读取就可以得到键盘(或鼠标)发送来的数据(实际读取的是输入缓冲区),读取该端口还会清零IBF(输入缓冲区满)标志,系统正是根据这个标志来触发键盘中断,读取&H64端口可以知道键盘控制器的状态。写入&H60端口会被写到输出缓冲区,通常是发送给键盘的数据。写入&H64端口则会被理解为向键盘控制器发送命令。命令分两种,键盘控制器命令和键盘命令,键盘控制器命令是操作控制器的,控制器也许需要再操作键盘才能完成此命令,但也可能不需要;键盘命令是直接发送给键盘的。发送控制器命令需要首先将控制字写入&H64端口,如果命令有参数,参数写入&H60端口,发送键盘命令的话,直接将控制字写入&H60端口就可以了,参数紧跟控制字也写入&H60端口。无论哪种命令,如果有什么返回数据的话,都通过&H60端口读取。下面列举一部分控制器命令和键盘命令。
控制器命令:
&HA1:获得版本号。 &HA4:获得密码。 &HA5:设置密码 &HA6:比较密码
&HA7:禁用鼠标 &HA8:启用鼠标 &HAD:禁用键盘 &HAE:启用键盘
&HD2:写键盘缓冲区的环回指令(模拟键盘可以用这个) &HD3:写鼠标缓冲区的环回指令(可以模拟鼠标操作)
键盘命令:
&HFF:复位键盘 &HFE:重新发送上一次的数据给主机 &HED:在此命令之后需要跟随一个参数字节,用于设置键盘上那3个指示灯的状态
那么现在知道怎样在控制器上直接关掉键盘中断了吧,只需要发送一个命令给控制器就可以了,具体的命令和参数可以参见最下面给出的示例代码。最后还有一个问题没解决:怎样才能直接读写键盘的端口?要知道在Windows中普通的应用程序是无权直接操作端口的,需要有Ring0的权限才行。这个问题的解放方式可以是很多的,我在示例代码中使用了一个开源的组件WinIO,WinIO是一个可以用来直接在应用程序中读取I/O端口和物理内存的组件,它被经常用在工控上操作IO卡什么的。要了解WinIO的详细用法可以在网上搜索此关键词。要注意一个问题是由于WinIO组件经常被用来干坏事,名气又那么大,现在已经是被不少安全类软件打入另册了。所以在实际实践中你不一定非要用它,只要能想办法直接读写端口就可以实现键盘记录了。
这时昏迷良久的记录器忽然醒了,对密码框大叫:有办法了,我可以HOOK掉你WinIO中的函数GetPortVal,这样就能在你之前截获到键盘输入了!
密码框:那我换一个其它组件来操作端口你不就失效了。而且这种办法也超出了记录器的讨论范围 - -
记录器:那我可以下I/O断点,这样就能断掉你的IO操作并抢在你前面截获键盘输入~
密码框:下断不是不能检测的,如果检测到被断则可以通知用户密码框保护已失效。并且这样做也很复杂,弄不好的话会极大降低系统稳定性,那样用户也能发现。
记录器:我还可以...
密码框:......
看来这两个永远是一对冤家了 ^_^
最后的最后,是我的示例代码的地址,它包含两个示例:键盘记录器和安全密码框,都是根据上述原理制成。(如果要在调试模式下运行此代码的话,需要把名称为winio的那几个文件复制到VB的安装目录下才行)你说,要是用你的矛戳你的盾会怎么样呢。哈哈,幸好WinIO永远不赞成这么做 ^_^
源码下载地址: http://www.pen88.com/download/keysafe.rar
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/rocLv/archive/2010/03/23/5405416.aspx