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键盘设备栈通常分为三层:

  1. 最顶层:Kbdclass(键盘类驱动)
  2. 中间层:i8042prt(PS/2端口驱动)或Kbdhid(USB HID驱动)
  3. 最底层:ACPI(硬件抽象层)

系统处理流程

  1. csrss.exe中的RawInputThread线程通过GUIDClass获取键盘设备栈PDO符号链接
  2. 调用ZwCreateFile打开设备
    • 内部调用链:NtCreateFile → IoParseDevice → IoGetAttachedDevice
  3. 获取最顶端设备对象,初始化IRP
  4. ObCreateObject创建文件对象,赋值为键盘栈的PDO
  5. 调用IopfCallDriver发送IRP到驱动
  6. 完成后在csrss.exe句柄表创建句柄指向键盘设备栈PDO

键盘数据过滤技术

基本过滤方法

  1. 绑定顶层设备栈Kbdclass
  2. 获取设备对象并打开
  3. 绑定及生成过滤设备
  4. 设置完成例程(IoSetCompletionRoutine)截获IRP中的扫描码
void IoSetCompletionRoutine(
    PIRP Irp,
    PIO_COMPLETION_ROUTINE CompletionRoutine,
    PVOID Context,
    BOOLEAN InvokeOnSuccess,
    BOOLEAN InvokeOnError,
    BOOLEAN InvokeOnCancel
);

动态卸载注意事项

  • 设置全局标识检查是否有未完成的请求
  • 确保所有IRP请求处理完成后再卸载
  • 避免卸载后仍有IRP处理导致蓝屏

HOOK技术实现

替换分发函数指针

  1. 保存原始派遣函数指针
  2. 替换为自定义函数
  3. 卸载时恢复原始指针
PDRIVER_DISPATCH OldReadAddress = NULL;

// 绑定后保存原始函数
OldReadAddress = KbdDriverObj->MajorFunction[IRP_MJ_READ];

// 设置HOOK函数
KbdDriverObj->MajorFunction[IRP_MJ_READ] = MyHook;

// 卸载时恢复
KbdDriverObj->MajorFunction[IRP_MJ_READ] = OldReadAddress;

类驱动下端口指针HOOK

  1. PS/2键盘端口驱动i8042prt处理硬件中断
  2. USB键盘驱动Kbdhid处理HID设备
  3. HOOK键盘中断服务例程KeyboardInterruptService
  4. 从更底层拦截键盘数据

反过滤与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实现步骤

  1. 获取IDT中键盘中断处理函数地址
  2. 计算函数偏移量
  3. 关闭写保护(CR0寄存器)
  4. 构造新的段描述符并修改门描述符中的段选择子
  5. 跳转到自定义处理函数
// 关闭写保护
__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构造技巧

  1. 找到空闲的GDT条目(如0xA8或0x4B)
  2. 拷贝现有描述符并修改基地址
  3. 确保描述符权限设置正确(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);

总结

键盘过滤技术涉及多个层次:

  1. 设备栈层面:绑定类驱动或端口驱动
  2. 函数指针层面:HOOK派遣函数或中断服务例程
  3. 系统表层面:修改IDT或GDT实现深层拦截

关键技术点:

  • 理解Windows键盘处理流程和设备栈结构
  • 掌握IRP处理机制和完成例程使用
  • 熟悉IDT/GDT结构和修改方法
  • 注意驱动卸载时的安全性处理

更底层的HOOK通常更难以检测和绕过,但也需要更深入的系统知识。

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(状态/命令) 扫描码处理 扫描码保存在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中的扫描码 动态卸载注意事项 设置全局标识检查是否有未完成的请求 确保所有IRP请求处理完成后再卸载 避免卸载后仍有IRP处理导致蓝屏 HOOK技术实现 替换分发函数指针 保存原始派遣函数指针 替换为自定义函数 卸载时恢复原始指针 类驱动下端口指针HOOK PS/2键盘端口驱动i8042prt处理硬件中断 USB键盘驱动Kbdhid处理HID设备 HOOK键盘中断服务例程KeyboardInterruptService 从更底层拦截键盘数据 反过滤与IDT HOOK技术 IDT基础 IDT(Interrupt Descriptor Table)包含0-255中断号和处理函数 键盘中断通常为0x93 可通过sidt指令获取IDT基址 IDT HOOK实现步骤 获取IDT中键盘中断处理函数地址 计算函数偏移量 关闭写保护(CR0寄存器) 构造新的段描述符并修改门描述符中的段选择子 跳转到自定义处理函数 GDT构造技巧 找到空闲的GDT条目(如0xA8或0x4B) 拷贝现有描述符并修改基地址 确保描述符权限设置正确(0~3bit为0) 总结 键盘过滤技术涉及多个层次: 设备栈层面:绑定类驱动或端口驱动 函数指针层面:HOOK派遣函数或中断服务例程 系统表层面:修改IDT或GDT实现深层拦截 关键技术点: 理解Windows键盘处理流程和设备栈结构 掌握IRP处理机制和完成例程使用 熟悉IDT/GDT结构和修改方法 注意驱动卸载时的安全性处理 更底层的HOOK通常更难以检测和绕过,但也需要更深入的系统知识。