简介 在驱动中模拟键盘按键的方法,包含 写端口
调用KeyboardClassServiceCallback函数
虚拟键盘
等。 其中 写端口
实现起来最简单,但是只能针对PS/2键盘。而 KeyboardClassServiceCallback
函数未导出, 需要我们不同系统去搜索不同的特征码,兼容性不佳。最后一个 虚拟键盘
稳定性最好,但是复杂度特别高。
写端口 在DOS时代,当我们按下或者放开一个键时,就会产生一个键盘中断(在允许键盘中断的情况下),这样程序 会跳转到BIOS中的键盘中断处理程序去执行。
打开windows设备管理器,可以查看到键盘控制器由两个端口控制。其中 0x60
是数据端口,可以读出键盘 数据,而 0x64
是控制端口,用来发出控制信号。也就是说,从 0x60
号端口可以读此键盘的按键信息。当 从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高1位则表示是按下键还是释放键。当按下 时,最高位为0,称为通码,当释放时,最高位为1,称为断码。
所以我们可以向这个端口写入数据,来模拟按键。如下为示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #define I8042_DATA_PORT ((PUCHAR)0x60) #define I8042_CTRL_PORT ((PUCHAR)0x64) #define OBUFFER_FULL 0x01 BOOLEAN WaitKeyboardWrite () { INT i = 0 ; UCHAR c = 0 ; LARGE_INTEGER Interval = { 0 }; Interval.QuadPart = -50 * 1000 * 10L ; for (i = 0 ; i < 1000 ; ++i) { KeDelayExecutionThread(KernelMode, FALSE, &Interval); c = READ_PORT_UCHAR(I8042_CTRL_PORT); if ((c & OBUFFER_FULL) == OBUFFER_FULL) break ; } return i ? TRUE : FALSE; } void SendKeyScanCode (IN UCHAR Code) { WaitKeyboardWrite(); WRITE_PORT_UCHAR(I8042_CTRL_PORT, 0xD2 ); WaitKeyboardWrite(); WRITE_PORT_UCHAR(I8042_DATA_PORT, Code); }
回调函数 在驱动 kbdclass.sys
中,有个函数 KeyboardClassServiceCallback
,作用是往 读取按键的IRP
中 添加按键信息,并完成这个 IRP
,这个函数的声明如下
1 2 3 4 5 6 VOID KeyboardClassServiceCallback ( _In_ PDEVICE_OBJECT DeviceObject, _In_ PKEYBOARD_INPUT_DATA InputDataStart, _In_ PKEYBOARD_INPUT_DATA InputDataEnd, _Inout_ PULONG InputDataConsumed ) ;
参数 DeviceObject
为任意键盘设备,一般情况下我们使用 L"\\Device\\KeyboardClass0"
这个设备,InputDataStart
和 InputDataEnd
表示输入的按键信息的 起始
和 结束
地址,而 InputDataConsumed
表示按键信息的数量。按键信息结构体如下所示
1 2 3 4 5 6 7 typedef struct _KEYBOARD_INPUT_DATA { USHORT UnitId; USHORT MakeCode; USHORT Flags; USHORT Reserved; ULONG ExtraInformation; } KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
我们只关注 MakeCode
和 Flags
两个参数,其中 MakeCode
是按键的扫描码,而 Flags
表示按键的动作,KEY_MAKE
和 KEY_E0
表示 普通键
和 扩展键
的 按下
,KEY_BREAK
和 KEY_E1
表示相应的 弹起
动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Keyboard Scan Codes (Numerical Order) ---------------+---------------+---------------+---------------+--------------- HEX DEC keys |HEX DEC keys |HEX DEC keys |HEX DEC keys |HEX DEC keys ---------------+---------------+---------------+---------------+--------------- |10 16 Q |20 32 D |30 48 B |40 64 F6 01 1 ESC |11 17 W |21 33 F |31 49 N |41 65 F7 02 2 1 |12 18 E |22 34 G |32 50 M |42 66 F8 03 3 2 |13 19 R |23 35 H |33 51 , |43 67 F9 04 4 3 |14 20 T |24 36 J |34 52 . |44 68 F10 05 5 4 |15 21 Y |25 37 K |35 53 / |45 69 Num 06 6 5 |16 22 U |26 38 L |36 54 R Shift|46 70 Scroll 07 7 6 |17 23 I |27 39 ; |37 55 PrtSc |47 71 Home 08 8 7 |18 24 O |28 40 ' |38 56 Alt |48 72 Up 09 9 8 |19 25 P |29 41 ` |39 57 Space |49 73 PgUp 0A 10 9 |1A 26 [ |2A 42 L Shift|3A 58 Caps |4A 74 - 0B 11 0 |1B 27 ] |2B 43 \ |3B 59 F1 |4B 75 Left 0C 12 - |1C 28 |2C 44 Z |3C 60 F2 |4C 76 Center 0D 13 = |1D 29 CTRL |2D 45 X |3D 61 F3 |4D 77 Right 0E 14 bs |1E 30 A |2E 46 C |3E 62 F4 |4E 78 + 0F 15 Tab |1F 31 S |2F 47 V |3F 63 F5 |4F 79 End ---------------+---------------+---------------+---------------+--------------- 50 80 Down | | | | 51 81 PgDn | | | | 52 82 Ins | | | | 53 83 Del | | | | ---------------+---------------+---------------+---------------+---------------
内存搜索 回调函数 KeyboardClassServiceCallback
并没有被导出,我们要想使用它,就只能通过特征码搜索, 首先需要找到 kbdclass.sys
驱动的基址,我们可以使用一个已导出但未文档的函数来获取
1 2 3 4 5 6 7 8 9 10 11 extern POBJECT_TYPE* IoDriverObjectType;NTKERNELAPI NTSTATUS ObReferenceObjectByName ( IN PUNICODE_STRING ObjectName, IN ULONG Attributes, IN PACCESS_STATE AccessState, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext, OUT PVOID* Object) ;
获取 键盘驱动
和 键盘设备
的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 PDRIVER_OBJECT KeyboardDriver = NULL ; PDEVICE_OBJECT KeyboardDevice = NULL ; PFILE_OBJECT KeyboardFile = NULL ; NTSTATUS GetKeyboardDriverDevice () { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING KbdDriverName = RTL_CONSTANT_STRING(L"\\Driver\\kbdclass" ); UNICODE_STRING KbdDeviceName = RTL_CONSTANT_STRING(L"\\Device\\KeyboardClass0" ); Status = IoGetDeviceObjectPointer( &KbdDeviceName, FILE_ALL_ACCESS, &KeyboardFile, &KeyboardDevice); if (!NT_SUCCESS(Status)) return Status; Status = ObReferenceObjectByName(&KbdDriverName, OBJ_CASE_INSENSITIVE, NULL , GENERIC_ALL, *IoDriverObjectType, KernelMode, NULL , &KeyboardDriver); if (!NT_SUCCESS(Status)) { ObDereferenceObject(&KeyboardFile); return Status; } return STATUS_SUCCESS; }
搜索回调函数的代码,其中特征码是通过 WinDbg
加载操作系统符号查找到的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef void (*KCSC) (PDEVICE_OBJECT, PKEYBOARD_INPUT_DATA, PKEYBOARD_INPUT_DATA, PULONG) ;KCSC pKeyboardClassServiceCallback = NULL ; NTSTATUS SearchKeyboardClassServiceCallback () { ULONG Index = 0 ; PUCHAR BaseAddr = KeyboardDriver->DriverStart; ULONG DrvSize = KeyboardDriver->DriverSize; for (Index = 0 ; Index < DrvSize; Index++) { if (*(PULONG)(BaseAddr + Index) != 0x8b55ff8b ) continue ; if (*(PULONG)(BaseAddr + Index + 4 ) != 0x8b5151ec ) continue ; if (*(PULONG)(BaseAddr + Index + 8 ) != 0x65830845 ) continue ; if (*(PULONG)(BaseAddr + Index + 12 ) != 0x8b530008 ) continue ; if (*(PULONG)(BaseAddr + Index + 16 ) != 0x5d2b105d ) continue ; pKCSC = (KCSC)(BaseAddr + Index); break ; } if (pKCSC != NULL ) return STATUS_SUCCESS; return STATUS_UNSUCCESSFUL; }
在WinXPSp3系统中,查找特征码步骤如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0: kd> x kbdclass!KeyboardClassServiceCallback f78b6192 kbdclass!KeyboardClassServiceCallback (<no parameter info>) 0: kd> dd f78b6192 f78b6192 8b55ff8b 8b5151ec 65830845 8b530008 f78b61a2 5d2b105d 708b560c 14458b28 57002083 f78b61b2 15ff046a f78b6ea8 ff6c4e8d 8b6ea415 0: kd> u kbdclass!KeyboardClassServiceCallback l 0xa kbdclass!KeyboardClassServiceCallback: f78b6192 8bff mov edi,edi f78b6194 55 push ebp f78b6195 8bec mov ebp,esp f78b6197 51 push ecx f78b6198 51 push ecx f78b6199 8b4508 mov eax,dword ptr [ebp+8] f78b619c 83650800 and dword ptr [ebp+8],0 f78b61a0 53 push ebx f78b61a1 8b5d10 mov ebx,dword ptr [ebp+10h] f78b61a4 2b5d0c sub ebx,dword ptr [ebp+0Ch]
最后 KeyboardClassServiceCallback
函数的用法如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void SendKeyInputExample () { ULONG InputDataConsumed = 0 ; KEYBOARD_INPUT_DATA KbdInData[2 ] = { 0 }; KbdInData[0 ].MakeCode = 0x1E ; KbdInData[0 ].Flags = KEY_MAKE; pKeyboardClassServiceCallback( KeyboardDevice, &KbdInData[0 ], &KbdInData[1 ], &InputDataConsumed); KbdInData[0 ].Flags = KEY_BREAK; pKeyboardClassServiceCallback( KeyboardDevice, &KbdInData[0 ], &KbdInData[1 ], &InputDataConsumed); }