自己写一个键盘钩子程序来监视键盘输入

钩子是Windows的消息监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们“

上面就是WIN32API手册中对钩子的描述。大概就是说钩子可以用来截获系统的消息。那么,要写一个钩子程序,肯定要先确定钩子的类型,钩子是有很多类型的,不同类型的钩子可以监视不同类型的消息。

钩子名称                                            监视消息的类型和时机
WH_CALLWNDPROC系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程序
WH_CALLWNDPROCRET窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程序
WH_GETMESSAGE应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。函数从程序的消息队列获取一个消息后调用钩子函数。
WH_KEYBOARD每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息,则调用钩子函数。也就是说这个钩子可以监视键盘消息。
WH_MOUSEWH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息
WH_HEADWARE

每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是鼠标消息,则调用钩子函数。就是说这个钩子用来监视鼠标的消息。

WH_MSGFILTER当用户对对话框,滚动条和菜单有所操作的时候,系统在发送对应的消息之前调用钩子函数,这种钩子只能是局部的
WH_SYSMSGFILTER和WH_MSGFILTER一样,但是是系统范围的
WH_SHELL当Windows shell程序准备接收一些通知事件前调用钩子函数,如shell被激活和重画等
WH_DEBUG用来给其他钩子函数除错
WH_CBT当基于计算机的训练(CBT)事件发生时调用钩子函数
WH_JOURNALRECORD日志记录钩子,用来记录发送给系统消息队列的所有消息
WH_JOURNALPLAYBACK日志回放钩子,用来回放日志记录钩子的系统事件
WH_FOREGROUNDLDLE系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些优先级很低的任务

钩子分为局部钩子和远程钩子,局部钩子只能属于自身进程的事件,而远程钩子的除了可以钩到自己的还可以钩到其他进程里面的消息,远程的钩子又分为两种,基于线程的和系统范围的。基于线程的远程钩子用来捕获其他进程中某一特定的事件,而系统范围的远程钩子可以捕捉系统中所有进程中发生的事件消息。

局部的和远程的钩子还有一个不同的地方就是局部的钩子的钩子函数可以安装在进程中,而远程的钩子的钩子函数自能安装在DLL中。因为远程钩子要获得其他进程的消息,如果安装在本进程中,又因为进程之间的地址空间是隔离开来的,这样钩子就无法监视其他的进程的消息了。而DLL是可以插入到其他进程的地址空间中去的,所以远程钩子一般放在DLL中。但是也有两个例外,分别是日志记录钩子和日志回放钩子。这两个明明是远程钩子,但是它们的钩子函数可以安装在进程里面,并且不碍事。可能是因为这两个钩子函数要监视一些比较底层的消息,所以函数的调用可能是从Windows内部发起的。

那么,了解了一些钩子的基本概念之后,我们就可以开始准备写钩子程序了。所以来看一下一般的钩子程序的结构:

  • 主程序:用来实现界面或者其他功能
  • 钩子回调函数:用来接收系统发过来的消息
  • 钩子的安装和卸载模块

对于局部钩子来说,这三个模块可以都在一个可执行文件中。但是对于远程钩子来说,第二部分必须放在DLL中。

先来看一下DLL的汇编源代码:

		.386
		.model	flat,stdcall
		option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include		windows.inc
include		user32.inc
includelib	user32.lib
include		kernel32.inc
includelib	kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
			.data
hInstance	dd		?			;动态链接库模块实例句柄
			.data?
hWnd		dd		?			;接收消息的窗口的句柄 
hHook		dd		?			;钩子句柄
dwMessage	dd		?			;要发送的消息的类型
szAscii		db		4	dup (?)			;按键的ASCII码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
			.code
DllEntry	proc	_hInstance,_dwReason,_dwReserved				;DLL入口

			push	_hInstance
			pop		hInstance
			mov		eax,TRUE
			ret
DllEntry	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;键盘钩子回调函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
HookProc	proc	_dwCode,_wParam,_lParam							;消息的处理方式,按键的虚拟码,按键的其他信息
			local	@szKeyState[256]:byte
			
			invoke	CallNextHookEx,hHook,_dwCode,_wParam,_lParam	;调用下一个钩子的函数
			invoke	GetKeyboardState,addr @szKeyState				;获取键盘所有按键的当前状态
			invoke	GetKeyState,VK_SHIFT							;获取SHIFT键的当前状态
			mov		@szKeyState + VK_SHIFT,al
			mov		ecx,_lParam
			shr		ecx,16
			invoke	ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0		;将按键信息转换为ASCII码,并返回szAscii缓冲区中的字符的数量
			mov		byte ptr szAscii [eax],0						;在缓冲区末尾加上字符个数
			invoke	SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL		
			xor		eax,eax											;返回0,表示将消息转发给目标窗口(不是我们自己的窗口)
			ret
			
HookProc	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;安装钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
InstallHook	proc	_hWnd,_dwMessage									;自己的窗口的句柄,自定义的消息类型
			push	_hWnd
			pop		hWnd
			push	_dwMessage
			pop		dwMessage
			invoke	SetWindowsHookEx,WH_KEYBOARD,addr HookProc,\	;安装钩子函数,钩子类型,回调函数,DLL句柄,监控所有进程
					hInstance,NULL
			mov		hHook,eax
			ret
			
InstallHook	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;卸载钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
UninstallHook	proc
				invoke	UnhookWindowsHookEx,hHook
				ret
UninstallHook	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
				End		DllEntry
			

可以看到我们把安装和卸载钩子的模块也放到了钩子回调函数的模块里面。因为钩子句柄在钩子回调函数和卸载钩子的时候都要用到,而钩子句柄是在安装钩子的时候得到的,为了方便就把它们放一个模块了。

SetWindowsHookEx:

invoke    SetWindowsHookEx,idHook,lpHookProc,hInstance,dwThreadID
.if       eax
          mov    hHook,eax
.endif

该函数安装一个钩子。第一个参数指出钩子类型,第二个参数指出钩子回调函数的地址,第三个参数指出DLL的实例句柄,第四个参数指出钩子想监视的线程ID,ID可以指定为本进程的线程ID和其他进程的线程ID。如果这个参数为NULL,那么这个钩子会被解释成系统范围的,可以用来监视所有的进程以及它们的线程。

钩子安装成功返回一个钩子的句柄。

UnhookWindowsHookEx:

invoke    UnhookWindowsHookEx,hHook

该函数卸载钩子,参数是钩子的句柄。

接下来就是最重要的钩子回调函数了。键盘钩子回调函数的一般结构是这样写的:

HookProc    proc    dwCode,wParam,lParam
            invoke  CallNextHookEx,hHook,_dwCode,_wParam,_lParam
            ;处理消息的代码
            mov     eax,返回值
            ret
HookProc    endp

一般是这么写的,当然只要能实现功能,不这么写也行。但是函数的格式是有要求的的,必须得是三个参数。三个参数的含义如下:

  • dwCode:键盘消息的处理方式。如果是HC_ACTION,表示收到一个正常的击键消息,如果是HC_MOREMOVE,表示对应消息并没有从消息队列中移去。
  • wParam:   按键的虚拟码
  • lParam:按键的重复次数,扫描码和标志等数据,不同数据位的定义如下:
    • 0~15位:按键的重复次数。
    • 16~23位:按键的扫描码。
    • 24位:按键是否是扩展键(如:F1和F2等,还有小数字键盘等),如果此位是1表示按键时扩展键。
    • 25~28:未定义。
    • 29位:如果Alt键在按下状态,此位置为1,否则为0。
    • 30位:按键的原先状态,消息发送前按键原来是按下的,此键为1,否则为0。
    • 31位:按键当前的动作,如果是按键按下,那么此位被设置为0,按键释放的话被设置为1。

然后这里还要介绍一个很重要的概念就是:钩子链。

WIndows中可以存在多个同种类型的钩子。这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。当一个事件发生的时候,WIndows调用最后安装的钩子,然后由这个钩子的回调函数发起调用下一个钩子的动作,Windows收到这个动作后后,再从链表中取出下一个钩子的地址并将调用传递下去。

大概就是说同种类型的钩子在一条链子上,然后消息会依次从最后面那个钩子开始向前传,但这个动作需要我们在回调函数中完成。因为你不知道你的钩子是什么时候被放到钩子链里面的,所以我们最好还是把消息传递下去。

CallNextHookEx:

invoke    CallNextHookEx,hHook,dwCode,wParam,lParam

这个函数就是调用下一个钩子的函数,第一个参数是当前钩子的句柄,后面三个参数就是当前钩子回调函数的三个参数。

我们写的这个是一个键盘钩子,所以我们要对获取的键盘消息先进行一个处理,然后再发给我们的主程序。先调用GetKeyboardState函数获取当前键盘的状态,然后因为我们获取的消息是按键的虚拟码和扫描码,我们要显示字符就得先把这些转换为ASCII码。ToAscii函数可以完成这个工作。最后我们再调用SendMessage函数将转换后的按键信息发送给我们的主窗口,主窗口的句柄是在主窗口调用DLL中的钩子安装程序的时候传递到DLL中的。消息类型是我们自己定义的。

然后钩子的DLL就写完了,但是我们在编译链接DLL的时候要注意一个地方就是,链接选项要指定是共享数据段的。所以链接的选项要使用  /section:.bss,S

做完这些后就只剩下主窗口的程序了。主窗口程序的结构就比较简单了,初始化的时候调用一下钩子安装函数,然后等待钩子消息来,有消息就显示到窗口,退出的时候就调用钩子卸载函数。

下面是主窗口程序的资源文件:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include	<resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define		ICO_MAIN		0x1000
#define		DLG_MAIN		0x1000
#define		IDC_TEXT		0x1001
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN	ICON			"Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN	DIALOG	208,130,234,167
STYLE		DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION		"键盘钩子"
FONT 9,	"宋体"
BEGIN
	EDITTEXT IDC_TEXT,5,5,224,158,ES_MULTILINE | ES_AUTOVSCROLL | WS_BORDER | WS_VSCROLL | WS_TABSTOP | ES_READONLY
END

下面是汇编源代码:

		.386
		.model	flat,stdcall
		option	casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include??t?¨?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include			windows.inc
include			user32.inc
includelib		user32.lib
include			kernel32.inc
includelib		kernel32.lib
include			Hookdll.inc
includelib		Hookdll.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN		equ		1000h
DLG_MAIN		equ		1000h
IDC_TEXT		equ		1001h
WM_HOOK			equ		WM_USER + 100h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;′ú???
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.code
_ProcDlgMain	proc	uses ebx edi esi hWnd,wMsg,wParam,lParam
				local	@dwTemp
				
				mov		eax,wMsg
				.if		eax == WM_CLOSE
						invoke	UninstallHook					
						invoke	EndDialog,hWnd,NULL						
				.elseif	eax == WM_INITDIALOG		
						invoke	InstallHook,hWnd,WM_HOOK		
						.if		! eax
								invoke	EndDialog,hWnd,NULL
						.endif
				.elseif	eax == WM_HOOK
						mov		eax,wParam
						.if		al == 0dh						
								mov		eax,0a0dh
						.endif
						mov		@dwTemp,eax
						invoke	SendDlgItemMessage,hWnd,IDC_TEXT,\
								EM_REPLACESEL,0,addr @dwTemp
				.else 
						mov		eax,FALSE
						ret
				.endif
				mov		eax,TRUE
				ret
_ProcDlgMain	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
				invoke	GetModuleHandle,NULL
				invoke	DialogBoxParam,eax,DLG_MAIN,NULL,\
						offset _ProcDlgMain,NULL
				invoke	ExitProcess,NULL
end 	start
						
						
				
				
				

编译链接出来后运行:

然后我们随便在什么地方输入键盘:

 

 

除了一些特殊的字符显示不出来,勉强还是能看的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值