4.1 技术原理 & 4.2 键盘过滤框架

4.1 预备知识

符号链接:符号链接其实就是一个“别名”。可以用一个不同的名字来代表一个设备对象(实际上),符号链接可以指向任何有名字的对象。

ZwCreateFile是很重要的函数。同名的函数实际上有两个:一个在内核中,一个在应用层。所以在应用程序中直接调用CreateFile,就可以引发对这个函数的调用。
它不但可以打开文件,而且可以打开设备对象(返回得到一个类似文件句柄的句柄)。所以后面会常常看见应用程序为了交互内核而调用这个函数,这个函数最终调用NtCreateFile。

PDO:Phsiycal Device Object的简称(物理设备对象)。PDO是设备栈最下面的那个设备对象(不精确,但是很实在)

nt!IoGetAttachedDevice、nt!ObpCreatHandle等写法是在调试工具WinDbg中常常出现的表示方法。!号之前的内容表示模块名,而之后的内容表示函数名或变量名。比如 nt!IoGetAttachedDevice表示模块nt中的函数IoGetAttachedDevice。

4.1.2 Windows中从击键到内核

在任务管理器的进程列表中可以看到“csrss.exe”进程。这个进程很关键,它有一个线程叫做win32!RawInputThread,这个线程通过一个GUID(GUID_CLASS_KEYBOARD)获得键盘设备中PDO的符号链接名

应用程序是不能直接根据设备名字打开设备的,一般都通过符号链接名来打开。

简单地说,win32k!RawInputThread线程总是nt!ZwReadFile要求读入数据,然后等待键盘的键被按下。当键盘上的键被按下时,win32k!RawInputThread处理nt!ZwReadFile得到的数据,然后nt!ZwReadFile要求读入数据,再等待键盘上的键被按下。

我们一般看到的PS/2键盘的设备栈,如果自己没有另外安装其他键盘过滤程序,那么设备栈的情况是这样的:
** 最顶层的设备对象是驱动 Kdbclass 生成的设备对象。
** 中间层的设备对象是驱动i8042prt生成的设备对象。
** 最底层的设备对象是驱动ACPI生成的设备对象。
这里只需要知道,接下来要找的是驱动Kdbclass的设备对象。

4.1.3 键盘硬件原理

从键盘被敲到计算机屏幕上出现一个字符,中间有很多复杂的变换。这里需要了解一些知识。
** 键并不是用字符代表,而是给每个键规定了一个扫描码。
** 键盘和CPU的交互方式是中断和读取端口,这个操作是串行的。一次中断发出就等于键盘给CPU发送一个通知,这个通知只能通知一个事件:某个键被被按下,某个键弹起。CPU只能接收通知并读取端口的扫描码,从不主动去“查看”任何键。
** 网上资料查到:如果一个键按下的扫描码为 X,则同一个键弹起的扫描码为 X+0x80。
** Windows XP 下端口和中断号都是定死的,即中断号为0x93,端口号为0x60。每次中断发送,CPU都去读取端口0x60中的扫描码,0x60中只能保存一个字节,但是扫描码是可以有两个字节的,此时会发生两次中断,CPU会先后读取扫描码的两个字节

请注意:比如“同时按下两个键”之类的事情在这种机制下是不可能发生的。无论如何按键,信息的传递都是一次一个字节串行发生的。

4.2 键盘过滤的框架
4.2.1 找到所有的键盘设备

打开驱动对象KdbClass,然后绑定它下面所有的设备。

找驱动下的所有对象:
(1)DRIVER_OBJECT下的DeviceObject,驱动下的所有设备对象都在这个设备链中,可以通过链节点中的域NextDevice依次进行遍历。
(2)调用IoEnumerateDeviceObjectList,这个函数可以枚举出一个驱动下的所有设备。

这里的新函数——ObReferenceObjectByName,它用来通过一个名字获得一个对象的指针。

4.2.2 应用设备扩展

在生成一个过滤设备时,我们可以给这个设备指定一个任意长度的“设备扩展”,这个扩展中的内容可以任意填写,作为一个自定义的数据结构。

这样就可以把真实设备的指针保存在设备对象里了,就没有必要做两个数组去对应起来,每次都要找一番。

在键盘过滤中,作者专门定义了一个结构作为设备扩展如下:

typedef struct _C2P_DEV_EXT
{
// 这个结构的大小
ULONG NodeSize;
// 过滤设备对象
PDEVICE_OBJECT pFilterDeviceObject;
// 同时调用时的保护锁
KSPIN_LOCK IoRequestsSpinLock;
// 进程间同步处理
KEVENT IoInProgressEvent;
// 绑定的设备对象
PDEVICE_OBJECT TargetDeviceObject;
// 绑定前底层设备对象(真实设备)
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, *PC2P_DEV_EXT;

要生成一个带有设备扩展信息的设备对象,关键是在调用IoCreateDevice时,注意第二个参数填入扩展的长度。比如前面的例子中生成过滤设备时,所用的代码是:

status = IoCreateDevice(
IN DriverObject,
IN sizeof(C2P_DEV_EXT), //扩展的长度
IN NULL,
IN pTargetDeviceObject->DeviceType,
IN pTargetDeviceObject->Characteristics,
IN FALSE,
OUT &pFilterDeviceObject
);

扩展域的填写函数如下:

NTSTATUS
c2pDevExtInit(
    IN PC2P_DEV_EXT devExt,
    IN PDEVICE_OBJECT pFilterDeviceObject,
    IN PDEVICE_OBJECT pTargetDeviceObject,
    IN PDEVICE_OBJECT pLowerDeviceObject )
{
    memset(devExt, 0, sizeof(C2P_DEV_EXT));
    devExt->NodeSize = sizeof(C2P_DEV_EXT);
    devExt->pFilterDeviceObject = pFilterDeviceObject;
    KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
    KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent,     FALSE);
    devExt->TargetDeviceObject = pTargetDeviceObject;
    devExt->LowerDeviceObject = pLowerDeviceObject;
    return( STATUS_SUCCESS );
}    

4.2.3 键盘过滤模块的DriverEntry
与串口的差别就是,这里需要关系键盘的移除。

NTSTATUS DriverEntry(
                     IN PDRIVER_OBJECT DriverObject,
                     IN PUNICODE_STRING RegistryPath
                     )
{
    ULONG i;
    NTSTATUS status;
    KdPrint (("c2p.SYS: entering DriverEntry\n")); 

    // 填写所有的分发函数的指针
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
        DriverObject->MajorFunction[i] = c2pDispatchGeneral;
    } 

    // 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
    // 其他的都不重要。这个分发函数单独写。
    DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead; 

    // 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
    // 一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
    DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower; 

    // 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
    // 被拔掉了?)所以专门写一个PNP(即插即用)分发函数
    DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP; 

    // 卸载函数。
    DriverObject->DriverUnload = c2pUnload;
    gDriverObject = DriverObject;
    // 绑定所有键盘设备
    status =c2pAttachDevices(DriverObject, RegistryPath);

    return status;
}

4.2.4 键盘过滤模块的动态卸载

与串口过滤稍有不同的是,键盘总是处于“有一个读请求没有完成”的状态。
键盘上有键被按下——》触发中断——》键盘驱动从端口读取扫描码——》从键盘得到的数据交给IRP-——》结束这个IRP——》导致win32k!RawInputThread线程对这个读操作的等待结束——》处理数据,处理完成后——》调用nt!ZwReadFile,开始新的等待。

防止未决请求没有完成的方法就是使用gC2pKeyCount。gC2pKeyCount在这里是一个全局变量,每次有一个读请求到来的时候,gC2pKeyCount被加1;每次完成时,则减1。于是只有所有请求都完成后,才结束等待;否则无休止的等待下去。

实际上,只有一个键被按下时,这个卸载过程才结束。gC2pKeyCount的运算在下面的4.3节“键盘过滤的请求处理”中会看到。

#define  DELAY_ONE_MICROSECOND  (-10)
#define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)

VOID
c2pUnload(IN PDRIVER_OBJECT DriverObject)
{
    PDEVICE_OBJECT DeviceObject;
    PDEVICE_OBJECT OldDeviceObject;
    PC2P_DEV_EXT devExt; 

    LARGE_INTEGER    lDelay;
    PRKTHREAD CurrentThread;
    //delay some time
    lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
    CurrentThread = KeGetCurrentThread();
    // 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
    KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);

    UNREFERENCED_PARAMETER(DriverObject);
    KdPrint(("DriverEntry unLoading...\n")); 

    // 遍历所有设备并一律解除绑定
    DeviceObject = DriverObject->DeviceObject;
    while (DeviceObject)
    {
        // 解除绑定并删除所有的设备
        c2pDetach(DeviceObject);
        DeviceObject = DeviceObject->NextDevice;
    }
    ASSERT(NULL == DriverObject->DeviceObject);

    while (gC2pKeyCount)
    {
        KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
    }
    KdPrint(("DriverEntry unLoad OK!\n"));
    return;
} 
时间: 2024-10-17 11:58:14

4.1 技术原理 & 4.2 键盘过滤框架的相关文章

键盘过滤驱动

在笔者接触驱动到如今以来一以后大半个月的时间,从中让我深深的体会到了万事开头难,以及学习持之以恒的重要性.笔者也是个驱动新人,開始接触驱动的时候看着张帆的<Windows驱动开发技术具体解释>讲的挺细,对新手来说是个不错的学习资料,可是更重要的还是自己要多动手练习,笔者在学习到同步操作的相关知识的时候,实在是看天书.最后还是放弃了学习本书.再找了本楚狂人的资料学习,感觉本书对新手来说还是比較吃力的,当中笔者就是这样,非常多知识点不是非常明确,仅仅能凭借自己的感觉去做,只是造成的后果就是无情的蓝

一台电脑控制多台手机技术原理分析

奥创软件研究院是首家研发电脑批量控制手机的软件研发机构,现在简单跟大家分析下电脑批量控制手机的原理. 现在电脑控制手机有多种方式,云端,WIFI ,以及USB 连接,云端的话,其实就是云端向手机里面的APP发送指令,这样无法及时的跟进手机执行任务的情况,所以奥创软件研究院不在这里多介绍,这里为大家介绍一台电脑控制27台,最高控制127台的技术原理,希望能够帮助到大家理清楚电脑控制手机的一些基本原理. 可能有人会疑问,为什么最多是127台呢?因为当时电脑主板的USB 接口在设计出来的时候,没想到会

虚拟化技术原理(CPU、内存、IO)

本文来自:http://www.ywnds.com/?p=5856 虚拟化 云计算现在已经非常成熟了,而虚拟化是构建云计算基础架构不可或缺的关键技术之一. 云计算的云端系统, 其实质上就是一个大型的分布式系统. 虚拟化通过在一个物理平台上虚拟出更多的虚拟平台, 而其中的每一个虚拟平台则可以作为独立的终端加入云端的分布式系统. 比起直接使用物理平台, 虚拟化在资源的有效利用. 动态调配和高可靠性方面有着巨大的优势. 利用虚拟化, 企业不必抛弃现有的基础架构即可构建全新的信息基础架构,从而更加充分地

安卓手机屏幕投射到电脑以及一台电脑控制多台手机技术原理浅析

奥创软件研究院推出的奥创微群控让越来越多的人了解到了电脑控制手机的操作.自奥创软件研究院首家发布电脑批量控制手机的解决方案以来,有很多人开始探讨电脑控制手机技术在实际工作中的应用. 由于市场太大,仅靠奥创软件研究院一家也是做不过来的,现在将手机屏幕投射到到电脑的技术原理,以及一台电脑批量控制多台手机的技术(即所谓的手机反响控制)简单的给大家介绍下,在此抛砖引玉,希望能给大家一些思路上的指导. 说到安卓手机的屏幕投射,就不得不说安卓的adb,ADB的全称为Android Debug Bridge这

EEPlat PaaS 总体方案及技术原理

EEPlat PaaS平台提供了基于元数据驱动的以配置为主的快速开发平台,同时提供了完整的多租户实现,各租户拥有自己的独立应用和数据库,租户间实现了应用和数据的完全隔离. EEPlat PaaS平台包括四大子系统:多租户管理子系统.企业应用后台配置开发环境.元数据驱动引擎.企业应用分发管理子系统等. 核心原理流程图 EEPlat PaaS平台的原理,核心是元数据和执行引擎.通过后台配置开发环境定义元数据,再由元数据的执行引擎解析执行为相应的系统. EEPlat PaaS平台的原理图如下所示: 平

新手入门:史上最全Web端即时通讯技术原理详解

前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为流行,而网上关于原生IM(相关文章请参见:<IM架构篇>.<IM综合资料>.<IM/推送的通信格式.协议篇>.<IM心跳保活篇>.<IM安全篇>.<实时音视频开发>).消息推送应用(参见:<推送技术好文>)的通信原理介绍也较多

Web端即时通讯技术原理详解

前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为流行,而网上关于原生IM(相关文章请参见:<IM架构篇>.<IM综合资料>.<IM/推送的通信格式.协议篇>.<IM心跳保活篇>.<IM安全篇>.<实时音视频开发>).消息推送应用(参见:<推送技术好文>)的通信原理介绍也较多

基于HTTP协议之WEB消息实时推送技术原理及实现

很早就想写一些关于网页消息实时推送技术方面的文章,但是由于最近实在忙,没有时间去写文章.本文主要讲解基于 HTTP1.1 协议的 WEB 推送的技术原理及实现.本人曾经在工作的时候也有做过一些用到网页消息实时推送的项目,但是当时实现的都不是很完美,甚至有时候是通过 Ajax 轮训的方式实现.在网上也找过不少的资料,真正说到点子上的几乎没有,很多文章大都是长篇大论,说了一些新有名字,什么“HTTP 长连接”,“实时推送”,“Comet 长连接推送技术”等.但真正提到如何实现实时推送的文章倒是没有看

web网站加速之CDN(Content Delivery Network)技术原理(转载)

CDN技术原理(转载) 1.Author: 北方人 2.LastUpdate : 2014-08-25 15:13:33 3.原文地址:http://www.51know.info/system_performance/cdn/cdn.html 在不同地域的用户访问网站的响应速度存在差异,为了提高用户访问的响应速度.优化现有Internet中信息的流动,需要在用户和服务器间加入中间层CDN. 使用户能以最快的速度,从最接近用户的地方获得所需的信息,彻底解决网络拥塞,提高响应速度,是目前大型网站使