文章目录

  • 技术原理
    • Windows中从击键到内核
      • 流程
  • 键盘硬件原理
  • 键盘过滤的框架搭建
  • 应用设备扩展
  • 键盘过滤模块的动态卸载
  • 键盘过滤的请求处理
    • 通常的处理
  • PNP的处理
  • 读的处理
  • 读完成的处理
  • 从请求中打印出按键信息
    • 从缓冲区中获得KEYBOARD_INPUT_DATA
  • 从KEYBOARD_INPUT_DATA中得到键
  • 从MakeCode得到实际字符
  • 完整代码

技术原理

Windows中从击键到内核

  • 打开任务管理器,可以看到一个“csrss.exe”进程,这个进程很关键,它有一个线程叫做win32!RawInputThread,这个线程通过一个GUID(GUID_CLASS_KEYBOARD)来获得键盘设备栈的PDO(Phsiycal Device Object)的符号连接名。
  • csrss.exe通常是系统的正常进程,所在的进程文件是csrss或csrss.exe,是微软客户端、服务端运行时子系统,windows的核心进程之一。管理Windows图形相关任务,对系统的正常运行非常重要。csrss是Client/Server Runtime Server Subsystem的简称,即客户/服务器运行子系统,用以控制Windows图形相关子系统,必须一直运行。csrss用于维持Windows的控制,创建或者删除线程和一些16位的虚拟MS-DOS环境。也有可能是W32.Netsky.AB@mm等病毒创建的
  • 应用程序是不能直接依据设备名来打开设备的,一般都可以使用CreateFile通过符号链接名来打开。

流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqwzYeCz-1581949420750)(E:\笔记\Windows内核安全与驱动开发\第二部分-开发\第八章-键盘的过滤\原理及框架.assets\1578474035408.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxmUr6Wf-1581949420758)(E:\笔记\Windows内核安全与驱动开发\第二部分-开发\第八章-键盘的过滤\原理及框架.assets\1578474054145.png)]

  • 上面一大段调用过程暂时不用仔细去理解,简单来说就是win32k!RawInputThread线程总是调用nt!ZwReadFile函数要求读入数据,然后等待键盘上的键被按下,当键盘上的键被按下时,win32k!RawInputThread处理nt!ZwReadFile得到的数据,然后nt!ZwReadFile要求读入数据,再等待键盘上的键被按下。
  • 我们一般看到的PS/2(Personal System2,常用于链接电脑键盘、鼠标等设备的接口)键盘的设备栈,如果自己没有安装另外其他键盘过滤程序,那么设备栈的情况就如下:
    • 最顶层的设备对象是驱动KbdClass生成的设备对象
    • 中间层的设备对象是驱动i8042prt生成的设备对象
    • 最底层的设备对象是驱动ACPI生成得设备对象

键盘硬件原理

  • 键盘并不用字符来表示键,而是给每一个键规定了一个扫描码,当然因为键盘的排布方式不同,所以去搞清楚每个键的扫描码时多时是没有意义的。
  • 键盘和CPU的交互方式是通过中断和读取端口,这是个串行操作。发生依次中断,就等于键盘给了CPU依次通知,这个通知只能通知一个事件;某个键被按下或弹起。CPU不会主动去“查看”任何键,只是接收通知并读取端口的扫描码。
  • 所以一个键需要两个扫描码:一个表示键按下,另一个表示键弹起。依据网上的资料来看,如果按下的键的扫描码为X,那么同一个键弹起的扫描码就为X+0x80.
  • XP下端口和和中断号都是定死的,即中断号为0x93,端口号为0x60。每次中断时发生时,CPU都去读取端口0x60中的扫描码,0x60中只保存一个字节,但是扫描码是可以有两个字节的,此时就会发生两次中断,CPU会先后读到扫描码的两个字节。

键盘过滤的框架搭建

  • 如果绑定了KbdClass驱动的所有设备对象,那么代表键盘的设备一定也在其中。

  • 获取一个驱动全部设备对象可以有下面的方法:

    • 驱动对象结构DRIVER_OBJECT下有一个DeviceObject的域,有因为每一个DeviceObject中又有一个叫做NextDevice的域指向了驱动中的下一个设备,所以这实际上就是一个设备链,可以从这里获取到驱动的所有设备。
    • 另一种方法是调用函数IoEnumerateDeviceObjectList,这个函数可以枚举出一个驱动下的所有设备。
  • 依据开源的键盘过滤示例Ctr12Cap的源码和书中作者的代码,完成自己的键盘过滤驱动:

  • 这里用到了一个新的函数-ObRefferenceObjectByName,函数可以通过一个名字来获得一个对象的指针。

//函数需要自己声明
NTSTATUS ObReferenceObjectByName(PUNICODE_STRING ObjectName,ULONG Attribute,PACCESS_STATE AccessState,ACCESS_MASK DesiredAccess,POBJECT_TYPE ObjectType,KPROCESSOR_MODE AccessMode,PVOID ParseContext,PVOID *Object
);

应用设备扩展

  • 在之前串口过滤的例子中,用到了两个数组:一个用于保存所有的过滤设备;另一个用于保存所有的真实设备。两个数组依据下标形成一个一一映射的表的作用,拿到过滤设备的指针,马上就可以找到真实设备的指针。

  • 但是实际上我们可以在生成一个过滤设备时,为这个设备指定一个任意长度的“设备扩展”,扩展中的内容可以任意填写,形成一个自定义的数据结构。这样就可以把真实设备的指针保存在虚拟的设备对象中,更加方便查询。

  • 在代码中有体现,定义了一个拓展结构,然后在使用IoCrreateDevice创建设备对象时第二个参数这次填入结构体的长度如:


//设备拓展结构体
typedef struct _C2P_DEVICE_EXT
{//结构体大小ULONG thisSize; //过滤设备对象PDEVICE_OBJECT pFilterDeviceObject;//同时调用时的保护锁KSPIN_LOCK IoRequestsSpinLock;//进程间同步处理KEVENT IoInprogressEvent;//绑定的设备对象PDEVICE_OBJECT targetDeviceObject;//绑定前底层设备对象PDEVICE_OBJECT lowerDeviceObject;
}C2P_DEVICE_EXT,*PC2P_DEVICE_EXT;
//创建示例
status=IoCreateDevice(pDriver,sizeof(C2P_DEVICE_EXT),NULL,pTargetDeviceObject->DeviceType,pTargetDeviceObject->Characteristics,FALSE,&pFilterDeviceObject
);
  • 具体设备扩展的使用体现在了完整代码中,可以将填写设备扩展中的内容封装在了一个MyC2pDeviceExtInit自定义函数中,这样更加方便使用。

键盘过滤模块的动态卸载

  • 键盘的过滤模块的动态卸载和前面的串口过滤稍有不同,回忆之前的内容,可以想到键盘总是处于“有一个读请求没有完成”的状态。

  • 简单在回顾一下就是:当键盘上有键被按下时,将触发键盘的中断,引起中断服务历程的执行,键盘中断的中断服务历程由键盘驱动提供,键盘驱动从端口读取扫描码,经过一系列的处理之后,把从键盘的到的数据交给IRP,然后结束这个IRP。

    这个IRP的结束将导致win32k!RawInputThread这个线程对读操作的等待结束,win32k!RawInputThread线程会对得到的数据进行处理,发送给合适的进程。一旦把输入的数据处理完之后,win32k!RawInputThread会立即再调用一个nt!ZwReadFile向键盘驱动请求读入数据,也就是在开始一次等待,等待键盘上的键被按下。

  • 因此,即使向串口一样等待5秒,这个等待请求也不会完成。这是如果卸载了过滤驱动,那么可能造成蓝屏崩溃。

  • 实际实现在完整代码中。

键盘过滤的请求处理

通常的处理

  • 最通常的处理就是直接发送到真实设备,跳过虚拟设备的处理,类似于前面串口过滤用过的方法一样。
  • 代码示例如下:
NTSTATUS MyC2pDispatchGeneral(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{//其它不处理的IRP直接skip然后再用IoCallDriver把IRP发送到帧数设备的设备对象上Kdprintf(("Other Dispatch\r\n"));IoSkipCurrentIrpStackLocation(pIrp);return IoCallDriver(((PC2P_DEVICE_EXT)pDriver->DeviceExtension)->LowerDeviceObject,Irp);
}
  • 与电源相关的IRP的处理相对不同:

    • 在调用IoSipCurrentIrpStackLocation前,先调用了PoStartNextPowerIrp
    • PoCallDriver代替了IoCallDriver
NTSTATUS MyC2pPowerDispatch(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{PC2P_DEVICE_EXT devExt=(PC2P_DEVICE_EXT)pDevice->DeviceExtension;PoStartNextPowerIrp(pIrp);IoSkipCurrentirpStackLocation(pIrp);return PoCallDriver(devExt->lowerDeviceObject,pIrp);
}

PNP的处理

  • 前面说到的PNP,对其唯一需要处理的是,当有一个设备被拔出时,解除绑定,并删除过滤设备。
  • 当PNP请求过来时,是没有必要担心是否还有未完成的IRP的,因为这是Windows系统要求卸载设备,此时Windows自己应该已经处理掉了未决的IRP,所以不用向之前一样自己在进行处理。
  • 具体操作看完整代码。

读的处理

  • 处理键盘读请求时,像之前那样处理完毕后直接下发不再可行。
  • 因为当一个键盘读请求带来时,我们只能拦截到一个键扫描码值,但是在完成前并不知道这个值是多少。但是我们要达到获取键的值的目的,所以使用如下的步骤进行过滤:
    1. 调用IoCopyCurrentIrpStackLocationToNext把当前IRP栈空间拷贝到下一个栈空间。
    2. 使用函数IoSetCompletionRoutine给这个IRP设置一个完成函数,完成函数的含义是,如果这个IRP完成了,系统就会回调这个函数。
    3. 调用IoCallDriver把请求发送到下一个设备,也就是真实设备。

读完成的处理

  • 也就是上面设置的完成回调函数的编写,这里是读请求完成后的调用,应该用来获得缓冲区,按键信息就存在输出缓冲区中。

  • 这个时候打印出的按键信息其实并不是我们想要的可读的信息,所以我们要对信息做进一步的处理。

从请求中打印出按键信息

从缓冲区中获得KEYBOARD_INPUT_DATA

  • 上面已经通过pIrp->AssociatedIrp.SystemBuffer中获取到了缓冲区的数据,不过这个缓冲区有固定的格式,其中可能含有n个KEYBOARD_INPUT_DATA结构,结构体定义如下:
typedef struct _KEYBOARD_INPUT_DATA{//在头文件里解释如下:对于设备\Device\KeyboardPort0,这个值是0,对于\Device\KeyboardPort1,这个值是1,依次类推USHORT UnitId;//扫描码USHORT MakeCode;//一个标志,标志着键是按下还是弹起USHORT Flags;//保留USHORT Reserved;//扩展信息ULONG ExtraInformation;
}KEYBOARD_INPUT_DATA,*PKEYBOARD_INPUT_DATA;
  • Flags可能的取值可以有如下这些:
#define KEY_MAKE     0
#define KEY_BREAK   1
#define KEY_E0      2
#define KEY_E1      3
#define KEY_TERMSRV_SET_LED  8
#define KEY_TERMSRV_SHADOW   0x10
#define KEY_TERMSRV_VKPACKET 0x20

从KEYBOARD_INPUT_DATA中得到键

  • KEYBOARD_INPUT_DATA下的MakeCode就是扫描码,不过暂时只考虑Flags为KEY_MAKE(0)KEY_BREAK(非0)两种可能,一种表示按下;另一种表示弹起。

从MakeCode得到实际字符

  • 大小写字符的ASCII码不同,但是它们的按键码却是相同的,具体是哪个会取决于如Shift、Caps Lock键的状态,因此必须先把这几个控制键的状态保存下来。
  • 然后对于不同的控制键使用不同的过滤方法。

完整代码

#include <ntddk.h>
#include <ntddkbd.h>//全局的一个对象类型,IoDriverObjectType实际上是一个全局变量
//但是在头文件中灭有,需要声明
extern  POBJECT_TYPE *IoDriverObjectType;ULONG g_C2PKeyCount = 0;  //存储未决请求的个数//函数ObRegerenceObjectByName原型声明
NTSTATUS ObReferenceObjectByName(PUNICODE_STRING ObjectName,ULONG Attribute,PACCESS_STATE AccessState,ACCESS_MASK DesiredAccess,POBJECT_TYPE ObjectType,KPROCESSOR_MODE AccessMode,PVOID ParseContext,PVOID *Object
);//全局的KbdClass驱动的名称
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"//设备拓展结构体
typedef struct _C2P_DEVICE_EXT
{//结构体大小ULONG thisSize; //过滤设备对象PDEVICE_OBJECT pFilterDeviceObject;//同时调用时的保护锁KSPIN_LOCK IoRequestsSpinLock;//进程间同步处理KEVENT IoInprogressEvent;//绑定的设备对象PDEVICE_OBJECT targetDeviceObject;//绑定前底层设备对象PDEVICE_OBJECT lowerDeviceObject;
}C2P_DEVICE_EXT,*PC2P_DEVICE_EXT;//初始化设备扩展
NTSTATUS MyC2pDeviceExtInit(IN PC2P_DEVICE_EXT devExt,IN PDEVICE_OBJECT pFilterDeviceObject,IN PDEVICE_OBJECT pTargetDeviceObject,IN PDEVICE_OBJECT pLowerDeviceObject
)
{memset(devExt, 0, sizeof(C2P_DEVICE_EXT));devExt->thisSize = sizeof(C2P_DEVICE_EXT);devExt->pFilterDeviceObject = pFilterDeviceObject;KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));KeInitializeEvent(&(devExt->IoInprogressEvent), NotificationEvent, FALSE);devExt->targetDeviceObject = pTargetDeviceObject;devExt->lowerDeviceObject = pLowerDeviceObject;return STATUS_SUCCESS;
}//函数能够打开驱动对象KbdClass,然后绑定它下面的所有设备
NTSTATUS MyC2pAttachDevices(IN PDRIVER_OBJECT pDriver)
{NTSTATUS status = 0;//设备扩展结构体类型数据PC2P_DEVICE_EXT devExt;//结构体中的各个元素定义PDEVICE_OBJECT pFilterDeviceObject = NULL;PDEVICE_OBJECT pTargetDeviceObject = NULL;PDEVICE_OBJECT pLowerDeviceObject = NULL;//kbdClass驱动设备PDRIVER_OBJECT kbdDriverObject = NULL; KdPrint(("Start MyAttcah\r\n"));//初始化一个KbdClass驱动名称的字符串UNICODE_STRING kbdNameString;RtlInitUnicodeString(&kbdNameString, KBD_DRIVER_NAME);//打开驱动对象status = ObReferenceObjectByName(&kbdNameString, OBJ_CASE_INSENSITIVE,NULL, 0, *IoDriverObjectType, KernelMode,NULL, &kbdDriverObject);if (!NT_SUCCESS(status)){//失败返回KdPrint(("MyAttack: 不能获取驱动对象\r\n"));return status;}else{//成功后//调用ObRegerenceObjectByName会导致对驱动对象的引用计数增加1//所有必须相应的调用ObdereferenceObject函数进行解引用ObDereferenceObject(kbdDriverObject);}//这是设备链中的第一个设备pTargetDeviceObject = kbdDriverObject->DeviceObject;//遍历设备链进行绑定while (pTargetDeviceObject){//生成一个过滤设备status = IoCreateDevice(pDriver, sizeof(C2P_DEVICE_EXT), NULL,pTargetDeviceObject->DeviceType,pTargetDeviceObject->Characteristics,FALSE, &pFilterDeviceObject);//失败就退出if (!NT_SUCCESS(status)){KdPrint(("MyAttach:不能生成一个新的过滤设备\r\n"));return status;}//绑定, pLowerDeviceObject用于存储绑定之后得到的下一个设备//也就是前面说的所谓的真实设备pLowerDeviceObject = IoAttachDeviceToDeviceStack(pFilterDeviceObject,pTargetDeviceObject);//如果绑定失败,就放弃操作退出if (!pLowerDeviceObject){KdPrint(("MyAttach:不能绑定新的设备\r\n"));IoDeleteDevice(pFilterDeviceObject);pFilterDeviceObject = NULL;return status;}//设备扩展devExt = (PC2P_DEVICE_EXT)(pFilterDeviceObject->DeviceExtension);MyC2pDeviceExtInit(devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject);//最后进行信设备的属性的修改pFilterDeviceObject->DeviceType = pLowerDeviceObject->DeviceType;pFilterDeviceObject->Characteristics = pLowerDeviceObject->Characteristics;pFilterDeviceObject->StackSize = pLowerDeviceObject->StackSize;pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags&(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);//继续遍历下一个设备pTargetDeviceObject = pTargetDeviceObject->NextDevice;}return status;
}#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)//函数解除设备的绑定
VOID MyC2pDetach(IN PDEVICE_OBJECT pDeviceObject)
{PC2P_DEVICE_EXT devExt;devExt = (PC2P_DEVICE_EXT)pDeviceObject->DeviceExtension;__try{IoDetachDevice(devExt->targetDeviceObject);//解除  原始键盘设备devExt->targetDeviceObject = NULL;//扩展置零IoDeleteDevice(pDeviceObject);//删除 过滤设备devExt->pFilterDeviceObject = NULL;//扩展置零}__except (EXCEPTION_EXECUTE_HANDLER) {}return;
}//动态卸载过滤驱动
VOID MyC2pUnload(IN PDRIVER_OBJECT pDriver)
{LARGE_INTEGER lDelay;PRKTHREAD currentThead;//睡眠一段时间lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);currentThead = KeGetCurrentThread();//将当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序KeSetPriorityThread(currentThead, LOW_REALTIME_PRIORITY);UNREFERENCED_PARAMETER(pDriver);KdPrint(("MyAttach:DriverEntry Unloading...\r\n"));//遍历所有设备并解除绑定PDEVICE_OBJECT pDeviceObject = NULL;pDeviceObject= pDriver->DeviceObject;while (pDeviceObject){//解除绑定并删除所有的设备MyC2pDetach(pDeviceObject);pDeviceObject = pDeviceObject->NextDevice;}ASSERT(NULL == pDriver->DeviceObject);while (g_C2PKeyCount){KeDelayExecutionThread(KernelMode, FALSE, &lDelay);}KdPrint(("MyAttach:DriverEntry Unload OK \r\n"));return;
}/******************所有的自定义分发函数************************/
//普通分发函数,不做任何过滤,直接skip
NTSTATUS MyC2pDispatchGeneral(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{//直接把IRP发送到真实设备的设备对象上IoSkipCurrentIrpStackLocation(pIrp);PC2P_DEVICE_EXT devExt = (PC2P_DEVICE_EXT)pDevice->DeviceExtension;return IoCallDriver(devExt->lowerDeviceObject, pIrp);
}
//IRP_MJ_POWER电源相关分发函数
NTSTATUS MyC2pDispatchPower(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{//进行转发PoStartNextPowerIrp(pIrp);IoSkipCurrentIrpStackLocation(pIrp);PC2P_DEVICE_EXT devExt = (PC2P_DEVICE_EXT)pDevice->DeviceExtension;return PoCallDriver(devExt->lowerDeviceObject, pIrp);
}
//IRP_MJ_PNP 分发函数
NTSTATUS MyC2pDispatchPNP(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{//获得真实设备的IRP栈PC2P_DEVICE_EXT devExt = (PC2P_DEVICE_EXT)pDevice->DeviceExtension;PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(pIrp);//对次级消息进行分类处理NTSTATUS status;switch (irpStack->MinorFunction){case IRP_MN_REMOVE_DEVICE:{KdPrint(("IRP_MN_REMOVE_DEVICE\r\n"));//先将请求下发IoSkipCurrentIrpStackLocation(pIrp);IoCallDriver(devExt->lowerDeviceObject, pIrp);//然后解除绑定IoDetachDevice(pDevice);status = STATUS_SUCCESS;}; break;default:{//其他类型的IRP,直接下发即可IoSkipCurrentIrpStackLocation(pIrp);status = IoCallDriver(devExt->lowerDeviceObject, pIrp);}; break;}return status;}
NTSTATUS MyC2pReadComplete(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp,IN PVOID context
);
//读请求处理
NTSTATUS MyC2pDispatchRead(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp
)
{NTSTATUS status;//清理无效的请求if (pIrp->CurrentLocation==-1){KdPrint(("Dispatch encountered bogus current location\r\n"));status = STATUS_INVALID_DEVICE_REQUEST;pIrp->IoStatus.Status = status;pIrp->IoStatus.Information = 0;return status;}//得到一个去请求就将全局的计数加1g_C2PKeyCount += 1;//得到设备扩展,目的是为了获得下一个设备的指针PC2P_DEVICE_EXT devExt = (PC2P_DEVICE_EXT)pDevice->DeviceExtension;IoCopyCurrentIrpStackLocationToNext(pIrp);IoSetCompletionRoutine(pIrp, MyC2pReadComplete, pDevice, TRUE, TRUE, TRUE);status = IoCallDriver(devExt->lowerDeviceObject, pIrp);return status;
}//打印出可读的实际的字符
#define S_SHIFT     0x1
#define S_CAPS      0x2
#define S_NUM       0x4
//将控制键的状态保存在kb_status变量中
//其中有三位,分别表示Caps Lock键,Num Lock键和Shift键是否按下
static int kb_status = S_NUM;
//所有可用键
unsigned char asciiTbl[] = {0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09,    //normal0x71, 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,  //caps0x51, 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,    //shift0x51, 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 + shift0x71, 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
};//函数实现对字符的转换和打印
VOID PrintKeyStroke(UCHAR cCode)
{UCHAR ch = 0;int off = 0;if ((cCode&0x80)==0)//如果是按下{//如果按下了字母或数字等可见字符if ((cCode<0x47)||((cCode>=0x47&&cCode<0x54)&&(kb_status&S_NUM))){//通过定义好的键的表,来查找出相应的键ch = asciiTbl[off + cCode];}switch (cCode){//屏蔽掉Caps Lock和Num Lock的按两次的效果//使用异或即可case 0x3A: //Caps Lockkb_status ^= S_CAPS;break;case 0x2A:    //Shift左case 0x36:  //Shift右kb_status |= S_SHIFT;break;case 0x45:  //Num Lockkb_status ^= S_NUM;break;default:break;}}else //弹起{if (cCode==0xAA||cCode==0xB6) //键锁松开了{kb_status &= ~S_SHIFT;}}if (ch >= 0x20 && ch <= 0x7F)  //键盘上可打印的ascii的范围{KdPrint(("%c", ch));    //打印}
}//读完成的回调函数
NTSTATUS MyC2pReadComplete(IN PDEVICE_OBJECT pDevice,IN PIRP pIrp,IN PVOID context
)
{pDevice;context;//假设请求是成功的if (NT_SUCCESS(pIrp->IoStatus.Status)){//获取到请求完成后的输出缓冲区
//      PUCHAR buff = pIrp->AssociatedIrp.SystemBuffer;//获得缓冲区长度
//      ULONG len = pIrp->IoStatus.Information;//虚拟按键信息PKEYBOARD_INPUT_DATA keyData = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;//获得缓冲区中个数ULONG numKeys = pIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);//简单的打印出所有的扫描码作为测试for (ULONG i = 0; i < numKeys; i++){/*KdPrint(("numKeys: %d", numKeys));KdPrint((" ScanCode: %x", keyData->MakeCode));KdPrint((" %s\r\n", keyData->Flags ? L"Up" : L"Down"));*///只打印按下的if (keyData->Flags==KEY_MAKE){KdPrint(("%x", keyData->MakeCode));PrintKeyStroke((UCHAR)keyData->MakeCode);}}}g_C2PKeyCount -= 1;//如果请求不成功,则检查pIrp->PendingReturned标志//如果设置了此标志,那么必须也将IRP标记为挂起if (pIrp->PendingReturned){IoMarkIrpPending(pIrp);}return pIrp->IoStatus.Status;
}VOID OnDriverUnload(PDRIVER_OBJECT pDriver)
{pDriver;MyC2pUnload(pDriver);KdPrint(("驱动被卸载了\r\n"));
}NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{KdPrint(("驱动被加载了\r\n"));DbgBreakPoint();pPath;pDriver->DriverUnload = OnDriverUnload;/*********进行一些键盘过滤的前置操作************///1. 设置所有分发函数的指针for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++){pDriver->MajorFunction[i] = MyC2pDispatchGeneral;}//2. 单独设置两个分发函数的指针//2.1 Read分发函数,内部用于过滤读取来的按键信息pDriver->MajorFunction[IRP_MJ_READ] = MyC2pDispatchRead;//2.2 IRP_MJ_POWER分发函数,应为电源请求中间要调用一个//PoCallDriver和一个PoSatrtNextPowerIrp来转发,比较特殊pDriver->MajorFunction[IRP_MJ_POWER] = MyC2pDispatchPower;//3.键盘是可插拔的设备,所以也需要知道是那么时候绑定过的一个设备//被卸载了(比如从机器上被拔掉了),所以专门写一个PNP(即插即用)分发函数pDriver->MajorFunction[IRP_MJ_PNP] = MyC2pDispatchPNP;//4.绑定所有的键盘设备MyC2pAttachDevices(pDriver);return STATUS_SUCCESS;
}

参考《Windows内核安全与驱动开发》

内核与驱动_08_键盘驱动原理及代码相关推荐

  1. linux键盘驱动程序分析,linux设备驱动之键盘驱动分析

    一:前言: 在分析intel8042芯片驱动的时候,对其中断处理的后续流程还没有讨论完.在本章以键盘通道为索引讲述intel8042的后续处理,这部份内容实际上是独立的键盘驱动部份,多种型号的键盘都有 ...

  2. Linux 驱动USB键盘驱动入门demo

    1 需要内核配置文件禁用CONFIG_USB_HID,不然下面的驱动不会被探测到. 2 以下模块打印了8个控制按键是否被按下, 另外如果A按键按下,也会有打印. #include <linux/ ...

  3. DD驱动鼠标键盘(驱动级别机器人使用鼠标键盘)

    官网下载 DD虚拟键盘虚拟鼠标 github下载 GitHub - ddxoft/master 点击下载后,将驱动包下,这里以win7为例 setup运行安装 安装成功后 可以打开电脑管理,可以看见D ...

  4. 键盘驱动系列---JIURL键盘驱动 2

    2 应用层基础知识 在讨论使用键盘的应用程序这个问题之前,我们首先介绍一下 Windows 中,应用程序使用驱动,应用程序与驱动通信的一些问题. 2.1 应用程序如何使用驱动 应用程序中使用 Crea ...

  5. 编写Linux usb 键盘驱动的笔记

    编写linux usb 设备驱动参考:    linux-5.8.5\Documentation\driver-api\usb\writing_usb_driver.rst    linux-5.8. ...

  6. 键盘驱动系列---JIURL键盘驱动 3

    4 编译与调试环境简介 4.1 源码 ps/2键盘驱动的设备栈有3层,最底层设备对象的驱动是 acpi,中间层设备对象的驱动是 i8042prt,最高层设备对象的驱动是 kbdclass. DDK 所 ...

  7. JIURL键盘驱动 3

    4 编译与调试环境简介 4.1 源码 ps/2键盘驱动的设备栈有3层,最底层设备对象的驱动是 acpi,中间层设备对象的驱动是 i8042prt,最高层设备对象的驱动是 kbdclass. DDK 所 ...

  8. 【usb】linux内核USB键盘驱动解析--特殊键值转化及上报

    文章目录 一.概况 二.探索 入口 usb_kbd_irq 三.总结 四.参考资料 一.概况 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kerne ...

  9. 【usb】linux内核USB键盘驱动解析--普通键值上报及转化

    一.概况 建议阅读前置文章[usb]linux内核USB键盘驱动解析–特殊键值上报及转化 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kernel. ...

最新文章

  1. 以下可以采用python语言保留字的是-以下选项中不是 Python 语言的保留字的是
  2. Codeforces Global Round 12 E. Capitalism 差分约束
  3. 多项式全家桶学习笔记【持续更新】
  4. VBA打开TXT类文件读写相关操作代码
  5. 3.7.1 - Strings
  6. POJ 1005 I Think I Need a Houseboat
  7. atitit.提升研发效率的利器---重型框架与类库的区别与设计原则
  8. 疯狂涨知识!我凭借这份PDF的复习思路,吊打面试官
  9. 科技爱好者周刊(第 171 期):云服务流量有多贵?
  10. tomcat 加载js 中文乱码
  11. 数据库 超市零售管理系统
  12. 内存卡损坏怎么修复?分享实际经验
  13. linux外接HDMI显示器 不能正常显示的问题
  14. 出栈顺序(栈和队列)B
  15. 微信小程序背景图片不显示
  16. [LGP2791] 幼儿园篮球题
  17. (转)QQ在线客服代码
  18. UNICODE汉字数据库
  19. 如何在powerpoint 2007 中播放 swf文件
  20. 家庭收支账户小程序设计

热门文章

  1. 【Nutz】Nutz起步
  2. mysql高效率sql统计_SQl多表查询优化 高效率SQL语句
  3. 如何联系CSDN客服
  4. empty() received an invalid combination of arguments - got (tuple, dtype=NoneType, device=NoneType),
  5. Switch语句流程图
  6. Java中super关键字及super()的使用
  7. 当当云阅读云书房电子书内容提取爬虫
  8. Python中的strip()用法
  9. 创建多个wordpress_如何轻松创建多语言WordPress网站
  10. Kotlin学习(二)Kotlin基础语法