Windows驱动编程之键盘过滤杂谈
字数 1573 2025-08-24 20:49:22
Windows驱动编程之键盘过滤技术详解
键盘接口基础
PS/2与USB接口对比
-
PS/2接口:
- 老式电脑使用,圆头插孔(绿色鼠标/紫色键盘)
- IBM 80年代推出Personal 2系列
- 兼容性好,可实现真正的无冲突(同时按下多个键精准识别)
-
USB接口:
- 逻辑无冲突,最多6键同时无冲突
- 早期USB传输中继最大8bit(2bit状态+6bit扫描码)
- 优势:支持热拔插、传输效率高、价格便宜、可扩展USB HUB
无线键盘
现代无线键盘多采用蓝牙连接,部分特殊设备使用P2P长线连接。
系统处理键盘过程
硬件中断处理
按下键盘时发送硬件外部中断(如键盘中断0x93),CPU通过中断码查找对应的中断处理服务。
键盘控制器(KBC)
- Intel 8042芯片负责读取键盘扫描缓冲区数据
- ECE1007负责连接键盘和EC,将键盘动作转换为扫描码
- 通信端口:0x60(数据)和0x64(状态/命令)
#define I8042_COMMAND_REG 0x64
#define I8042_STATUS_REG 0x64
#define I8042_DATA_REG 0x60
扫描码处理
- 扫描码保存在0x60端口(1字节,但扫描码可以是2字节)
- 断码 = 通码 + 0x80
- PS/2键盘扫描码表是标准化的
Windows键盘处理流程
设备栈结构
Windows键盘设备栈通常分为三层:
- 最顶层:Kbdclass(键盘类驱动)
- 中间层:i8042prt(PS/2端口驱动)或Kbdhid(USB HID驱动)
- 最底层:ACPI(硬件抽象层)
系统处理流程
- csrss.exe中的RawInputThread线程通过GUIDClass获取键盘设备栈PDO符号链接
- 调用ZwCreateFile打开设备
- 内部调用链:NtCreateFile → IoParseDevice → IoGetAttachedDevice
- 获取最顶端设备对象,初始化IRP
- ObCreateObject创建文件对象,赋值为键盘栈的PDO
- 调用IopfCallDriver发送IRP到驱动
- 完成后在csrss.exe句柄表创建句柄指向键盘设备栈PDO
键盘数据过滤技术
基本过滤方法
- 绑定顶层设备栈Kbdclass
- 获取设备对象并打开
- 绑定及生成过滤设备
- 设置完成例程(IoSetCompletionRoutine)截获IRP中的扫描码
void IoSetCompletionRoutine(
PIRP Irp,
PIO_COMPLETION_ROUTINE CompletionRoutine,
PVOID Context,
BOOLEAN InvokeOnSuccess,
BOOLEAN InvokeOnError,
BOOLEAN InvokeOnCancel
);
动态卸载注意事项
- 设置全局标识检查是否有未完成的请求
- 确保所有IRP请求处理完成后再卸载
- 避免卸载后仍有IRP处理导致蓝屏
HOOK技术实现
替换分发函数指针
- 保存原始派遣函数指针
- 替换为自定义函数
- 卸载时恢复原始指针
PDRIVER_DISPATCH OldReadAddress = NULL;
// 绑定后保存原始函数
OldReadAddress = KbdDriverObj->MajorFunction[IRP_MJ_READ];
// 设置HOOK函数
KbdDriverObj->MajorFunction[IRP_MJ_READ] = MyHook;
// 卸载时恢复
KbdDriverObj->MajorFunction[IRP_MJ_READ] = OldReadAddress;
类驱动下端口指针HOOK
- PS/2键盘端口驱动i8042prt处理硬件中断
- USB键盘驱动Kbdhid处理HID设备
- HOOK键盘中断服务例程KeyboardInterruptService
- 从更底层拦截键盘数据
反过滤与IDT HOOK技术
IDT基础
- IDT(Interrupt Descriptor Table)包含0-255中断号和处理函数
- 键盘中断通常为0x93
- 可通过sidt指令获取IDT基址
typedef struct _IDTR {
USHORT IDT_limit;
USHORT IDT_LOWbase;
USHORT IDT_HIGbase;
} IDTR, *PIDTR;
IDT HOOK实现步骤
- 获取IDT中键盘中断处理函数地址
- 计算函数偏移量
- 关闭写保护(CR0寄存器)
- 构造新的段描述符并修改门描述符中的段选择子
- 跳转到自定义处理函数
// 关闭写保护
__asm {
pushad;
pushfd;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
popfd;
popad;
}
// 开启写保护
__asm {
pushad;
pushfd;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
popfd;
popad;
}
GDT构造技巧
- 找到空闲的GDT条目(如0xA8或0x4B)
- 拷贝现有描述符并修改基地址
- 确保描述符权限设置正确(0~3bit为0)
// 构造新描述符示例
RtlCopyMemory((PVOID)gdtrBase21, (PVOID)(gdtrBase + sizeof(KGDTENTRY)), sizeof(KGDTENTRY));
// 修改基地址
sgdtrDataArr->HighWord.Bytes.BaseMid = (UCHAR)(((unsigned int)AddrNew >> 16) & 0xff);
sgdtrDataArr->HighWord.Bytes.BaseHi = (UCHAR)((unsigned int)AddrNew >> 24);
sgdtrDataArr->BaseLow = (USHORT)((unsigned int)AddrNew & 0x0000FFFF);
总结
键盘过滤技术涉及多个层次:
- 设备栈层面:绑定类驱动或端口驱动
- 函数指针层面:HOOK派遣函数或中断服务例程
- 系统表层面:修改IDT或GDT实现深层拦截
关键技术点:
- 理解Windows键盘处理流程和设备栈结构
- 掌握IRP处理机制和完成例程使用
- 熟悉IDT/GDT结构和修改方法
- 注意驱动卸载时的安全性处理
更底层的HOOK通常更难以检测和绕过,但也需要更深入的系统知识。