对钩子的理解
说起钩子,很多人都想起它的形状,根据形状去想象windows的钩子,会陷入误区。Windows的钩子,是没有形状的,之所以叫钩子,并不是说它的形状象钩子,而是它的作用和钩子一样:就是用来“挂靠”的。比如有个包工头想去接个项目,但是他没有公司,拿不出相关的证件,怎么办呢?那就得找个公司挂靠,成立个子公司(subclass技术)之类,让那个公司帮他出资源,出相关的证件之类......windows的钩子和这很相似。比如我们常常想访问另一个进程的一些资源(常见的获取编辑框的密码),怎么办呢?用钩子来做挂靠,成为另一个进程的一个分子,那样你就可以在一定规则下(挂靠的当然都会有限制了)访问、控制该进程的资源了。钩子不但可以挂靠别的进程,本进程也可以用钩子去“挂靠”和控制。
有关钩子的API
(有关API的更详细和权威的介绍,请参考MSDN)
1、安装钩子的API:SetWindowsHookEx(),要使用钩子,首先得安装钩子。任何一个钩子都由系统来维护一个指针列表(钩子链表),其指针指向钩子的各个处理函数。最近安装的钩子放在链的开始,最早安装的钩子则放在最后,当钩子监视的消息出现时,操作系统调用链表开始处的第一个钩子处理函数进行处理,也就是说最后加入的钩子优先获得控制权。呵呵,最后这句话就是提示我们怎么反钩子的。
HHOOK SetWindowsHookEx(int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId)
其中:参数idHook 指定了钩子的类型,总共有如下13种:
WH_CALLWNDPROC 系统将消息发送到指定窗口之前的"钩子"
WH_CALLWNDPROCRET 消息已经在窗口中处理的"钩子"
WH_CBT 基于计算机培训的"钩子"
WH_DEBUG 差错"钩子"
WH_FOREGROUNDIDLE 前台空闲窗口"钩子"
WH_GETMESSAGE 接收消息投递的"钩子"
WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD"钩子"记录的输入消息
WH_JOURNALRECORD 输入消息记录"钩子"
WH_KEYBOARD 键盘消息"钩子"
WH_MOUSE 鼠标消息"钩子"
WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息"钩子"
WH_SHELL 外壳"钩子"
WH_SYSMSGFILTER 系统消息"钩子"
这样,你就可以用各种各样的钩子去做挂靠访问了,比如你可以用鱼勾去钓鱼,用衣服钩子去钩衣服,但不要用错……
参数lpfn为指向钩子处理函数的指针,即回调函数的首地址;参数hMod则标识了钩子处理函数所处模块的句柄;第四个参数dwThreadId 指定被监视的线程,如果明确指定了某个线程的ID就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为0,则表示此钩子为监视系统所有线程的全局钩子。此函数在执行完后将返回一个钩子句柄。
对于全局钩子,要使用动态连接库来实现,因为你的钩子需要被其他进程和系统调用,如果放在你的进程中,估计别的进程也得产生个钩子来访问你的钩子了J,对于一些线程钩子,虽然不是全局钩子,建议也使用动态连接库。
2、传递钩子的API:CallNextHookEx( )。在安装钩子以后,钩子会对相关的资源进行访问控制,如果希望自己处理完相应的事物后,将系统交还给系统和原来的进程,就需要调用CallNextHookEx( )。一些反钩子的代码常常不调用该函数,这样外挂程序安装的钩子就不会被运行。
LRESULT CallNextHookEx(HHOOK hhk,int nCode,WPARAM wParam,LPARAM lParam);
其中,参数hhk为由SetWindowsHookEx()函数返回的当前钩子句柄;参数nCode为传给钩子过程的事件代码;参数wParam和lParam 则为传给钩子处理函数的参数值,其具体含义同设置的钩子类型有关。
3、卸载钩子的API:SetWindowsHookEx()。由于钩子对系统的性能有影响,在安装使用完钩子以后,就需要把钩子卸载。
BOOL UnhookWindowsHookEx(HHOOK hhk);
其中hhk是使用SetWindowsHookEx()安装钩子时产生的返回值。
注意:windows的钩子为了提高系统的性能,采用了“copy-on-write”的技术,在SetWindowsHookEx()调用后并不马上安装钩子。比如在设置WH_CALLWNDPROC钩子时,钩子并不马上生效,而要在系统将消息发送到指定窗口的时候才会正式安装钩子,才会把钩子挂靠在进程内(实际上不只是挂靠钩子过程,还会把整个dll影射到进程内),才能访问进程的资源,否则是个假钩子,徒有虚名而已。
钩子的简单应用
这个例子不是我写的,在网上找的,是个键盘钩子。通过这个例子可以对钩子和钩子的有关API有比较深刻的认识。原来的连接在http://www.pconline.com.cn/pcedu/empolder/gj/vc/0403/340480.html可以找到,但是代码编译有问题。我作了些修改。
附代码如下:
// test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_main_tid = 0;
HHOOK g_kb_hook = 0;
#define _WIN32_WINNT 0x0400
BOOL CALLBACK con_handler (DWORD)
{
PostThreadMessage (g_main_tid, WM_QUIT, 0, 0);
return TRUE;
};
//--add by pdg
#define WH_KEYBOARD_LL 13
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD tim e;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
//--add by pdg ,由于编译时认不到这两个定义,因此此次重新做了定义
LRESULT CALLBACK kb_proc (int code, WPARAM w, LPARAM l)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)l;
const char *info = NULL;
if (w == WM_KEYDOWN)
info = "key dn";
else if (w == WM_KEYUP)
info = "key up";
else if (w == WM_SYSKEYDOWN)
info = "sys key dn";
else if (w == WM_SYSKEYUP)
info = "sys key up";
printf ("%s - vkCode [%04x], scanCode [%04x]/n",
info, p->vkCode, p->scanCode);
// always call next hook
return CallNextHookEx (g_kb_hook, code, w, l);
};
int _tmain(int argc, _TCHAR* argv[])
{
g_main_tid = GetCurrentThreadId ();
SetConsoleCtrlHandler (&con_handler, TRUE);
g_kb_hook = SetWindowsHookEx (
WH_KEYBOARD_LL,
&kb_proc,
GetModuleHandle (NULL), // 不能为NULL,否则失败
0);
if (g_kb_hook == NULL)
{
fprintf (stderr,
"SetWindowsHookEx failed with error %d/n",
::GetLastError ());
return 0;
};
// 消息循环是必须的,想知道原因可以查msdn
MSG msg;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
};
UnhookWindowsHookEx (g_kb_hook);
return 0;
}