[转载]HOOK钩子教程

http://blog.sina.com.cn/s/blog_675049f701019ka9.html(原贴)

先留着,好好学一学!

原文地址:HOOK钩子教程作者:X_TK

在你读到这篇文章之前,也许你还已经读过不少关于HOOK钩子的教程,如果你已经成功HOOK上了,那么请阅读本博客更高级别的文章。如果你还没HOOK成功,相信本文能给你很大的帮助。如果阅读完本教程依然有疑问,请在评论中留言。本教程是基础教程,作者也是刚刚学会HOOK,文章中难免有错漏之处,敬请读者斧正。

至于作者写本文的原因,是在作者没有HOOK成功之前读过很多教程,却感觉这些教程都没写到点子上。没有把一些初学者常常遇到的问题说清楚,而在本文,作者会详细讲述这些问题(作者仅对X_TK博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。

另外,本教程所用语言为C/C++。

钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获window消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。其中最后装载的钩子最先获得消息。

以上便是对钩子的定义。

钩子能对系统中其他窗口的消息提前截取,相信很多人都对这项技术充满了向往,甚至觉得其深不可测。其实HOOK非常简单。

先来看一下设置钩子的API:SetWindowsHookEx

The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. An application installs a hook procedure to monitor the system for certain types of events. A hook procedure can monitor events associated either with a specific thread or with all threads in the system. This function supersedes the SetWindowsHook function.

这段话的意思大致是这个API函数会向钩子链(即一连串钩子)中安装一个钩子并处理指定的消息,可以安装在指定的进程或系统中的所有进程(全局钩子)。

再来看看函数的原型:

HHOOK SetWindowsHookEx(
int idHook, // type of hook to install 要安装的钩子的类型
HOOKPROC lpfn, // address of hook procedure 钩子函数的地址
HINSTANCE hMod, // handle of application instance 包含钩子函数模块的句柄
DWORD dwThreadId // identity of thread to install hook for 要安装钩子的线程的PID
);

其中,第一个参数idHook可以有以下取值:

WH_CALLWNDPROC//监视到达窗口前的消息
WH_CALLWNDPROCRET//监视窗口处理后的消息
WH_DEBUG//监视系统调用其他HOOK关联的HOOK子程
WH_GETMESSAGE//监视发送到窗体消息队列里的消息
WH_JOURNALPLAYBACK//全局HOOK,可以插入消息到消息队列
WH_JOURNALRECORD//全局HOOK,监视输入事件(键盘、鼠标等)
WH_KEYBOARD//键盘钩子
WH_MOUSE//鼠标钩子
WH_MSGFILTER//监视菜单、滚动条、消息框、对话框消息和切换窗口的组合键(Alt+Tab等)
WH_SHELL//接收系统中重要的通知(如窗口被产生、摧毁等)

由于作者能力有限,这里只选择一个取值进行举例:WH_GETMESSAGE,这种HOOK能监视到窗体的所有消息。

第二个参数lpfn是回调函数的地址,将在后文中详细解说其获取方法。

第三个参数hMod是包含第二个参数指向的函数的模块的句柄,也将在后文中详细解说。

最后一个参数dwThreadId是HOOK的目标进程的PID。

函数的返回值:若返回NULL,则钩子安装失败,可以通过GetLastError查询错误;若返回的不是NULL,那么就是安装的钩子的句柄。

下面就取WH_GETMESSAGE作为例子,对Windows自带的记事本程序安装钩子。

记事本进程名为notepad.exe,而安装钩子需要其PID,而每次启动记事本,其PID都是不一样的,那么下面介绍获取其PID的方法。

获取进程PID用的是GetWindowThreadProcessId这个API:

DWORD GetWindowThreadProcessId(

HWND hWnd, // handle of window 要获取的窗口句柄
LPDWORD lpdwProcessId // address of variable for process identifier 若此值不为NULL,API会将PID复制到这个参数所指向的DWORD型变量
);

返回值:线程的PID。

对于第二个参数,我们设为NULL即可,我们只需要取返回值就行了。

至于第一个参数,要求窗口句柄,而我们只知道进程名称。这时候就应该用FindWindow这个API了:

HWND FindWindow(

LPCTSTR lpClassName, // pointer to class name 窗体Class Name指针
LPCTSTR lpWindowName // pointer to window name 窗体标题指针
);

返回值:窗体句柄。

根据窗体标题来判读是很不可靠的,因此对于第二个参数,作者设为了NULL。

第一个参数要求窗体的Class Name,类型是LPCTSTR,只需输入字符串即可。那么怎么获取Notepad的Class Name呢?

现在要用到VC自带的工具spy++(没有的请自行下载spy++),打开spy++,按下Ctrl+M,弹出了如图的窗口:
    Finder Tool是一个箭靶的图案,拖动箭靶到记事本标题栏上,松开鼠标,窗体变成了如下的样子:

右侧显示了Class:Notepad,于是我们可以知道Class Name就是”Notepad”。

回到FindWindow API中,我们就可以这么写:

FindWindow(“Notepad”,NULL);

定义一个变量进行接收:

HWND notepadhandle;

notepadhandle=FindWindow(“Notepad”,NULL);

句柄到手了,PID自然可以拿到:

GetWindowThreadProcessId(notepadhandle,NULL);

返回的就是notepad.exe的PID了。

有了PID,SetWindowsHookEx的第四个参数就解决了。还有第二个和第三个呢?

由于目标进程不是本线程,所以HOOK的回调函数要封装在DLL中,定义如下:

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam);

在本例程中,我们的SetWindowsHookEx操作由动态链接库DLL完成,所以HOOK回调函数地址(第二个参数)直接写函数名即可。如果SetWindowsHookEx操作是由外部模块执行的,就应该先LoadLibrary获取Dll的Handle,再用GetProcAddress获取函数地址(注意函数名修饰问题),本文中这个不是重点,如果有这方面需要的,可以在评论里留言,作者将作详细答复(作者仅对X_TK博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。

所以第二个参数,回调函数的地址,我们直接写:HookProc。

第三个参数,就是包含HookProc的模块句柄了,本例程中执行操作的DLL就是包含HookProc的DLL,而在入口函数DllMain中:

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved);

hModule就是Dll的句柄,而参数中给出的hModule是HANDLE类型的,我们只需强制转换成HINSTANCE即可。

因此第三个参数即为:(HINSTANCE)hModule。

于是整个SetWindowsHookEx就写成:

SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));

该API函数返回值是HHOOK类型的,表示安装的钩子的句柄,定义一个全局变量接收即可:

HHOOK hooker;

hooker=SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));

上面的篇幅解释了怎么安装钩子。对各个参数进行了详细的介绍。安装钩子是HOOK的整个过程中相对重要的一步,如果在这步上面失败了,初学者便会失去信心。上文对记事本进行了钩子安装,如果对上文仍有疑问,或者按照上文的方法仍然安装失败,请在评论中留言,作者将给予帮助(作者仅对X_TK博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。

我们知道,钩子实际上是一个程序段,于是这个程序段就必不可少了,这个程序段是一个回调函数,是上一个钩子调用CallNextHookEx API之后系统调用的,因此传来的参数有可能是被上一个钩子处理过的,在处理消息过后,也应该CallNextHookEx以将消息传给下一个钩子(如果你想阻止该消息的传递,可以不调用该API)。下面介绍一下CallNextHookEx:

LRESULT CallNextHookEx(

HHOOK hhk, // handle to current hook 钩子句柄
int nCode, // hook code passed to hook procedure nCode参数,回调函数参数中有
WPARAM wParam, // value passed to hook procedure wParam参数,回调函数参数中有
LPARAM lParam // value passed to hook procedure lParam参数,回调函数参数中有
);

返回值:如果执行成功,返回的值是下一个钩子函数返回的值;如果执行失败,返回NULL。

nCode、wParam、lParam都来自于HookProc的参数。

返回值倒是值得一提,这个做个抽象的描述,先安装了Hook1,然后是Hook2,一直到Hookn(都是同一类型的钩子):

Windows—-Hookn—-……—-Hook2—-Hook1

—-Application

消息从Windows发出,从左到右经过多个钩子,最后一个钩子Hook1返回一个值,而这个值又被Hook2返回,一直返回到Hookn,再返回到Windows,然后消息传到Application。也就是说,回调函数要返回由CallNextHookEx返回的值。

综上,一个什么都不做的HookProc就可以写成这样:
extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

CallNextHookEx的解释就到这里,令很多人头疼的是回调函数中的三个参数,这些会在后面作详细介绍。现在先来看看另一个API:

BOOL UnhookWindowsHookEx(

HHOOK hhk // handle of hook procedure to remove 要卸载的HOOK的句柄
);

由于上文中我们用的是hooker来接收句柄的,卸载就可以写作:

UnhookWindowsHookEx(hooker);

卸载不是必要的,因为线程结束后系统会自动卸载,但是在不想处理消息之后,卸载钩子就显得重要了,卸载后,仍需返回CallNextHookEx的返回值。

接下来就是重点了,HookProc里面的三个参数。

首先是第一个,nCode,这个值是由你设定的钩子类型决定的。它用来告诉你该怎么处理这个发来的消息。

在上面的例程中,设定了WH_GETMESSAGE钩子,这个钩子中,nCode的取值有以下两种:

第一种,是HC_ACTION,HC_ACTION实际上就是0;

第二种,是小于0的取值,这个时候,钩子必须毫无修改地把消息传给下一个钩子。

至于第二个参数,wParam,这个值也是由你设定的钩子类型决定的,各种钩子各不相同。如WH_KEYBOARD钩子中wParam就是按键的virtual-key code,即虚拟键值。而在WH_GETMESSAGE钩子中,该参数指示传来的消息是否已经从消息队列中移除,有以下两个取值:

第一种,是PM_NOREMOVE,表示还没有从消息队列中移除;

第二种,是PM_REMOVE,表示已经从消息队列中移除。

至于第三个参数,lParam,和前两个参数一样,由钩子类型决定,而这个参数也是初学者眼中最难的一个参数。本钩子中,lParam的解释如下:

Points to an MSG structure that contains details about the message.

就是说,这个lParam实际上就是一个指针,指向包含传来的消息详情的结构体变量:MSG。

先来看看MSG的原型:

typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;

众多参数中,我们只关注message、wParam、lParam三个。

message是UNIT变量,它可以表示各种消息,数不胜数,这里只列出几个作为示例:

WM_CLOSE
WM_KEYDOWN
WM_PAINT
WM_NULL

至于最后的WM_NULL,将在用到的时候作出解释。

而wParam和lParam又是依赖于message参数的。例如WM_CLOSE消息中,wParam和lParam都是NULL,而WM_KEYDOWN消息中,wParam是虚拟键值,lParam是关于按键消息的其他信息,如重复次数等。

所以,我们要定义一个tagMSG指针:

tagMSG* msg;

给它赋上lparam的值:

msg=(tagMSG*)lParam;

于是就能通过msg访问消息内容了,由于msg本身是指针,要访问其内容如message就要如下:

msg->message

通过上面的讲述,我们大概了解了如何设置钩子,如何编写钩子过程,以及对过程的参数的理解等都有了比较详细的解释。但有些地方难免缺漏,有任何疑问或补充敬请在评论中留言(作者仅对X_TK博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。

下面我们来编写一个完整的HookProc,用来阻止一次WM_CLOSE消息之后卸载。

怎么知道已经已经处理过一次了呢?定义一个BOOL变量,初始值为FALSE,处理过后为TRUE即可:

BOOL handled;

handled=FALSE;

也许你会有疑问:既然用一次就卸载为什么还要设置这么一个变量呢?原因是可能同时又多个消息传来,那么这段消息会被执行多次。

那么怎么阻止消息传递呢?上文提到可以不CallNextHookEx,这里介绍另一种方法,将消息设为WM_NULL继续传递。

对于WM_NULL,微软是这样解释的:

For example, if an application has installed a WH_GETMESSAGE hook and wants to prevent a message from being processed, the GetMsgProc callback function can change the message number to WM_NULL so the recipient will ignore it.

这段话是说,HookProc可以通过将消息设为WM_NULL阻止该消息传递。

于是我们就有思路了:消息传来,判断是不是WM_CLOSE,如果是,而且handled为FALSE,那么就将handled设为TRUE,然后将消息改为WM_NULL,然后卸载钩子,然后调用CallNextHookEx继续传递,并返回它的返回值。

我们的HookProc应该这样写:

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
if(nCode<0)
return CallNextHookEx(hooker,nCode,wParam,lParam);
tagMSG* msg;
msg=(tagMSG*)lParam;
if(nCode==HC_ACTION && (msg->message==WM_CLOSE) && handled==FALSE){
if(handled==FALSE)
handled=TRUE;
UnhookWindowsHookEx(hooker);
msg->message=WM_NULL;
return CallNextHookEx(hooker,nCode,wParam,(LPARAM)msg);
}
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

这样,就能阻止一次WM_CLOSE消息了。为了测试,我们需要在DllMain函数中添加一个PostMessage的调用,对其发送WM_CLOSE消息。PostMessage的原型如下:

BOOL PostMessage(

HWND hWnd, // handle of destination window 接收消息的窗口句柄
UINT Msg, // message to post 发送消息类型
WPARAM wParam, // first message parameter wParam参数
LPARAM lParam // second message parameter lParam参数
);

这样,我们就能发送一个WM_CLOSE消息了:

PostMessage(notepadhandle,WM_CLOSE,NULL,NULL);

在VC6种新建一个DLL工程,命名为notepadhook:

找到源文件:

完整代码如下:

// notepadhook.cpp : Defines the entry point for the DLL application.
//

#include “stdafx.h”
#include <stdio.h>
#include <stdlib.h>
HHOOK hooker;
HWND notepadhandle;
BOOL handled;extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam);
char* ConvertInttoChar(int i);

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){
if(ul_reason_for_call==DLL_PROCESS_ATTACH){
handled=FALSE;
notepadhandle=FindWindow(“Notepad”,NULL);
if(notepadhandle==NULL){
printf(“Notepad Not Found.n”);
return TRUE;
}
hooker=SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));
if(hooker){
printf(“Hook Successfully.nHookID:%dn”,hooker);
}
else{
printf(“Hook Failed.nError:%dn”,GetLastError());
return TRUE;
}
PostMessage(notepadhandle,WM_CLOSE,0,0);
}
return TRUE;
}

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
if(nCode<0)
return CallNextHookEx(hooker,nCode,wParam,lParam);
tagMSG* msg;
msg=(tagMSG*)lParam;
if(nCode==HC_ACTION && (msg->message==WM_CLOSE)){
if(handled==FALSE)
handled=TRUE;
UnhookWindowsHookEx(hooker);
msg->message=WM_NULL;
return CallNextHookEx(hooker,nCode,wParam,(LPARAM)msg);
}
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

以上是DLL的源码,另外新建一个C++ Source File,命名为hook:

键入如下代码:

#include <stdio.h>
#include <windows.h>
int main(){
LoadLibrary(“notepadhook.dll”);
getchar();//这里getchar是为了防止程序退出,若程序过快退出,钩子可能没有效果
return 1;
}
    编译连接得到hook.exe,将notepadhook.dll复制到hook.exe同一目录,打开记事本,运行hook.exe,可以看到Hook Successfully的提示,而且记事本并没有被关闭,这说明成功拦截了WM_CLOSE消息。

以上是《HOOK钩子教程》的全部内容。内容较为基础,要学习HOOK的更高级别的应用,请继续关注X.TK博客。可能最后的部分显得有点仓促,因为现在已经快凌晨了,作者快撑不住了。如果对本文由任何疑问,请在评论里留言(作者仅对X_TK博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。感谢您阅读本文。

HOOK钩子教程的更多相关文章

  1. Windows API 教程(七) hook 钩子监听

    茵蒂克丝 如何创建一个窗口 手动创建窗口的流程 实际代码 安装钩子 (Install hook) 钩子简介 SetWindowsHookEx 函数 设置监听[键盘]消息 设置监听[鼠标]消息 如何创建 ...

  2. HOOK钩子 - 钩子函数说明

    翻译参考自MaybeHelios的blog: http://blog.csdn.net/maybehelios/ 通过SetWindowsHookEx方法安装钩子,该函数指定处理拦截消息的钩子函数(回 ...

  3. Wordpress解析系列之PHP编写hook钩子原理简单实例

    Wordpress作为全球应用最广泛的个人博客建站工具,有很多的技术架构值得我们学习推敲.其中,最著名最经典的编码技术架构就是采用了hook的机制. hook翻译成中文是钩子的意思,单独看这个词我们难 ...

  4. 面试官: 说说看, 什么是 Hook (钩子) 线程以及应用场景?

    文章首发自个人微信号: 小哈学Java 个人网站地址: https://www.exception.site/java-concurrency/java-concurrency-hook-thread ...

  5. {Django基础十之Form和ModelForm组件}一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 ModelForm

    Django基础十之Form和ModelForm组件 本节目录 一 Form介绍 二 Form常用字段和插件 三 From所有内置字段 四 字段校验 五 Hook钩子方法 六 进阶补充 七 Model ...

  6. windows hook 钩子

    windows  hook  钩子 场景: 1.打印机 Ctrl+P弹出支付窗口,付款成功后打印

  7. 【转】Hook钩子C#实例

    [转]Hook钩子C#实例 转过来的文章,出处已经不知道了,但只这篇步骤比较清晰,就贴出来了. 一.写在最前 本文的内容只想以最通俗的语言说明钩子的使用方法,具体到钩子的详细介绍可以参照下面的网址: ...

  8. 学习之路三十八:Hook(钩子)的学习

    好久没写文章了,还记得年前面试了一家公司,为了检测一下我的学习能力,给了我一个任务,做一个自动登录并自动操作菜单的程序. 花了几天的时间研究了Hook以及使用WindowsAPI操作程序的知识,现在记 ...

  9. WordPress 插件机制的简单用法和原理(Hook 钩子)

    WordPress 的插件机制实际上只的就是这个 Hook 了,它中文被翻译成钩子,允许你参与 WordPress 核心的运行,是一个非常棒的东西,下面我们来详细了解一下它. PS:本文只是简单的总结 ...

随机推荐

  1. CSU 1046 追杀

    Description 在一个8行9列的国际象棋棋盘上,有一名骑士在追杀对方的国王.该骑士每秒跨越一个2*3的区域,如下图所示. 而对方的国王慌忙落逃,他先沿着右下斜线方向一直跑,遇到边界以后会沿着光 ...

  2. id---显示用户ID

    d命令   id命令可以显示真实有效的用户ID(UID)和组ID(GID).UID 是对一个用户的单一身份标识.组ID(GID)则对应多个UID 语法 id [-gGnru][--help][--ve ...

  3. pycharm不显示工具栏,自动导入模块,格式化代码快捷键

    我们需修改View里面的Toolbar,在前面打上沟,然后就可以显示了 自动导入模块设置:import numpy as np 我们需用鼠标选中numpy,然后在键盘上同时按住Alt+Enter键,通 ...

  4. Java中join和yield的作用

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.   A.join,在API中的解释是,堵塞当前线程B,直到A执行完毕并死掉,再执行B. 用一个 ...

  5. pat(A) 2-06. 数列求和(模拟摆竖式相加)

    1.链接:http://www.patest.cn/contests/ds/2-06 2.思路:模拟摆竖式相加,因为同样位置上的数字同样,那么同一位上的加法就能够用乘法来表示 3.代码: #inclu ...

  6. 火车票问题.以及x轴连续矩形,最大面积问题

    假设火车有10个站点: 1000个座位 api(1)  -> param  : leftStation, rightStation -> result : cnt             ...

  7. HttpUtility.UrlEncode 方法 (String) 对 URL 字符串进行编码 NET Framework 4.6 and 4.5

    对 URL 字符串进行编码. 这些方法重载可用于对整个 URL(包括查询字符串值)进行编码. 要编码或解码 Web 应用程序之外的值,请使用 WebUtility 类. 重载此成员.有关此成员的完整信 ...

  8. 一个小的考试系统 android 思路

    一个小的考试系统 android 思路 假如有 100 组,每组有4个单选钮,设置超时检测确认后去测结果估分视图去切换,如果还有,就再显示下一组 所有结束就给个总结显示 有超时结束过程加上 提示正确选 ...

  9. CISP/CISA 每日一题 17

     CISSP 每日一题(答) What are often added to passwords to maketheir resultant hash secure and resistant to ...

  10. Win8.1部署 .NET Framework 3.5 安装方式

    Windows 8.1中包含.NET Framework,操作系统安装过程中默认安装 .NET Framework 4.5.1.如果程序需要.NET Framework 3.5支持,将自动启用相关功能 ...