Windows键盘过滤

Windows键盘过滤

本文记录我复现谭文老师的《Windows内核编程》一书中相关章节的实验的过程。

Windows键鼠驱动体系结构

微软官方文档

何为PS/2

Windows 设备管理器里显示的 PS/2 ,其实是一个键盘和鼠标的接口标准,而不是指 IBM 的 PS/2 电脑型号。它诞生于 1987 年,是早期 PC 连接键盘和鼠标的主流方式。

虽然现在大家普遍使用 USB 接口,但 PS/2 在系统底层依然有"特权",所以 Windows 为了兼容老设备或主板接口,依然保留了这套驱动和标识。

特征 PS/2 接口(圆孔) USB 接口(方口)
外观 圆形 6 针接口,分紫(键盘)绿(鼠标) 扁平方形接口
连接 不支持热插拔(开机前必须插好) 支持热插拔
系统占用 独占中断(IRQ),不占用 USB 资源 共享 USB 总线资源

即使你用的是 USB 键盘,设备管理器也可能显示"PS/2 标准键盘"。这是因为主板 BIOS 或系统驱动进行了协议转换:

  • 硬件层面:主板内部将 USB 设备模拟成 PS/2 设备进行识别。
  • 驱动层面 :Windows 使用 i8042prt.sys 驱动来统一处理这类输入信号。

那么现在PS/2类型的键盘还有用吗:

  • 普通用户:基本被 USB 取代。
  • 特殊场景 :部分服务器、工控机或键盘发烧友仍偏好 PS/2,因为它能实现 NKRO(全键无冲) 且延迟极低,不依赖操作系统驱动,在 BIOS 环境下兼容性更好。

所以,PS/2 在 Windows 里更像是一个"历史遗留的兼容层",确保那些老式圆口键盘在最新系统上也能即插即用。

关于i8042

i8042prt.sys 这个看似晦涩的名字,其实是 Windows 驱动命名惯例与硬件历史的结合体。它由两部分构成:i8042 是硬件芯片的型号,prt 是微软的驱动类型后缀。

i8042这部分名字直接来源于硬件。

  • 历史背景:在早期的 IBM PC/AT(1984年)及兼容机上,键盘控制任务并非由 CPU 直接处理,而是交给一块独立的、廉价的微控制器芯片。
  • 芯片型号 :这块芯片通常就是 Intel 8042(或兼容芯片)。它的职责非常专一:负责扫描键盘矩阵、处理按键信号,并通过特定端口与主板通信。
  • 命名继承 :微软在编写驱动时,直接沿用了这个硬件控制器的型号名"8042",并在前面加上了制造商"i"(Intel)作为前缀。这遵循了 Windows 底层驱动常以硬件型号命名的传统(如 i8042prt.sys、atapi.sys)。

prt这是微软定义的驱动类型后缀。

  • PRT 的含义 :在 Windows NT 架构中,prt 通常代表 Port Driver(端口驱动)。这类驱动位于驱动栈的底层,直接与硬件端口(Port)或总线打交道,负责最基础的输入输出控制。
  • 角色定位 :i8042prt.sys 正是充当了键盘和鼠标的"交通警察"。它直接与主板上的 8042 芯片(或模拟该芯片的硬件)交互,读取端口 0x60 和 0x64 的数据,然后将原始的扫描码(Scan Code)转换为标准格式,再向上层驱动传递。

为什么 USB 键盘也显示 PS/2?这就涉及到一个关键机制:PS/2 仿真(Emulation)。

  • 虽然现代键盘多使用 USB 接口,但为了确保在 BIOS 阶段或极低层级的系统环境下(如安装系统时)键盘依然可用,主板 BIOS 或 USB 控制器会将 USB 键盘模拟成传统的 PS/2 设备。
  • 此时,Windows 加载的仍然是 i8042prt.sys 这个"老管家"来接收信号。因此,你在设备管理器里看到的依然是"PS/2 标准键盘",尽管物理连接是 USB。
名称部分 来源 含义
i8042 硬件历史 Intel 8042 键盘控制器芯片型号
prt 系统架构 Port Driver,表示底层端口驱动

所以,i8042prt.sys 本质上是一个为了兼容 40 年前硬件而存在的"古董级"驱动。它的存在确保了从古老的 AT 键盘到现代 USB 设备,都能在 Windows 上被统一识别和处理。

键盘信号处理过程

简单地说,win32k!RawInputThread线程总是调用nt!ZwReadFile函数要求读入数据,然后等待键盘上的键被按下。当键盘上的键被按下时 , win32k!RawInputThread 处 理 nt!ZwReadFile 得到的数据 , 然后nt!ZwReadFile要求读入数据,再等待键盘上的键被按下。

键盘上的每一个键都有扫描码,驱动程序会读取扫描码,将其翻译为正确的动作。键盘和CPU的交互方式是中断和读取端口,这个操作是串行的。

示例代码

头文件

cpp 复制代码
#pragma once

#include <wdm.h>
#include <ntddkbd.h>

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

// 未文档化的函数,声明之后可以直接使用
extern "C" NTKERNELAPI NTSTATUS ObReferenceObjectByName(
	PUNICODE_STRING objName,
	ULONG attributes,
	PACCESS_STATE accessState,
	ACCESS_MASK desiredAccess,
	POBJECT_TYPE objType,
	KPROCESSOR_MODE accessMode,
	PVOID parseContext,
	PVOID* obj);

extern "C" POBJECT_TYPE* IoDriverObjectType;

struct DEV_EXT
{
	// 下层设备
	PDEVICE_OBJECT pLowerDevObj;
	// 目标设备
	PDEVICE_OBJECT pTargetDevObj;
};

源文件

cpp 复制代码
#include "KbFilter_3.h"

static ULONG sg_cnt = 0; // 全局计数

VOID MyDetachDevice(PDEVICE_OBJECT pDevObj)
{
	DEV_EXT* pDevExt = (DEV_EXT*)pDevObj->DeviceExtension;
	IoDetachDevice(pDevExt->pTargetDevObj);
	pDevExt = nullptr;
	IoDeleteDevice(pDevObj);

	DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Detached Lower Device\n");
}

/*
卸载驱动例程
*/
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
	// 把当前线程设置为低实时模式
	PKTHREAD currentThread = KeGetCurrentThread();
	KeSetPriorityThread(currentThread, LOW_REALTIME_PRIORITY);

	// 遍历设备链,解绑设备
	PDEVICE_OBJECT pDevObj = pDriverObj->DeviceObject;
	while (pDevObj)
	{
		MyDetachDevice(pDevObj);
		pDevObj = pDevObj->NextDevice;
	}

	ASSERT(pDevObj == nullptr);

	// 等待未完成的IRP
	LARGE_INTEGER lg;
	lg.QuadPart = (__int64)1000 * 100;
	while (sg_cnt)
	{
		KeDelayExecutionThread(KernelMode, false, &lg);
	}

	DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Uninstalled Keyboard Filter Driver\n");
	return;
}

// 打开驱动对象KbdClass,并绑定它下面的所有驱动设备
NTSTATUS AttachKeybroadObj(PDRIVER_OBJECT pDriverObj)
{
	DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
		"Attaching keybroad device object\n");

	// 打开驱动对象KbdClass
	UNICODE_STRING KbdclassDriverName;
	RtlInitUnicodeString(&KbdclassDriverName, KBD_DRIVER_NAME);

	PDRIVER_OBJECT pKbdDriverObject = nullptr;
	NTSTATUS status = ObReferenceObjectByName(
		&KbdclassDriverName,
		OBJ_CASE_INSENSITIVE,
		NULL,
		0,
		*IoDriverObjectType,
		KernelMode,
		NULL,
		(PVOID*)&pKbdDriverObject);

	if (!NT_SUCCESS(status)) // 打开失败了,直接退出
	{
		DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
			"Open KbdClass driver object failed\n");

		return status;
	}

	// 开始遍历键盘驱动对象的设备链,绑定它的所有设备
	PDEVICE_OBJECT pTargetDeviceObj = pKbdDriverObject->DeviceObject;
	PDEVICE_OBJECT pFilterDevObj = nullptr, pLowerDevObj = nullptr;
	DEV_EXT* devExt = nullptr;
	while (pTargetDeviceObj)
	{
		// 创建过滤设备
		status = IoCreateDevice(pDriverObj, sizeof(DEV_EXT), nullptr, pTargetDeviceObj->DeviceType,
			pTargetDeviceObj->Characteristics, false, &pFilterDevObj);
		if (!NT_SUCCESS(status))
		{
			DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Create filter device object failed\n");
			return status;
		}

		pLowerDevObj = IoAttachDeviceToDeviceStack(pFilterDevObj, pTargetDeviceObj);
		if (!pLowerDevObj) // 绑定失败
		{
			DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Attached target device failed\n");
			IoDeleteDevice(pFilterDevObj);
			pFilterDevObj = nullptr;
			return (status);
		}

		// 初始化设备拓展
		devExt = (DEV_EXT*)pFilterDevObj->DeviceExtension;
		devExt->pLowerDevObj = pLowerDevObj;
		devExt->pTargetDevObj = pTargetDeviceObj;

		// 过滤设备的基本设置
		pFilterDevObj->DeviceType = pLowerDevObj->DeviceType;
		pFilterDevObj->Characteristics = pLowerDevObj->Characteristics;
		pFilterDevObj->StackSize = pLowerDevObj->StackSize + 1;
		pFilterDevObj->Flags |= pLowerDevObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
		// next device
		pTargetDeviceObj = pTargetDeviceObj->NextDevice;
	}

	ObDereferenceObject(pKbdDriverObject); // 记得释放对象

	return STATUS_SUCCESS;
}

// 通用处理例程,不感兴趣的直接跳过
NTSTATUS DispatchGeneral(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	IoSkipCurrentIrpStackLocation(pIrp);
	return IoCallDriver(((DEV_EXT*)pDevObj->DeviceExtension)->pLowerDevObj, pIrp);
}

// PNP处理函数。pnp请求不能像其它IRP一样简单跳过交给下一驱动处理
NTSTATUS DispatchPnp(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	PIO_STACK_LOCATION pStLoc = IoGetCurrentIrpStackLocation(pIrp);
	DEV_EXT* pDevExt = (DEV_EXT*)pDevObj->DeviceExtension;

	NTSTATUS status;
	switch (pStLoc->MinorFunction)
	{
	case IRP_MN_REMOVE_DEVICE: // 设备移出请求
	{
		IoSkipCurrentIrpStackLocation(pIrp); // 先下发请求
		IoCallDriver(pDevExt->pLowerDevObj, pIrp);

		IoDetachDevice(pDevExt->pLowerDevObj); // 解除绑定
		IoDeleteDevice(pDevObj); // 删除过滤设备

		status = STATUS_SUCCESS;
		break;
	}
	default:
	{
		IoSkipCurrentIrpStackLocation(pIrp);
		status = IoCallDriver(pDevExt->pLowerDevObj, pIrp);
	}
	}

	return status;
}

// POWER处理函数
NTSTATUS DispatchPower(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	DEV_EXT* pDevExt = (DEV_EXT*)pDevObj->DeviceExtension;
	
	PoStartNextPowerIrp(pIrp);
	IoSkipCurrentIrpStackLocation(pIrp);
	return PoCallDriver(pDevExt->pLowerDevObj, pIrp);
}

// 请求完成回调函数
NTSTATUS ReadComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PVOID pContext)
{
	UNREFERENCED_PARAMETER(pDevObj);
	UNREFERENCED_PARAMETER(pContext);

	//	PIO_STACK_LOCATION pStLoc = IoGetCurrentIrpStackLocation(pIrp);

		// 判断下层驱动返回的状态,如果下层驱动返回了失败的结果,那么我们也没有必要继续处理该请求了
	if (NT_SUCCESS(pIrp->IoStatus.Status))
	{
		// 读请求完成后返回的系统缓冲区
		auto buf = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
		ULONG_PTR buf_len = pIrp->IoStatus.Information;
		ULONG numberKey = buf_len / sizeof(KEYBOARD_INPUT_DATA);
		for (size_t i = 0; i < numberKey; i++)
		{
			DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
				"number key: %d | scancode: %x | Flags: %s\n",
				numberKey, buf[i].MakeCode, buf[i].Flags ? "Up" : "Down");
		}
	}

	sg_cnt--; // 完成了一个读请求,全局计数器-1

	if (pIrp->PendingReturned)
	{
		IoMarkIrpPending(pIrp);
	}

	return pIrp->IoStatus.Status;
}

// 读取请求处理例程
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
	// 到达最后一层了,不能继续往下传递了
	if (pIrp->CurrentLocation == 1)
	{
		pIrp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
		pIrp->IoStatus.Information = 0;

		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
		return STATUS_INVALID_DEVICE_REQUEST;
	}

	// 全局计数器+1
	sg_cnt++;

	// 继续下发
	DEV_EXT* pDevExt = (DEV_EXT*)pDevObj->DeviceExtension;

	// PIO_STACK_LOCATION pStLoc = IoGetCurrentIrpStackLocation(pIrp);
	IoCopyCurrentIrpStackLocationToNext(pIrp);
	IoSetCompletionRoutine(pIrp, ReadComplete, pDevObj, true, true, true);
	return IoCallDriver(pDevExt->pLowerDevObj, pIrp);
}

EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegPath)
{
	UNREFERENCED_PARAMETER(pRegPath);
	DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
		"Installing keyboard-filter driver\n");

	pDriverObj->DriverUnload = DriverUnload;

	for (size_t i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		pDriverObj->MajorFunction[i] = DispatchGeneral;
	}

	pDriverObj->MajorFunction[IRP_MJ_READ] = DispatchRead;
	pDriverObj->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
	pDriverObj->MajorFunction[IRP_MJ_POWER] = DispatchPower;

	NTSTATUS status = AttachKeybroadObj(pDriverObj);

	return status;
}
相关推荐
java资料站4 小时前
二、Spring AI Alibaba · ChatModel
java·windows·spring
北冥you鱼5 小时前
Go 语言空接口(interface{})使用场景与最佳实践
开发语言·windows·golang
2401_862880826 小时前
iMX6ULL 驱动工程搭建 + LED 灯驱动
c语言·驱动开发·嵌入式硬件
夏虫不可与之言冰7 小时前
Windows WSL2 (Ubuntu) 搭建 ESP‑IDF 6.1 开发环境全过程演示
linux·windows·ubuntu·esp32·乐鑫科技·乐鑫eim·乐鑫代理商
打工仔折腾 AI10 小时前
FaceFusion本地换脸实战:Windows整合包、模型选择与遮罩调参记录
人工智能·windows·后端·python·深度学习·性能优化·ai agent 实战
硅基手札11 小时前
【串口技术系列文档 07】UART控制器寄存器详解
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札12 小时前
【串口技术系列文档 09】FIFO管理与流控
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札12 小时前
【串口技术系列文档 08】中断驱动vs轮询vs DMA
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
小蜗牛狂飙记12 小时前
Windows 双击 Jar 包无反应,不用写 bat 脚本!Jarfix 一键修复(以 Struts2 扫描工具为例)
windows·struts·jar
坐看风云变幻13 小时前
Betaflight 开发环境搭建保姆级教程(Ubuntu / Windows+WSL / Docker 三方案 + 官方网址大全)
windows·ubuntu·docker·无人机·飞控·betaflight