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;
}