HOOK钩子教程

[转载]HOOK钩子教程

 

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

先留着,好好学一学!

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

[转载]HOOK钩子教程

    在你读到这篇文章之前,也许你还已经读过不少关于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,弹出了如图的窗口:[转载]HOOK钩子教程
    Finder Tool是一个箭靶的图案,拖动箭靶到记事本标题栏上,松开鼠标,窗体变成了如下的样子:[转载]HOOK钩子教程

    右侧显示了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:

[转载]HOOK钩子教程

    找到源文件:

HOOK钩子教程

    完整代码如下:

// 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:

[转载]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博客中的评论进行回复,转载至其他论坛及博客的,本作者概不负责)。感谢您阅读本文。

原文链接: https://www.cnblogs.com/dengpeng1004/p/4971328.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    HOOK钩子教程

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/224645

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午12:31
下一篇 2023年2月13日 下午12:31

相关推荐