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.sysatapi.sys)。

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

  • PRT 的含义 :在 Windows NT 架构中,prt 通常代表 Port Driver(端口驱动)。这类驱动位于驱动栈的底层,直接与硬件端口(Port)或总线打交道,负责最基础的输入输出控制。
  • 角色定位i8042prt.sys 正是充当了键盘和鼠标的"交通警察"。它直接与主板上的 8042 芯片(或模拟该芯片的硬件)交互,读取端口 0x600x64 的数据,然后将原始的扫描码(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;
}
相关推荐
苦青藤7 小时前
从零搭建 WSUS 隔离网络:完整实战指南(内网离线补丁分发)
运维·windows·microsoft
Smoothcloud润云15 小时前
5大功能精修,重构AI算力使用体验!
java·人工智能·windows·算法·重构·编辑器·sublime text
ModestCoder_15 小时前
windows/ubuntu解决挂梯子但是codex reconnecting五次的问题
linux·windows·ubuntu
玖釉-16 小时前
Vulkan 中 Shader 的 vert、frag、mesh、comp 全面解析:作用、关系、特点与工程实践
开发语言·c++·windows·算法·图形渲染
玖釉-16 小时前
Vulkan 示例解析:gltfscenerendering.cpp 如何渲染一个复杂 glTF 场景
c++·windows·图形渲染
一个人旅程~17 小时前
Windows的6月份安全启动证书过期如何查看是否过期是否需要更新如何操作
windows·经验分享·macos·电脑
风吹夏回18 小时前
保姆级教程:Dify 本地一键部署(Windows/Mac 通用)
windows·macos
Fly feng18 小时前
windows 内核原理之内核名字及相关概念
windows·内核原理
海 月19 小时前
adb install 右键快捷菜单
windows