本人驱动方面小白一枚,如果有什么不足之处与不正确之处还请各位大佬多多指正。

另外,本篇博客参考了以下博客,非常感谢原作者的分享!我在学习的时候真的受到了很大的帮助!

IRP相关:

http://blog.csdn.net/whatday/article/details/7106721

http://bbs.fishc.com/thread-59944-1-1.html

http://blog.csdn.net/zacklin/article/details/7612499

设备对象结构:

http://www.cnblogs.com/xuankuwa/p/3657968.html

卸载后蓝屏问题解决:

http://blog.sina.com.cn/s/blog_538396fd0100zgef.html

1:原理:

通过ObReferenceByName打开KbdClass驱动对象,然后遍历KbdClass的设备栈,对上面的设备逐一绑定。随后发送到KbdClass的IRP都会先经过我们自己的设备对象,就可以在读派遣函数中设置完成例程,当IRP完成后在完成历程中得到按键信息。

2:实现:

A:宏定义与数据结构:

  1. extern POBJECT_TYPE IoDriverObjectType;
    ULONG count = 0;
    #define KBD_DRIVER_NAME L”\Driver\Kbdclass”
    #define DELAY_ONE_MICROSECOND (-10)
    #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND
    1000)
    #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND1000)

    typedef struct
    {
    ULONG NodeSize;
    PDEVICE_OBJECT filterdevice;
    PDEVICE_OBJECT lowerdevice;
    PDEVICE_OBJECT targetdevice;
    KSPIN_LOCK IoRequestsSpinLock;
    KEVENT IoInProcessEvent;
    }C2P_DEV_EXT,PC2P_DEV_EXT;

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

B:DriverEntry:

程序入口部分,设定卸载函数,分发函数等等。

  1. NTSTATUS DriverEntry(IN PDRIVER_OBJECT driver,IN PUNICODE_STRING path)
    {
    ULONG i;
    NTSTATUS status;
    KdPrint((“Entering the filter driver\r\n”));
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    driver->MajorFunction[i] = Gdispatch;
    driver->MajorFunction[IRP_MJ_READ] = Dispatchread; //读分发函数
    driver->MajorFunction[IRP_MJ_POWER] = Power; //电源事件分发函数
    driver->MajorFunction[IRP_MJ_PNP] = Pnp; //PNP事件分发函数
    driver->DriverUnload = Unload; //卸载函数
    status = AttachDevices(driver,path);
    return status;
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

C:绑定函数:

本部分功能是寻找KbdClass和绑定设备,首先需要用到的是通过设备名打开驱动对象的函数ObReference().该函数在官方文档里面虽然没有给出,但是的确已经被导出。所以只需要在函数里面声明一下即可使用。

  1. NTSTATUS ObReferenceObjectByName
    (
    PUNICODE_STRING ObjectName,
    ULONG Attributes,
    PACCESS_STATE AccessState,
    ACCESS_MASK DesiredAccess,
    POBJECT_TYPE ObjectType,
    KPROCESSOR_MODE AccessMode,
    PVOID ParseContext,
    PVOID Object
    );

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

然后通过获取到的驱动对象kbddriver指向的设备链进行遍历。对遍历得到的每一个设备对象进行绑定。

为了便于后面IRP请求以及卸载的操作,我们为设备扩展(DeviceExtensions)定义了数据结构,有兴趣的为了保证内核线程的安全性,可以把时间和锁加上去。

  1. NTSTATUS AttachDevices(PDRIVER_OBJECT driver,PUNICODE_STRING path)
    {
    NTSTATUS status=0;
    UNICODE_STRING string;
    PC2P_DEV_EXT ext;
    PDEVICE_OBJECT filter = NULL;
    PDEVICE_OBJECT lower = NULL;
    PDEVICE_OBJECT target = NULL;

    PDRIVER_OBJECT kbd = NULL;

    //获取Kbdclass驱动对象
    RtlInitUnicodeString(&string, KBD_DRIVER_NAME);
    status = ObReferenceObjectByName(&string,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&kbd);
    if (!NT_SUCCESS(status))
    {
    KdPrint((“attach failed\r\n”));
    return( status );
    }
    else
    {
    ObDereferenceObject(kbd);
    }
    //遍历设备链
    target = kbd->DeviceObject;
    while (target != NULL)
    {
    //创建过滤设备
    status = IoCreateDevice(driver, sizeof(C2P_DEV_EXT), NULL, target->DeviceType, target->Characteristics,TRUE, &filter);
    if (!NT_SUCCESS(status))
    {
    KdPrint((“could not create device\r\n”));
    return (status);
    }
    lower=IoAttachDeviceToDeviceStack(filter, target);
    if (!lower)
    {
    KdPrint((“could not attach device\r\n”));
    IoDeleteDevice(filter);
    filter = NULL;
    return (status);
    }
    //初始化设备扩展项,便于后来操作
    ext = (PC2P_DEV_EXT)(filter->DeviceExtension);
    memset(ext,0,sizeof(C2P_DEV_EXT));
    ext->lowerdevice = lower;
    ext->filterdevice = filter;
    ext->targetdevice = target;
    //KeInitializeSpinLock(&(ext->IoRequestsSpinLock));
    //KeInitializeEvent(&(ext->IoInProcessEvent), NotificationEvent, FALSE);

    //设置过滤设备的一些参数,具体作用参看引用博客
    filter->Characteristics = lower->Characteristics;
    filter->DeviceType = lower->DeviceType;
    filter->StackSize = lower->StackSize;
    filter->Flags |= lower->Flags & (DO_DIRECT_IO | DO_BUFFERED_IO | DO_POWER_PAGABLE);

    target = target->NextDevice;
    }
    return status;
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

IoAttachDeviceToDeviceStack是将调用方的设备对象附加到设备对象链中的最高层,并返回之前在设备对象链中最高的设备对象。靠着这个函数,我们的设备对象从低层的kbdclass指向的设备链对象绑定到了设备链的最顶层。也就是说,我们创建的filterdevice载设备链里紧贴着的并不一定是Kbdclass设备链的对象。故此我们使用C2P_DEV_EXT结构中的plowerdevice存储filterdevice下面的那个设备对象。这个设备对象我们一般称为真实设备。

但是在接触绑定的时候,传递的参数依然是最开始绑定的那个设备对象。所以在C2P_DEV_EXT结构中,我们使用ptargetdevice存储最开始的kbdclass的设备链中的设备对象。

D:分发函数:

a:电源事件分发函数:

调用PoStartNextPowerIrp()后把请求下发即可。

  1. NTSTATUS Power( IN PDEVICE_OBJECT device,IN PIRP irp)
    {
    KdPrint((“power dispatch\r\n”));
    PoStartNextPowerIrp(irp);
    IoSkipCurrentIrpStackLocation(irp);
    return IoCallDriver(((C2P_DEV_EXT )(device->DeviceExtension))->lowerdevice, irp);
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

b:pnp(即插即拔)事件分发函数:

将请求下发,解除绑定,删除绑定的设备。

在触发pnp请求的时候,毫无疑问所有的击键与中断都已经由系统完成。所以不需要考虑会有中断滞留的情况。

  1. NTSTATUS Pnp(IN PDEVICE_OBJECT device,IN PIRP irp)
    {
    NTSTATUS status;
    KdPrint((“PNP dispatch\r\n”));
    PC2P_DEV_EXT ext = (PC2P_DEV_EXT)(device->DeviceExtension);
    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
    switch(irpsp->MinorFunction)
    {
    case IRP_MN_CANCEL_REMOVE_DEVICE:
    IoSkipCurrentIrpStackLocation(irp);
    IoCallDriver(ext->lowerdevice,irp);
    IoDetachDevice(ext->lowerdevice);
    IoDeleteDevice(device);
    status = STATUS_SUCCESS;
    break;
    default:
    IoSkipCurrentIrpStackLocation(irp);
    IoCallDriver(ext->lowerdevice, irp);
    break;
    }
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

c:读请求分发函数:

在我们没有击键的时候,系统的函数会创建一个IRP_MJ_READ的请求到内核,然后该请求会滞留在那里。一直到我们击键触发中断。在这种情况下,我们卸载驱动的时候,如果直接删除驱动,那么这个IRP_MJ_READ在我们下一次击键触发中断的时候会导致系统的蓝屏。所以为了解决这个问题,我们必须在一次中断得到满足的时候卸载驱动,count用来标记当前的IRP_MJ_READ有没有得到满足。

如此一来,键盘的读请求就应该分成两个状态:击键和击键完成上一个IRP_MJ_READ请求被满足。故此我们设定两个函数Dispatchread和readcomplete。将readcomplete设为回调函数。

  1. NTSTATUS Dispatchread(IN PDEVICE_OBJECT device,IN PIRP irp)
    {
    PC2P_DEV_EXT ext;
    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION currentstack;
    //KEVENT event;
    //KeInitializeEvent(&event, NotificationEvent, FALSE);

    //当请求已经到达了栈底的时候,设置请求已经完成,开始回调
    if (irp->CurrentLocation == 1)
    {
    ULONG Returnedinformation=0;
    KdPrint((“Dispatch encountered bogus current location\r\n”));
    status = STATUS_INVALID_DEVICE_REQUEST;
    irp->IoStatus.Status = status;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp,IO_NO_INCREMENT);
    return (status);
    }

    count++;
    ext = (PC2P_DEV_EXT)(device->DeviceExtension);

    //将请求传递到下一层
    currentstack = IoGetCurrentIrpStackLocation(irp);
    IoCopyCurrentIrpStackLocationToNext(irp);

    //设置回调函数
    IoSetCompletionRoutine(irp, readcomplete, device, TRUE, TRUE, TRUE);
    return IoCallDriver(ext->lowerdevice, irp);
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  1. NTSTATUS readcomplete(IN PDEVICE_OBJECT device,IN PIRP irp,IN PVOID context)
    {
    PIO_STACK_LOCATION irpsp;
    PUCHAR buf = NULL;
    ULONG buf_len;
    size_t i;

    UNREFERENCED_PARAMETER(device);
    irpsp = IoGetCurrentIrpStackLocation(irp);

    //打印击键信息
    if (NT_SUCCESS(irp->IoStatus.Status))
    {
    buf = irp->AssociatedIrp.SystemBuffer;
    buf_len = irp->IoStatus.Information;
    for (i = 0; i < buf_len; i++)
    DbgPrint(“ctralcap:%2x\r\n”,buf[i]);
    }
    //计数器减一
    count—;

    //声明irp没有完成,用以检查irp返回值
    if (irp->PendingReturned)
    {
    IoMarkIrpPending(irp);
    }
    return irp->IoStatus.Status;
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

关于irp.Iostatus的详细信息,参看参考博客。

d:普通分发函数:

简单的将请求传到下一层即可。

  1. NTSTATUS Gdispatch(IN PDEVICE_OBJECT device,IN PIRP irp)
    {
    KdPrint((“other dispatch\r\n”));
    IoSkipCurrentIrpStackLocation(irp);
    return IoCallDriver(((C2P_DEV_EXT )(device->DeviceExtension))->lowerdevice, irp);
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

E:卸载函数:

解除绑定删除设备这些常见的姑且不提,有一点需要注意。在读分发函数的解释里面已经说过,卸载必须在彻底完成一个请求的时候进行,否则就会导致系统蓝屏。所以我们通过判断标志位count是否为0来确定请求是否被满足。这样一来,该驱动卸载时间就变成了下一次击键的时候。

  1. VOID Unload(IN PDRIVER_OBJECT driver)
    {
    PDEVICE_OBJECT device, old;
    LARGE_INTEGER delay;
    PC2P_DEV_EXT ext;
    delay = RtlConvertLongToLargeInteger(100 DELAY_ONE_MILLISECOND);
    PRKTHREAD current;
    current = KeGetCurrentThread();
    KeSetPriorityThread(current,LOW_REALTIME_PRIORITY);

    UNREFERENCED_PARAMETER(driver); //通知编译器driver已被使用过
    KdPrint((“unloading\r\n”));

    device = driver->DeviceObject;
    //遍历设备链,解除绑定和删除设备。
    while (device != NULL)
    {
    ext = (PC2P_DEV_EXT)device->DeviceExtension;
    try
    {

  2. try{
    IoDetachDevice(ext->targetdevice);

    ext->targetdevice = NULL;
    IoDeleteDevice(device);
    ext->filterdevice = NULL;
    }
    _except(EXCEPTION_EXECUTE_HANDLER){}
    }
    __finally{}
    device = device->NextDevice;
    }
    KdPrint((“%ld\r\n”,count));

    //等待IRP_MJ_READ完成
    while (count!=0)
    {
    KeDelayExecutionThread(KernelMode, FALSE, &delay);
    }
    KdPrint((“unload completely\r\n”));
    return;
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

F:关于《寒江独钓》书中代码的两个问题。

在windows7下执行书中的源代码生成的驱动的时候,会出现通过无法打开设备对象的问题。要解决这个问题,只需要修改下面两行代码即可。

原代码:

  1. extern POBJECT_TYPE IoDriverObjectType;
    status = ObReferenceObjectByName (
    &uniNtNameString,
    OBJ_CASE_INSENSITIVE,
    NULL,
    0,
    IoDriverObjectType,
    KernelMode,
    NULL,
    &KbdDriverObject
    );

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

修改之后:

  1. extern POBJECT_TYPE IoDriverObjectType;
    status = ObReferenceObjectByName (
    &uniNtNameString,
    OBJ_CASE_INSENSITIVE,
    NULL,
    0,
    *IoDriverObjectType,
    KernelMode,
    NULL,
    &KbdDriverObject
    );

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

这个问题在windowsXP下并没有,我也搞不清楚原因,还请各位大佬指教!在这里先感谢大家了!

另一个问题是windows7下面在卸载原代码编译生成的驱动之后,再次击键的一瞬间会出现蓝屏的问题。这是因为ObDeferenceObject()里面的参数应该是ObReferenceByName得到的驱动对象,而不应该是本驱动的驱动对象。或许是windowsXP检查不严谨导致?

原代码:

  1. ObDereferenceObject(DriverObject);

    • 1

修改:

  1. ObDereferenceObject(kbd); //kbd是通过ObReferenceByName获取到的KbdClass的驱动对象

    • 1

windows7 《寒江独钓》传统键盘过滤驱动学习相关推荐

  1. 《寒江独钓》键盘过滤部分程序修改

    <寒江独钓>键盘过滤部分程序修改 看书的时候,发现书里第四章的键盘过滤程序并不能实现打印出扫描码详细信息的功能.我将程序修改了一下以后,可以实现该功能.以下是我的修改之处: void __ ...

  2. 寒江独钓windows 内核安全编程学习笔记

    寒江独钓windows 内核安全编程学习笔记 本博客记录自己的学习过程,如有侵犯或者打扰请告知. 由于项目的需求,第一次接触到驱动程序.开始学习了寒江大神的的内核安全编程.小白一个,第一章就遇到了问题 ...

  3. 寒江独钓前辈的第一个例子的编译运行过程

    环境: vs2103 +wdk 7+ 看雪中的2013 模板   可以参考我的上篇文章. 环境ok后,可以编译前辈的驱动例子了. 从网上下载了寒江独钓的第一个驱动例子代码  串口过滤 comcap. ...

  4. 驱动开发专家解读 寒江独钓 Windows内核安全编程

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 驱动开发 ...

  5. 寒江独钓:Windows内核安全编程(china-pub到货首发)

    寒江独钓:Windows内核安全编程(china-pub到货首发) [作 者]谭文;杨潇;邵坚磊等 [丛 书 名] 驱网核心技术丛书  [出 版 社] 电子工业出版社     [书 号] 978712 ...

  6. 寒江独钓-Windows内核安全编程(完整版).pdf

    寒江独钓-Windows内核安全编程(完整版).pdf   编写Windows内核程序,就意味着这个程序可以执行任意指令,可以访问计算机所有的软件.硬件资源.因此,稍有不慎就有可能将系统变得不稳定.W ...

  7. 《寒江独钓》内核学习笔记(1)-- IRP - .Little Hann

     原文  http://www.cnblogs.com/LittleHann/p/3450436.html 在学习内核过滤驱动的过程中,遇到了大量的涉及IRP操作的代码,这里有必要对IRP的数据结 ...

  8. 寒江独钓 Windows内核安全编程

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! < ...

  9. 《寒江独钓》内核学习笔记

    <寒江独钓>内核学习笔记(1)-- IRP - .Little Hann 时间 2013-11-30 15:40:00  博客园_.Little Hann原文  http://www.cn ...

最新文章

  1. 加密货币银行是什么?它又将如何运作?
  2. 在web.xml文件中配置Servlet时,主要配置哪些信息?
  3. 干货丨一位16岁CEO教你如何在高中阶段入门人工智能
  4. 学业水平考试网登录_江西学业水平考试成绩查询登录入口
  5. MySQL无法创建外键、查询外键的属性
  6. 卢伟冰:Redmi K30会支持全网通5G 雷军:必须的!
  7. maven之pom深入
  8. DataLoader, when num_worker 0, there is bug
  9. 保存用户数据到mysql_MySQL中所有用户信息都保存在【 】数据表中。
  10. CSDN下载频道1月最受欢迎资源强力推荐~!
  11. P1005 [NOIP2007 提高组] 矩阵取数游戏
  12. 虚拟机服务器异常怎么解决,windows server 2016 虚拟机异常关闭
  13. npm安装依赖报错——npm ERR gyp verb cli的解决方法 Node Sass version 7.0.1 is incompatible with ^4.0.0. 因为在此系统上禁止运
  14. linux 环境命令行导出dmp文件
  15. 按住Shift键右击鼠标打开命令行窗口
  16. Java导出数据到Word模板中
  17. 【调剂】东北石油大学计算机科学与技术专业接收调剂
  18. 为AI而生的IPU芯片,或挑战GPU的霸主位?
  19. CDR插件开发之CPG插件018 - 在CPG插件中调用外部EXE程序并传递参数
  20. 自适应阈值分割(最大类间分割法 + OTSU)

热门文章

  1. PyTorch实现基于卷积神经网络的面部表情识别
  2. Mac 更新port卡住怎么办?
  3. java list 索引值_List中固定某个索引的值-简单替换位置
  4. 计算机视觉论文-2021-05-28
  5. 如何做一个不被喷的产品经理?
  6. 模拟键盘按键 自动输入文字
  7. IOS屏幕旋转的检测 与 强行切换
  8. 罗云彬:实现水波特效的代码例子
  9. 黑苹果睡眠唤醒usb失灵_黑苹果解决USB3.0驱动问题
  10. AcWing 188. 武士风度的牛