钩子函数、注册函数、回调函数,他们的概念其实是一样的。
钩子函数,顾名思义,就是把我们自己实现的hook函数在某一时刻挂接到目标挂载点上。
- hook函数,就是我们自己实现的函数,函数类型与挂载点匹配(返回值,参数列表)
- 挂接,也就是hook或者叫注册(register),使得hook函数对目标可用
- 目标挂载点,也就是挂我们hook函数的地方(我们想在这个目标点实现我们自己的功能)
首先来说说windows 消息Hook,这个消息Hook就是我们常用的通过SetWindowsHookEx来设置一个Hook,这个函数通过将这个Hook插入到Hook链的最前端,而发送给我们已经Hook了的窗口的消息首先会被我们的Hook函数截获,也就是我们优先于窗体捕获到消息。
Windows Message Hook 可以实现全局Hook和局部Hook。局部Hook是在自身进程中创建一个Hook,可以用来捕获自身进程中的消息;而全局Hook是可以捕获操作系统下所有的指定的消息,需要借助于DLL来实现。全局钩子必须要使用DLL,DLl存放了钩子函数的代码。在操作系统中安装了全局钩子后,只要进程收到可以发出钩子的消息后,全局钩子的DLl文件会被操作系统自动或强行地加载到该进程中。可以发现设置消息钩子也是一种导入DLl的方法。
其实钩子函数也就3个:
(1)设置钩子:SetWindowsHookEx 将用户定义的钩子函数添加到钩子链中, 也就是我们的注册钩子函数
(2)释放钩子:UnhookWindowsHookEx
(3)继续钩子:CallNextHookEx
在线程级的钩子中经常用到 GetCurrentThreadID 函数来获取当前线程的 ID
在没有钩子函数的情况下windows程序运行机制
键盘输入 --> 系统消息队列 --> 对应应用程序的消息队列 --> 将消息发送到对应的窗口中
在有了钩子函数的情况下windows程序运行机制
键盘输入 --> 系统消息队列 --> 对应应用程序消息队列 --> 将消息发送到钩子链中 --> 消息一一调用完毕所有的钩子函数(需要调用CallNextHookEx函数才能将消息传递下去) --> 将消息发送到对应的窗口中
1.SetWindowsHookEx
HHOOK SetWindowsHookEx(
int idHook, // hook type(钩子类型,即它处理的消息类型)
HOOKPROC lpfn, // hook procedure(回调函数的地址)
HINSTANCE hMod, // handle to application instance(实例句柄,标识包含lpfn所指的子程的DLL)
DWORD dwThreadId // thread identifier(线程ID)
);
第一个参数为钩子的类型,钩子的类型一共还有14种,表示在什么时机调用钩子。下面会介绍。
第二个参数为钩子的子程,实际上就是一个回调函数的地址,当相应的事件发生时系统就会调用这个回调函数。(就是我们要对这个钩子做的事)
第三个参数为这个钩子绑定的进程的实例句柄,即包含回调函数的DLL的实例句柄,如果dwThreadId 标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为NULL。 可以很简单的设定其为本应用程序的实例句柄。(对全局可以设为None)
第四个参数为线程的ID,若是线程钩子可以使用GetCurrentThreadId来获得当前线程的ID(若为全局钩子这个参数设置为0)
返回值:若调用成功,返回的是这个钩子过程的句柄;否则返回NULL
2.UnhookWindowsHookEx
钩子在使用完之后需要用UnhookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,1只有一个参数。函数原型如下:
UnhookWindowsHookEx( HHOOK hhk );
函数成功返回TRUE,否则返回FALSE。
3.CallNextHookEx
在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个 SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值, 返回值的类型依赖于钩子的类型。
LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam;);
hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。
NCode为传给钩子过程的事件代码。
wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。
钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。
钩子处理函数(回调函数)
钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
钩子子程必须按照以下的语法:
LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
);
HookProc是应用程序定义的名字。
nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。
wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。
钩子类型
每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。
1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。
WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。
CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
2、WH_CBT Hook
在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
2. 完成系统指令;
3. 来自系统消息队列中的移动鼠标,键盘事件;
4. 设置输入焦点事件;
5. 同步系统消息队列事件。
Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。
3、WH_DEBUG Hook
在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。
4、WH_FOREGROUNDIDLE Hook
当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。
5、WH_GETMESSAGE Hook
应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
6、WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。
WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。
WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。
WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被注射到任何行程位址空间。
7、WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。
WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。
WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行程位址空间。
8、WH_KEYBOARD Hook
在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。
9、WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
10、WH_MOUSE Hook
WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。
11、WH_MOUSE_LL Hook
WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
13、WH_SHELL Hook
外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
WH_SHELL 共有5钟情况:
1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
2. 当Taskbar需要重画某个按钮;
3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
4. 当目前的键盘布局状态改变;
5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。
实例:
import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD
#1.使用windll:使用windll声明user32与kernel类型的变量。使用相应DLL提供的函数时,
#格式为user32.API名称或kernel.API名称。
user32 = windll.user32
kernel32 = windll.kernel32
#2.声明变量:在win32API内部定义并使用的变量值,可以通过MSDN或者网络搜索轻松获取,
#将其声明为变量并事先放入变量
WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
CTRL_CODE = 162
#3.定义类:定义拥有挂钩与拆钩功能的类
class KeyLogger:
def __init__(self):
self.lUser32 = user32
self.hooked = None
#4.定义挂钩函数:使用user32DLL的SetWindowsHookExA函数设置钩子。 要监听的事件为
#WHKEYBOAD_LL,范围设置为操作系统中运行的所有线程
def installHookProc(self, pointer):
#mar = self.lUser32.LoadLibraryW("user32.dll");
self.hooked = self.lUser32.SetWindowsHookExA(
WH_KEYBOARD_LL, #要监控的hook类型
pointer, #hook处理回调函数指针
None,
0
)
print('self.hooked=',self.hooked)
if not self.hooked:
return False
return True
#5.定义拆钩函数:调用user32Dll的SetWindowsHookEx()函数,拆除之前设置的钩子。
#钩子会大大增加系统负荷,调用完必须拆除
def uninstallHookProc(self):
if self.hooked is None:
return
self.lUser32.UnhookWindowsHookEx(self.hooked)
self.hooked = None
#6.获取函数指针:用来取得hook回调函数的指针。若想注册钩子过程(回调函数),必须传入函数指针。ctypes为此提供
#了专门的方法。通过CFUNCTYPE()函数指定SetWindowshookExA()函数所需要的钩子过程
#的参数与参数类。通过CMPFUNC()函数获取内部声明的函数指针
def getFPTR(fn):
CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
#CFUNCTYPE 定义回调函数类型,参数的第一个参数为返回值类型后面的参数为回调函数传递的参数类型a
return CMPFUNC(fn)
#7.定义钩子过程:钩子过程是一种回调函数,指定事件发生时,调用其执行相应处理。若
#到来的消息类型是WM__KEYDOWN,则将消息值,输出到屏幕;若消息与<CTRL>键的值一致,
#则拆除钩子。处理完毕后,将控制权限让给勾连中的其他钩子过程(CallNextHookEx()函数)
def hookProc(nCode, wParam, lParam):
print ('nCode=',nCode, 'wParam=',wParam, 'lParam=',lParam)
print(user32.GetKeyState(0x45))
if wParam is not WM_KEYDOWN:
return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
hookedKey = chr(0xFFFFFFFF&lParam[0]) #chr函数返回指定的字符代码的对应字符
print(hookedKey)
if(CTRL_CODE == int(0xFFFFFFFF&lParam[0])):
print("Ctrl pressed, call uninstallHook()")
keyLogger.uninstallHookProc()
MSG(0)
#sys.exit(-1)
return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
#8.传递消息:GetMessageA()函数函数监视队列,消息进入队列后取出消息,并传递给勾连中的
#第一个钩子
def startKeyLog():
msg = MSG()
user32.GetMessageA(byref(msg), 0, 0, 0)
#9.启动消息钩取,首先创造KeyLogger 类,然后installHookProc()函数设置钩子,同时
#注册钩子过程回调函数。最后调用startKeyLog()函数,将进入队列的消息传递给勾连
keyLogger = KeyLogger()
pointer = getFPTR(hookProc)
if keyLogger.installHookProc(pointer):
print("installed keyLogger")
startKeyLog()