内核与驱动_08_键盘驱动原理及代码

技术原理

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;
	}
	//得到一个去请求就将全局的计数加1
	g_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,	//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
};

//函数实现对字符的转换和打印
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 Lock
			kb_status ^= S_CAPS;
			break;
		case 0x2A:	//Shift左
		case 0x36:	//Shift右
			kb_status |= S_SHIFT;
			break;
		case 0x45:	//Num Lock
			kb_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内核安全与驱动开发》

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页