Windows键盘驱动结构与消息机制

转自 豆瓣

本文主要介绍按键消息是如何传递到窗口并转化为具体的按键消息的。


Windows系统是事件驱动的多任务系统,其中按键和鼠标是主要的事件。按键是由键盘驱动获得并转换,然后广播给各个窗口。

整个架构的核心是csrss.exe这个进程,对于“一般”的窗口,收到的消息都是由这个任务产生的。该任务负责用CreateFile方式打开键盘设备并读取信息,获得对应的键码并发送给特定的进程,csrss.exe的启动的输入线程为win32k!RawInputThread,具体要把键盘消息发送给哪个线程,以及如何发送,是由csrss来控制的。

所以,每个进程的MessageLoop实际上是从这里读取的,于是SetWindowHook之类的函数有些是挂到各个进程里,有些实际上是挂到了csrss里,达到监控键盘的目的。但对于加密的控件(如网银等),SetWindowHook不管用,但如果hook到RawInpuThread里,那么还是有可能读到信息的。

csrss访问的是键盘设备,具体这个设备是一个kbdclass设备,以下部分就是内核的代码空间了。

因为一个系统里可能不只一个键盘(同理,鼠标也是),所以需要一个统一的驱动去管理所有的键盘设备,这个驱动就是kbdclass.sys,键盘设备类驱动,对应的设备名是L"\\Driver\\Kdbclass",RawInpuThread实际上访问的是这个设备,而对于每个键盘,则是有各自的驱动,对于PS/2键盘是i8042prt.sys,对于USB键盘,这是另外的驱动,这些驱动称为port驱动,是真正的设备驱动,对应的设备是KeyboadClass0、KeyboardClass1等等(有几个键盘,数字就排到多少),RawInpuThread把请求发送到kbdclass,kbdclass把请求(IRP)pend到这一层,等待下层port驱动返回。

这里需要特别提出的一个函数是KeyboardClassServiceCallback,这个函数是kbdclass一层的callback,下层设备驱动的所有返回值都需要经过它。所以,如果钩子挂到这里,那么理论上所有的输入都可以被拦截的,已经亲自测试过网银控件的密码会在此被泄露,但QQ不会,QQ2013会启动失败,估计是检查了这个驱动。

顺便说一句,早期的QQ加密没那么恐怖,但它是启动一个线程不停的SetWindowHook,使得自己的hook永远在第一个,然后过滤掉所有的消息防止被人监听。后来的QQ可能是修改IDT里的键盘中断实现的。

当然病毒也可以通过filter驱动注入到kbdclass和port驱动之间,来实现监听键盘,这也是一种常见的情况。

所以,一个按键的消息产生流程如下:

1)硬件中断/硬件端口数据
//WinIO能模拟,或者修改IDT是在这一层
2)键盘Port驱动(USB or PS/2)
//Filter驱动在此
//KeyboardClassServiceCallback也在这一层被调用
3)kbdclass驱动
//处理键盘布局和键盘语言
4)Windows内核边界(zwCreate/zwReadFile)
----------------------(系统调用)----------------------
5)Windows内核边界(zwCreate/zwReadFile)
6)csrss.exe的win32k!RawInputThread读取,完成scancode和vk的转换
//SetWindowHook工作在这里(全局)
//kbd_event工作在这里
7)csrss.exe调用DispatchMessage等函数分发消息
//SetWindowHook工作在这里(进程)
//PostMessage和SendMessage在这里
8)各个进程处理消息

WinIO这个驱动比较特殊,它提供接口可以允许应用层直接写端口,但只能写PS/2端口,所以有些模拟按键程序通过WinIO来模拟按键。

对于标准的程序,PostMessage等函数可以完成模拟,但对于不太标准的软件,只能用kbd_event来模拟,但有些软件(如网银控件)就只能在更靠近内核的区域模拟了。

同时,有一种输入是很特殊的,就是DirectInput,这是DirectX提供的一种方法,大型游戏中很常见的用法,因为DirectInput的输入速度很快,绕过了消息层。但对于这种软件,在kbdclass一层甚至都无法模拟。目前还不确定DirectInput工作在哪一层,猜测可能是在kbdclass和port驱动之间,也许是一种filter驱动。对于这种输入方法,可以WinIO来模拟,但对于USB键盘则没有办法。

Windows提供了一套API:SendInput,这个驱动发送按键消息时有两种类型,一种是VK模式的,实际上跟kbd_event一样,工作在csrss这一层,而另一种是ScanCode模式,MSDN里有这样的描述:

Windows 2000/XP: Set the KEYEVENTF_SCANCODE flag to define keyboard input in terms of the scan code. This is useful to simulate a physical keystroke regardless of which keyboard is currently being used.

可以看出,这是能够模拟更底层的按键的,理论上说是可以模拟DirectInput的按键的,实际测试也是这样,但文档中没有说明在vista以后的版本是什么状态,所以暂时也无法知道在WIN7里的工作情况。

你可能感兴趣的:(Windows键盘驱动结构与消息机制)