KbdClass键盘过滤驱动以及书中几处错误

《寒江独钓》这本书中的键盘过滤驱动例子中,照着书上的例子抄完,代码有些错误的地方!

书上的例子有3处错误,分别如下:

1)对象类型声明错误

extern POBJECT_TYPE IoDriverObjectType;

修改为以下正确方式

extern POBJECT_TYPE *IoDriverObjectType;

在使用的*IoDriverObjectType地方,也需要修改成正确的方式

status = ObReferenceObjectByName (
        &uniNtNameString,
        OBJ_CASE_INSENSITIVE,
        NULL,
        0,
        *IoDriverObjectType, // 这里由IoDriverObjectType改成*IoDriverObjectType
        KernelMode,
        NULL,
        &KbdDriverObject
        );

2)解除对象引用计数,传入了错误的参数,应该传入KbdDriverObject

ObDereferenceObject(DriverObject);

改为

ObDereferenceObject(KbdDriverObject);

3)绑定过滤设备对象后,过滤设备对象的StackSize不需要加1

pFilterDeviceObject->StackSize  = pLowerDeviceObject->StackSize+1;

msdn对于设备对象的StackSize字段解释中,第二句话指明了当使用IoAttachDeviceIoAttachDeviceToDeviceStack绑定一个设备对象时,IO管理器会自动绑定的设备对象设置合适的StackSize值,只有当前面是使用IoGetDeviceObjectPointer获取的设备对象,才需要显示声明它的设备对象StackSize+1

Specifies the minimum number of stack locations in IRPs to be sent to this driver. IoCreateDevice and IoCreateDeviceSecure set this member to 1 in newly created device objects;

lowest-level drivers can therefore ignore this member. The I/O manager automatically sets the StackSize member in a higher-level driver’s device object to the appropriate value if the driver calls IoAttachDevice or IoAttachDeviceToDeviceStack.

Only a higher-level driver that chains itself over another driver with IoGetDeviceObjectPointer must explicitly set the value of StackSize in its own device object(s) to 1 + the StackSize value of the next-lower driver’s device object.

在调试时发现,刚创建的pFilterDeviceObject过滤设备对象,它的StackSize1
在这里插入图片描述
经过IoAttachDeviceToDeviceStack函数绑定设备对象后,pFilterDeviceObject.StackSize被IO管理器设置成了4,而pLowerDeviceObject->StackSize此时是3
在这里插入图片描述
所以根本不需要再执行这一行代码:pFilterDeviceObject->StackSize = pLowerDeviceObject->StackSize+1;

最终键盘过滤效果:输入test
在这里插入图片描述
最终完整代码:

#include <ntifs.h>
#include <ntstrsafe.h>
#include <ntddkbd.h>

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


// IoDriverObjectType实际是一个全局遍历,但是头文件中没有,只要声明之后就可以使用了
extern POBJECT_TYPE *IoDriverObjectType;


// KbdCLass 驱动的名字
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"


// 全局变量键盘计数
ULONG gC2pKeyCount = 0;


unsigned char asciiTbl[] = {
    0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09,    //normal
    0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x0D, 0x00, 0x61, 0x73,
    0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x7A, 0x78, 0x63, 0x76,
    0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
    0x32, 0x33, 0x30, 0x2E,
    0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09,    //caps
    0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x5B, 0x5D, 0x0D, 0x00, 0x41, 0x53,
    0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x5A, 0x58, 0x43, 0x56,
    0x42, 0x4E, 0x4D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
    0x32, 0x33, 0x30, 0x2E,
    0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09,    //shift
    0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x0D, 0x00, 0x41, 0x53,
    0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x5A, 0x58, 0x43, 0x56,
    0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
    0x32, 0x33, 0x30, 0x2E,
    0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09,    //caps + shift
    0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x7B, 0x7D, 0x0D, 0x00, 0x61, 0x73,
    0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x7A, 0x78, 0x63, 0x76,
    0x62, 0x6E, 0x6D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,
    0x32, 0x33, 0x30, 0x2E
};



// 这个函数是存在的,只是文档中没有公开, 声明一下就可以直接使用了
// 这个函数是事实存在的,只是文档中没有公开。声明一下
// 就可以直接使用了。
NTSTATUS
ObReferenceObjectByName(
    PUNICODE_STRING ObjectName,
    ULONG Attributes,
    PACCESS_STATE AccessState,
    ACCESS_MASK DesiredAccess,
    POBJECT_TYPE ObjectType,
    KPROCESSOR_MODE AccessMode,
    PVOID ParseContext,
    PVOID* Object
);
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;


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;
    devExt->TargetDeviceObject = pTargetDeviceObject;
    devExt->LowerDeviceObject = pLowerDeviceObject;


    KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
    KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);


    return STATUS_SUCCESS;
}


// 处理普通请求
NTSTATUS c2pDispatchGeneral(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    // 其他的分发函数直接skip,然后用IoCallDriver把IRP发送到真是设备的设备对象
    KdPrint(("Other Dispatch!"));
    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(((PC2P_DEV_EXT)DeviceObject->DeviceExtension)->LowerDeviceObject, Irp);
}


// 处理Power请求
NTSTATUS c2pPower(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PC2P_DEV_EXT devExt;
    devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
    PoStartNextPowerIrp(Irp);
    IoSkipCurrentIrpStackLocation(Irp);
    return PoCallDriver(devExt->LowerDeviceObject, Irp);
}


// 处理PnP请求
NTSTATUS c2PnP(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{


    // 唯一需要处理的是,当有一个设备被拔出时,解除绑定,并删除过滤设备
    PC2P_DEV_EXT devExt;
    PIO_STACK_LOCATION irpStack;
    NTSTATUS status = STATUS_SUCCESS;
    KIRQL oldIrql;
    KEVENT event;


    // 获取真实设备
    devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;
    irpStack = IoGetCurrentIrpStackLocation(Irp);


    // 对于其他类型的IRP,全部下发即可
    IoSkipCurrentIrpStackLocation(Irp);
    status = IoCallDriver(devExt->LowerDeviceObject, Irp);


    if (irpStack->MinorFunction == IRP_MN_REMOVE_DEVICE)
    {
        KdPrint(("IRP_MN_REMOVE_DEVICE \n"));
        // 然后解除绑定,并删除我们自己生成的虚拟设备
        IoDeleteDevice(devExt->LowerDeviceObject);
        IoDeleteDevice(DeviceObject);
        status = STATUS_SUCCESS;
    }
    return status;
}


// 键盘状态
#define S_SHIFT 1
#define S_CAPS  2
#define S_NUM   4


// 这是一个标记用来保存当前键盘状态,其中3个位,分别表示Caps Lock、 Num Lock、和Shift键是否按下了
static ULONG g_kbStatus = S_NUM;


// 从扫描码到ASCII
VOID MyPrintKeyStroke(UCHAR sch)
{
    UCHAR ch = 0;
    ULONG off = 0;


    if ((sch & 0x80) == 0) // 如果是按下
    {
        // 如果按下了字母或者数字等可见字符
        if ((sch < 0x47) || (sch >= 0x47 && sch < 0x54) && (g_kbStatus & S_NUM))
        {
            // 最终得到哪个字符必须由Caps Lock 和 Num Lock 以及Shift 这几个键的状态来决定,所以写在一张表中
            ch = asciiTbl[off + sch];
        }


        switch (sch)
        {
            // Caps Lock 和 Num Lock键类似,都是按下2次 等于没按过意义的反复键,所以这里用异或来这是标志。
        case 0x3A:
            g_kbStatus ^= S_CAPS;
            break;


            // 注意Shift的特点:
            // 1) Shift键有两个,左右各一个,扫描码不同
            // 2) Shift键是按下起作用, 谈起则作用消失
        case 0x2A:
        case 0x36:
            g_kbStatus |= S_SHIFT;
            break;
        case 0x45:
            g_kbStatus ^= S_NUM;
        }
    }
    else // break
    {
        // 按键弹起时,如果是shfist
        if (sch == 0xAA || sch == 0xB6)
            g_kbStatus &= ~S_SHIFT;
    }


    if (ch >= 0x20 && ch < 0x7F)
    {
        KdPrint(("%C \n", ch));
    }


}


// 处理Read请求完成回调函数
NTSTATUS c2pReadComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    // 读请求完成之后,应该获得输出缓冲器,按键信息就再输出缓冲区中,全局变量gC2pKeyCount应该减1,
    PIO_STACK_LOCATION IrpSp;
    ULONG ulTotalKeys = 0;
    PKEYBOARD_INPUT_DATA pKeyData = NULL;
    SIZE_T i;


    IrpSp = IoGetCurrentIrpStackLocation(Irp);


    // 假设这个请求是成功的,如果失败了,进一步获取信息是没有意义的
    if (NT_SUCCESS(Irp->IoStatus.Status))
    {


        // 获得读请求完成后的输出缓冲区
        pKeyData = Irp->AssociatedIrp.SystemBuffer;


        // 缓冲区中存在的按键数量
        ulTotalKeys = Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);


        // 这里可以做进一步处理,笔者只是很简单的打印出所有的扫描码
        for (i = 0; i < ulTotalKeys; i++)
        {
            // 下面打印按键的信息
            KdPrint(("TotalKeys: %u ", ulTotalKeys));


            // MakeCode就是扫描码
            KdPrint(("ScanCode: %#X ", pKeyData->MakeCode));


            // Flags值有很多种,这里只考虑按下和弹起两种情况,KEY_MAKE 表示按下、KEY_BREAK表示弹起
            KdPrint(("%s\n", pKeyData->Flags ? "Up" : "Down"));


            // 将扫描码,传入打印函数
            MyPrintKeyStroke((UCHAR)pKeyData->MakeCode);


            // 拦截实验,修改按下的Caps Lock,为Control






        }
    }


    gC2pKeyCount--;


    if (Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }
    return Irp->IoStatus.Status;
}


// 处理Read请求
NTSTATUS c2pDispatchRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS status = STATUS_SUCCESS;
    PC2P_DEV_EXT devExt;
    PIO_STACK_LOCATION currentIrpStack;
    KEVENT waitEvent;
    KeInitializeEvent(&waitEvent, NotificationEvent, FALSE);
    if (Irp->CurrentLocation == 1)
    {
        ULONG ReturnedInformation = 0;
        KdPrint(("Dispatch encountered bogus current location \n"));
        status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = ReturnedInformation;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }


    // 全局变量键盘计数器加1
    gC2pKeyCount++;


    // 得到设备扩展,目的时为了获得下一个设备的指针
    devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension;


    // 设置回调函数并把IRP传递下去。 之后的读处理也就结束了 剩下的任务就是要等待读请求完成
    currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
    IoCopyCurrentIrpStackLocationToNext(Irp);
    IoSetCompletionRoutine(Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE);
    return IoCallDriver(devExt->LowerDeviceObject, Irp);
}


// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    KdPrint(("Driver unloaded!!!\n"));


    // 把当前线程设置为低实时模式,以便让它的运行尽量的少影响其他程序
    PRKTHREAD CurrentThread = KeGetCurrentThread();
    KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);


    // 遍历我们驱动对象所创建的所有filter设备对象
    PDEVICE_OBJECT pDeviceObj = pDriverObject->DeviceObject;
    while (pDeviceObj)
    {
       
        // 解除绑定并删除所有的设备, 例如:我们绑定的是键盘驱动的1号设备对象,现在就需要detach(键盘驱动的1号设备对象),然后把绑定对应的filter对象也删掉
        PC2P_DEV_EXT devExt = (PC2P_DEV_EXT)pDeviceObj->DeviceExtension;
        IoDetachDevice(devExt->TargetDeviceObject);
        IoDeleteDevice(pDeviceObj);
         // 拿到下一个设备对象
        pDeviceObj = pDeviceObj->NextDevice;

    }


    ASSERT(NULL == pDriverObject->DeviceObject);


    // delay some time
    LARGE_INTEGER lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);


    while (gC2pKeyCount)
    {
        KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
    }


    KdPrint(("DriverEntry unload ok!\n"));
}


// 这个函数经过改造,能打开驱动对象kbdCLass,然后绑定它下面所有设备
NTSTATUS c2pAttachDevices(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    NTSTATUS status = 0;
    UNICODE_STRING uniNtNameString;
    PC2P_DEV_EXT devExt;
    PDEVICE_OBJECT pFilterDeviceObject = NULL;
    PDEVICE_OBJECT pTargetDeviceObject = NULL;
    PDEVICE_OBJECT pLowerDeviceObject = NULL;


    // 用来保存键盘驱动对象
    PDRIVER_OBJECT KbdDriverObject= NULL;


    // 初始化一个字符串,就是kdbClass驱动的名字
    RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME);


    // 根据驱动名字,得到键盘驱动对象
    status = ObReferenceObjectByName(&uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, *IoDriverObjectType, KernelMode, NULL, &KbdDriverObject);
    // 如果失败了就直接返回
    if (!NT_SUCCESS(status))
    {
        KdPrint(("MyAttach: couldn't get the KbdClass Device Object\n"));
        return status;
    }
    else
    {
        // 调用ObReferenceObjectByName会导致对KbdDriverObject驱动对象的引用计数增加
        // 必须相应的调用解引用ObDereferenceObject
        ObDereferenceObject(KbdDriverObject);
    }


    // 这是键盘驱动对象的设备链中的第一个设备对象
    pTargetDeviceObject = KbdDriverObject->DeviceObject;


    // 现在开始遍历这个设备链
    while (pTargetDeviceObject)
    {
        // 在我们的驱动对象上,生成一个过滤设备对象pFilterDeviceObject
        status = IoCreateDevice(DriverObject, sizeof(C2P_DEV_EXT), NULL, pTargetDeviceObject->DeviceType, pTargetDeviceObject->Characteristics, FALSE, &pFilterDeviceObject);


        // 如果失败了就直接退出
        if (!NT_SUCCESS(status))
        {
            KdPrint(("MyAttach: Couldn't create pFilterDeviceObject\n"));
            return status;
        }


        KdPrint(("Filter DO,%#X\n",pFilterDeviceObject));


        // 绑定。将我们创建的过滤设备对象pFilterDeviceObject,附加到键盘驱动的设备对象链的最顶端,并把之前最顶端的设备对象返回
        pLowerDeviceObject = IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject);


        // 如果绑定失败,删除之前创建的过滤设备对象,并退出
        if (!pLowerDeviceObject)
        {
            KdPrint(("MyAttach: Couldn't attach to pFilterDeviceObject Device Object\n"));
            IoDeleteDevice(pFilterDeviceObject);
            pFilterDeviceObject = NULL;
            return status;
        }


        // 设备扩展。 下面要详细讲述设备扩展的应用
        devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);


        // 初始化过滤设备扩展信息
        c2pDevExtInit(devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject);


        // 下面的操作和前面过滤串口的操作基本一致,这里不再解释了
        pFilterDeviceObject->DeviceType         = pLowerDeviceObject->DeviceType;
        pFilterDeviceObject->Characteristics = pLowerDeviceObject->Characteristics;
        pFilterDeviceObject->Flags            |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);


        // 移动到下一个设备,继续遍历
        pTargetDeviceObject = pTargetDeviceObject->NextDevice;


    }


    return status;
}


NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pDriverObject, _In_ PUNICODE_STRING RegistryPath)
{
    UNREFERENCED_PARAMETER(pDriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);
    ULONG i;
    NTSTATUS status;
    KdPrint(("c2p.sys: entering DriverEntry\n"));
    KdPrint(("Driver loaded!!\n"));


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


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


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


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


    // 卸载函数
    pDriverObject->DriverUnload = DriverUnload;


    DbgBreakPoint();


    // 绑定所有键盘设备
    status = c2pAttachDevices(pDriverObject, RegistryPath);
    
    return status;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值