内核与驱动_08_键盘驱动原理及代码
文章目录
- 技术原理
- 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,所以不用向之前一样自己在进行处理。
- 具体操作看完整代码。
读的处理
- 处理键盘读请求时,像之前那样处理完毕后直接下发不再可行。
- 因为当一个键盘读请求带来时,我们只能拦截到一个键扫描码值,但是在完成前并不知道这个值是多少。但是我们要达到获取键的值的目的,所以使用如下的步骤进行过滤:
- 调用
IoCopyCurrentIrpStackLocationToNext
把当前IRP栈空间拷贝到下一个栈空间。 - 使用函数
IoSetCompletionRoutine
给这个IRP设置一个完成函数,完成函数的含义是,如果这个IRP完成了,系统就会回调这个函数。 - 调用
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_键盘驱动原理及代码相关推荐
- linux键盘驱动程序分析,linux设备驱动之键盘驱动分析
一:前言: 在分析intel8042芯片驱动的时候,对其中断处理的后续流程还没有讨论完.在本章以键盘通道为索引讲述intel8042的后续处理,这部份内容实际上是独立的键盘驱动部份,多种型号的键盘都有 ...
- Linux 驱动USB键盘驱动入门demo
1 需要内核配置文件禁用CONFIG_USB_HID,不然下面的驱动不会被探测到. 2 以下模块打印了8个控制按键是否被按下, 另外如果A按键按下,也会有打印. #include <linux/ ...
- DD驱动鼠标键盘(驱动级别机器人使用鼠标键盘)
官网下载 DD虚拟键盘虚拟鼠标 github下载 GitHub - ddxoft/master 点击下载后,将驱动包下,这里以win7为例 setup运行安装 安装成功后 可以打开电脑管理,可以看见D ...
- 键盘驱动系列---JIURL键盘驱动 2
2 应用层基础知识 在讨论使用键盘的应用程序这个问题之前,我们首先介绍一下 Windows 中,应用程序使用驱动,应用程序与驱动通信的一些问题. 2.1 应用程序如何使用驱动 应用程序中使用 Crea ...
- 编写Linux usb 键盘驱动的笔记
编写linux usb 设备驱动参考: linux-5.8.5\Documentation\driver-api\usb\writing_usb_driver.rst linux-5.8. ...
- 键盘驱动系列---JIURL键盘驱动 3
4 编译与调试环境简介 4.1 源码 ps/2键盘驱动的设备栈有3层,最底层设备对象的驱动是 acpi,中间层设备对象的驱动是 i8042prt,最高层设备对象的驱动是 kbdclass. DDK 所 ...
- JIURL键盘驱动 3
4 编译与调试环境简介 4.1 源码 ps/2键盘驱动的设备栈有3层,最底层设备对象的驱动是 acpi,中间层设备对象的驱动是 i8042prt,最高层设备对象的驱动是 kbdclass. DDK 所 ...
- 【usb】linux内核USB键盘驱动解析--特殊键值转化及上报
文章目录 一.概况 二.探索 入口 usb_kbd_irq 三.总结 四.参考资料 一.概况 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kerne ...
- 【usb】linux内核USB键盘驱动解析--普通键值上报及转化
一.概况 建议阅读前置文章[usb]linux内核USB键盘驱动解析–特殊键值上报及转化 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kernel. ...
最新文章
- 以下可以采用python语言保留字的是-以下选项中不是 Python 语言的保留字的是
- Codeforces Global Round 12 E. Capitalism 差分约束
- 多项式全家桶学习笔记【持续更新】
- VBA打开TXT类文件读写相关操作代码
- 3.7.1 - Strings
- POJ 1005 I Think I Need a Houseboat
- atitit.提升研发效率的利器---重型框架与类库的区别与设计原则
- 疯狂涨知识!我凭借这份PDF的复习思路,吊打面试官
- 科技爱好者周刊(第 171 期):云服务流量有多贵?
- tomcat 加载js 中文乱码
- 数据库 超市零售管理系统
- 内存卡损坏怎么修复?分享实际经验
- linux外接HDMI显示器 不能正常显示的问题
- 出栈顺序(栈和队列)B
- 微信小程序背景图片不显示
- [LGP2791] 幼儿园篮球题
- (转)QQ在线客服代码
- UNICODE汉字数据库
- 如何在powerpoint 2007 中播放 swf文件
- 家庭收支账户小程序设计
热门文章
- 【Nutz】Nutz起步
- mysql高效率sql统计_SQl多表查询优化 高效率SQL语句
- 如何联系CSDN客服
- empty() received an invalid combination of arguments - got (tuple, dtype=NoneType, device=NoneType),
- Switch语句流程图
- Java中super关键字及super()的使用
- 当当云阅读云书房电子书内容提取爬虫
- Python中的strip()用法
- 创建多个wordpress_如何轻松创建多语言WordPress网站
- Kotlin学习(二)Kotlin基础语法