zoukankan      html  css  js  c++  java
  • [内核编程] 4.1 技术原理 & 4.2 键盘过滤框架

    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
    ")); 
    
        // 填写所有的分发函数的指针
        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...
    ")); 
    
        // 遍历所有设备并一律解除绑定
        DeviceObject = DriverObject->DeviceObject;
        while (DeviceObject)
        {
            // 解除绑定并删除所有的设备
            c2pDetach(DeviceObject);
            DeviceObject = DeviceObject->NextDevice;
        } 
        ASSERT(NULL == DriverObject->DeviceObject);
    
        while (gC2pKeyCount)
        {
            KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
        }
        KdPrint(("DriverEntry unLoad OK!
    ")); 
        return; 
    } 
  • 相关阅读:
    EXE、DLL和OCX文件的最佳压缩工具ASPack
    mysql忘记帐号密码 解决办法。
    vs2010 C++ 静态编译(解决:程序在别人的机子运行不了,缺少mfc100.dll, xxx100d.dll等的解决方法)
    去掉word每个标题前都有个小黑点 附word2003与2007方法
    struts2 中jsp页面replace的使用
    struts2 改变portlet windowState
    .net 知识补充 注意点
    广义表(1)
    字符串匹配(kmp)
    二叉排序树
  • 原文地址:https://www.cnblogs.com/fanling999/p/4068901.html
Copyright © 2011-2022 走看看