首页
社区
课程
招聘
[原创]按键监控技术
发表于: 2021-11-21 10:19 30252

[原创]按键监控技术

2021-11-21 10:19
30252

关于如何挂钩子,请看:常见的几种DLL注入技术中后面的全局钩子注入,后面有对钩子技术的解释。要用钩子技术实现按键监控,整体过程和挂全局钩子是一样的,只是一些参数和回调函数设置不同罢了。

想要通过全局键盘钩子来实现对键盘消息进行截获,就需要当调用SetWindowHookEx的时候,将参数idHook,也就是安装的钩子类型设置为WH_KEYBOARD,此时的回调函数就是KeyboardProc,通过该回调函数就可以实现按键监控,该函数定义如下:

指定钩子过程用于确定如何处理消息的代码。如果代码小于零,则钩子过程必须将消息传递给CallNextHookEx函数而无需进一步处理,并且应该返回CallNextHookEx返回的值。此参数可以是以下值之一。

HC_ACTION:

wParam和lParam参数包含有关击键消息的信息。

HC_NOREMOVE:

wParam和lParam参数包含有关击键消息的信息,并且击键消息尚未从消息队列中删除。

0-15

指定重复计数。该值是由于用户按住键而重复击键的次数。

16-23

指定扫描代码。该值取决于OEM。

24

指定该键是扩展键,如功能键还是数字键盘上的键。如果该键是扩展键,则该值为1;否则,它是0。

25-28

保留的。

29

指定上下文代码。如果ALT键按下,则该值为1;否则,它是0。

30

指定上一个键状态。如果在发送消息之前按键已按下,则该值为1;如果钥匙向上,则为0。

31

指定转换状态。如果按下该键,则该值为0,如果释放该键,则该值为1。

由此可知,当code等于HC_ACTION的时候,lParam包含了消息的信息,此时可以通过GetKeyNameText来获得消息,该函数定义如下:

因此,通过设置回调函数和GetKeyNameText函数就可以截获按键名,就可以知道此时键盘按下的按键是哪一个,具体代码如下

可以看到,当钩子函数成功挂载以后,所有的按键消息将被监控到

想要实现按键监控,可以利用原始输入模型直接从输入设备上获取数据,该方法相比于全局键盘钩子技术更为底层有效,功能也更为强大。但是,在默认情况下,应用程序不接收原始输入,也就是不接收WM_INPUT消息,所以首先要做的就是通过RegisterRawInputDevices来注册原始输入设备,该函数定义如下:

其中RAWINPUTDEVICE结构体定义如下:

注册设备的时候,RAWINPUTDEVICE中的usUsagePage(设备类)和usUsage(设备类内具体设备)说明了它所希望接收设备的类别

键盘设备:usUsagePage == 0x1,usUsage == 0x06

鼠标设备:usUsagePage == 0x01,usUsage == 0x02

因此,想要接收WM_INPUT消息,就需要通过指定对应的usUsagePage和usUsage来注册输入设备

成功注册设备以后,接下来就需要使用GetRawInputData函数来从设备中获取原始输入,该函数的定义如下:

命令标志,此参数可以是以下值之一

RID_HEADER:从RAWINPUT结构获取头信息

RID_INPUT:从RAWINPUT结构获取原始数据

通过这个函数,就可以将接收到的消息保存在pData中,其中RAWINPUT结构体定义如下:

RAWINPUTHEADER类型的header保存了原始输入的信息,该结构体定义如下:

当dwType表示原始输入的类型,当它为RIM_TYPEBOARD的时候表示的就是键盘的原始输入。

要获取的键盘消息则在RAWKEYBOARD的keyboard中,RAWKEYBOARD的定义如下:

表示相应的窗口消息

WM_KEYDOWN:普通按键消息

WM_SYSKEYDOWN:系统按键消息

由于这种键盘监控的方法需要用到窗口,所以需要创建窗口程序,接下来就看看在VS2017中如何使用资源来创建窗口程序。

首先新建一个Win32应用程序,并把自动生成的文件删除,只留下.cpp文件

接着新增对话框的资源

此时会生成resource.h和MonitorKeyboard.rc,打开MonitorKeyboard.rc文件就会出现创建的对话框,删除创建的对话框中的按钮,并把对话框的ID改为IDD_DIALOG_MAIN

此时打开resource.h文件,就会看到IDD_DIALOG_MAIN的宏定义被添加进去了

此时只需要在.cpp文件中将resource.h文件引入就可以使用创建的对话框,而要创建窗口就需要使用DialogBox函数,该函数定义如下:

其中的lpDialogFunc函数是用来处理接收到的消息的,该函数定义如下

根据uMsg所代表的消息的不同,就可以在该函数中可以做出不同的处理。

对于接收到的不同类别的消息,如果要在该函数中处理,则返回值需要为TRUE,否则返回值应该为FALSE

根据以上内容最终可以使用以下代码来实现键盘按键监控

最终可以看到按键操作被监控到了,并以虚拟键码的形式弹出来了

相对于用户层的键盘监控,驱动层的监控更加底层,它可以绕过绝大多数的保护。

按键操作IRP在驱动层会经过分层驱动设备栈,对于同一设备栈中,IRP会从栈顶一直传递到栈底,且新添加的设备总是附加在设备栈的顶部,也就是说新添加的设备可以更先获取IRP。

过滤驱动的实现便是基于此:

分层驱动中在加一层而不影响它的上下层,以过滤它们之间的数据,对数据或行为进行安全的控制。

过滤是通过设备绑定实现的。

键盘过滤驱动是工作在异步模式下的,为了得到一个按键操作,它首先需要发送IRP_MJ_READ到驱动的设备栈,驱动会立刻完成这个IRP,并将刚按下的键的相关数据作为该IRP的返回值返回。

键盘过滤驱动的按键记录就是基于这个原理。驱动程序创建一个键盘设备,将它附加在键盘类KbdClass设备栈上,此时该设备就是设备栈的栈顶。当有键盘按下IRP消息的时候,该设备最先接收到IRP消息,此时就可以设置完成回调函数,向下传递按键信息,获取按键信息。该键盘驱动设备就是一个过滤驱动设备,附加在键盘类驱动设备栈之上。

具体的创建过滤驱动设备的实现流程如下:

调用IoCreateDevice创建FILE_DEVICE_KEYBOARD键盘设备,并调用IoCreateSymbolicLink函数为该设备创建一个符号链接

调用IoGetDeviceObjectPointer函数获取KeyboardClass0驱动设备对象

调用IoAttachDeviceToDeviceStack函数将创建的键盘设备附加到KeyboardClass0设备对象所在的设备栈顶之上

IoGetDeviceObjectPointer函数的定义如下:

IoAttachDeviceToDeviceStack函数的定义如下:

该函数的返回值是原来设备栈上的栈顶设备,也就是当前设备栈上附加设备的下一个设备

想要截获键盘输入,重点是截获操作系统发给键盘的IRP读请求。当键盘收到IRP读请求时,等待用户输入,若用户输入,则把用户输入的数据填充到IRP读请求中,在把这个IRP读请求发送给操作系统。自定义的键盘过滤驱动设备最先捕获到这个IRP读请求,但是里面并没有按键数据。解决方法是设置一个回调函数,然后,将IRP读请求继续往下传递。等到键盘的IRP读请求处理完毕之后,再去执行先前设置的回调函数,从中获取键盘信息。

在IRP读请求中具体的处理流程如下:


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-12-2 09:40 被1900编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (9)
雪    币: 3144
活跃值: (1624)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
不错,可惜是老技术,我他喵羡慕现在的00后,上来文章大篇列子一堆,学习一点不废劲~
2021-11-22 04:21
1
雪    币: 22413
活跃值: (25361)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
3
shun其自然 不错,可惜是老技术,我他喵羡慕现在的00后,上来文章大篇列子一堆,学习一点不废劲~
可惜我是90后
2021-11-22 08:21
0
雪    币: 4749
活跃值: (4296)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
NtUserGetKeyState
NtUserGetAsyncKeyState
NtUserGetKeyboardState
NtUserAttachThreadInput
NtUserGetRawInputBuffer
NtUserGetRawInputData
NtUserRegisterHotKey
2021-11-25 10:46
0
雪    币: 48
活跃值: (1104)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
厉害了
2021-11-27 00:37
0
雪    币: 6977
活跃值: (1786)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
过滤驱动的缺点就是第一个按键监控不到,卸载时需要等一个按键
可以去hook kbdclass中的回调
2021-11-27 16:25
0
雪    币: 1525
活跃值: (3422)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
第二种原始设备模型可以做成dll,在窗口程序里面加载dll检测吗
2021-12-5 16:23
0
雪    币: 22413
活跃值: (25361)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
8
tmflxw 第二种原始设备模型可以做成dll,在窗口程序里面加载dll检测吗
可以的
2021-12-9 09:17
0
雪    币: 1525
活跃值: (3422)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
1900 可以的
可以区分是按键模拟还是真实硬件的吗
2021-12-9 13:02
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
软键盘输入可以进行拦截吗?是不是和9楼的问题是一样的?
2022-2-25 16:33
0
游客
登录 | 注册 方可回帖
返回
//