关于Windows消息钩子的理解与测试项目

  • 前奏

近来一直在自学Windows Hook相关的知识,已经尝试多种注入方式。尤其对消息钩子方式很感兴趣,因为看到Spy++能够截获系统中绝大多数应用的消息流,就很想知道它的工作原理,打算制作属于自己的Spy++。

  • 消息钩子简介

消息钩子简言之就是Windows处理消息的一个平台,用户可以在此平台获取或过滤所需应用的消息(例如某鼠标位置,键盘击下等),这些被监控的消息都会在目标窗口处理函数之前被截获。系统在拿到消息后会问你:这是不是你想要的消息?如果是,则恭喜你,你可以在回调函数里做相应操作了。

  • 消息钩子函数背景

消息钩子主要由三部分组成:

  1. 钩子安装函数
  2. 钩子回调函数
  3. 钩子卸载函数
  • 钩子安装函数原型
1
2
3
4
5
6
HHOOK WINAPI SetWindowsHookEx(
   _In_   int  idHook,
   _In_  HOOKPROC lpfn,
   _In_  HINSTANCE hMod,
   _In_  DWORD dwThreadId
);

参数1-idHook:这个函数代表了你要安装钩子的种类,比如键盘钩子,鼠标钩子,窗体钩子等等。以下我列了一张表,结合MSDN供大家参考。


参数2-HOOKPROC lpfn:此参数是钩子回调函数的地址,对应上述不同种类的钩子类型,其钩子回调函数原型基本是一致的。请见下(需要注意的是,wParam和lParam针对不同类型的钩子,传递参数所代表意义不同,因种类繁多,请各位查阅MSDN):

1
2
3
4
5
6
7
8
9
10
11
LRESULT CALLBACK HookProc(
   int  nCode,
   WPARAM wParam,
   LPARAM lParam
)
{
    / /  process event
    ...
 
    return  CallNextHookEx(NULL, nCode, wParam, lParam);
}

nCode:int,此参数指示Hook例程是否需要处理消息,如果nCode为HC_ACTION,则需要处理;如果小于0,不予处理,须调用CallNextHookEx函数返回。 wParam与lParam:针对不同类型的钩子,代表的意义不同,但总体是针对当前类型钩子的消息。后面以例子做出验证。 参数3-HINSTANCE hMod:包含Hook例程的Dll所在模块基址。 参数4-DWORD dwThreadId:所要注入程序的主线程ID。如果此项填0,则代表钩子为系统钩子,基本所有在运行的进程都会被注入。如果此项指定线程ID,则是有针对性的线程钩子。

返回值:HHOOK类型的句柄。

  • 钩子回调函数
请见上
  • 钩子卸载函数
1
2
3
BOOL  WINAPI UnhookWindowsHookEx(
   _In_  HHOOK hhk
);

参数:SetWindowsHookEx的返回值。

  • 键盘消息钩子及CBT钩子程序代码


首先我们要明确下,钩子从安装到回显的流程

  1. 编写Dll,在Dll中实现我们所需要的钩子回调函数及安装函数。

  2. Dll中除了要编写与钩子相关的处理函数,也要编写与显示程序通信的模块,以便验证钩子消息的正确性。

  3. Dll需要导出钩子启动函数与卸载函数。

  4. 回显程序处理已安装钩子发送的信息。

至此,框架已经明了,我们分步拆解完成这个小项目。(整体代码请见附件,VS2013编译)

  • 结构体及枚举的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct COMMPACK
{
     int  nType; / / Hook类型
     WPARAM wParam; / / 参数 1
     LPARAM lParam; / / 参数 2
     TCHAR szMsg[ 16 ]; / / 额外的字符串信息,取决于具体钩子
} COMMPACK,  * PCOMMPACK; / / 与回显程序通讯的包结构
 
typedef struct HOOKSTC
{
     int  nType; / / Hook类型
     HOOKPROC hkproc; / / Hook回调
     HHOOK hhook; / / Hook句柄
} HOOKSTC,  * PHOOKSTC;
 
HOOKSTC m_Array4Hooks[NHOOKNUMS]  =  0  }; / / 创建多钩子类型的结构体数组
 
enum { M_CALLWNDPROC, M_CBT, M_DEBUG, M_GETMESSAGE, M_KEYBOARD, M_MOUSE, M_MSGFILTER }; / / 钩子类型枚举
 
enum { IDHCBT_ACTIVATE, IDHCBT_CLICKSKIPPED, IDHCBT_CREATEWND, IDHCBT_DESTROYWND, IDHCBT_KEYSKIPPED,
     IDHCBT_MINMAX, IDHCBT_MOVESIZE, IDHCBT_QS, IDHCBT_SETFOCUS, IDHCBT_SYSCOMMAND, IDUnknown
}; / / CBT钩子的消息群

  • CBT钩子的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
LRESULT WINAPI m_CBTProc( int  nCode, WPARAM wParam, LPARAM lParam)
{
     if  (nCode <  0 ) return  CallNextHookEx(m_Array4Hooks[M_CBT].hhook, nCode, wParam, lParam);
     CHAR szCode[ 16 =  0  };
     PCOMMPACK pCP  =  new COMMPACK;
     pCP - >nType  =  M_CBT;
     switch (nCode)
     {
     case HCBT_ACTIVATE: / / 系统要激活一个窗口
         pCP - >lParam  =  (LPARAM)IDHCBT_ACTIVATE;
         pCP - >wParam  =  IDHCBT_ACTIVATE;
         break ;
     case HCBT_CLICKSKIPPED: / / 系统已经从系统消息队列中删除了一个鼠标消息
         pCP - >lParam  =  (LPARAM)IDHCBT_CLICKSKIPPED;
         pCP - >wParam  =  IDHCBT_CLICKSKIPPED;
         break ;
     case HCBT_CREATEWND: / / 一个窗口将要被创建
         pCP - >lParam  =  (LPARAM)IDHCBT_CREATEWND;
         pCP - >wParam  =  IDHCBT_CREATEWND;
         break ;
     case HCBT_DESTROYWND: / / 一个窗口将要被销毁
         pCP - >lParam  =  (LPARAM)IDHCBT_DESTROYWND;
         pCP - >wParam  =  IDHCBT_DESTROYWND;
         break ;
     case HCBT_KEYSKIPPED: / / 系统已从系统消息队列中删除了一个键盘消息
         pCP - >lParam  =  (LPARAM)IDHCBT_KEYSKIPPED;
         pCP - >wParam  =  IDHCBT_KEYSKIPPED;
         break ;
     case HCBT_MINMAX: / / 一个窗口将被最小化或最大化
         pCP - >lParam  =  (LPARAM)IDHCBT_MINMAX;
         pCP - >wParam  =  IDHCBT_MINMAX;
         break ;
     case HCBT_MOVESIZE: / / 一个窗口将被移动或改变尺寸
         pCP - >lParam  =  (LPARAM)IDHCBT_MOVESIZE;
         pCP - >wParam  =  IDHCBT_MOVESIZE;
         break ;
     case HCBT_QS: / / 系统已从系统消息队列中取到一个WM_QUEUESYNC 消息
         pCP - >lParam  =  (LPARAM)IDHCBT_QS;
         pCP - >wParam  =  IDHCBT_QS;
         break ;
     case HCBT_SETFOCUS: / / 一个窗口将要获得键盘焦点
         pCP - >lParam  =  (LPARAM)IDHCBT_SETFOCUS;
         pCP - >wParam  =  IDHCBT_SETFOCUS;
         break ;
     case HCBT_SYSCOMMAND: / / 一个系统命令将被执行
         pCP - >lParam  =  (LPARAM)IDHCBT_SYSCOMMAND;
         pCP - >wParam  =  IDHCBT_SYSCOMMAND;
         break ;
     default: / / 其他
         pCP - >lParam  =  (LPARAM)IDUnknown;
         pCP - >wParam  =  IDUnknown;
         break ;
     }
     m_Com(pCP); / / 与回显程序通信
     delete pCP;
     return  CallNextHookEx(m_Array4Hooks[M_CBT].hhook, nCode, wParam, lParam);
}
  • 键盘钩子的回调函数
1
2
3
4
5
6
7
8
9
10
11
LRESULT WINAPI m_KeyboardProc( int  nCode, WPARAM wParam, LPARAM lParam)
{
     if  ((nCode <  0  || !((DWORD)lParam &  0x40000000 ))) return  CallNextHookEx(m_Array4Hooks[M_KEYBOARD].hhook, nCode, wParam, lParam);
     PCOMMPACK pCP  =  new COMMPACK;
     pCP - >lParam  =  lParam;
     pCP - >wParam  =  wParam; / / 这个值就是按键的VirtualKey
     pCP - >nType  =  M_KEYBOARD;
     m_Com(pCP); / / 与回显程序通讯
     delete pCP;
     return  CallNextHookEx(m_Array4Hooks[M_KEYBOARD].hhook, nCode, wParam, lParam);
}
  • 与回显程序通讯的函数
1
2
3
4
5
6
7
8
9
void m_Com(PCOMMPACK& pCP,  int  addi)
{
     hTarget  =  ::FindWindow(NULL, L "HookApp" ); / / 寻找回显程序窗口标题
     COPYDATASTRUCT stcCDS; / / 采用WM_COPYDATA消息方式进程间通讯
     stcCDS.cbData  =  sizeof(COMMPACK);
     stcCDS.dwData  =  addi;
     stcCDS.lpData  =  pCP; / / 打包消息结构体
     ::SendMessage(hTarget, WM_COPYDATA,  0 , (LPARAM)&stcCDS);
}
  • 钩子初始化函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void InitHooks()
{
     hTarget  =  ::FindWindow(NULL, L "HookApp" );
     m_Array4Hooks[M_CALLWNDPROC].hkproc  =  m_CallWndProc;
     m_Array4Hooks[M_CALLWNDPROC].nType  =  WH_CALLWNDPROC;
     m_Array4Hooks[M_CBT].hkproc  =  m_CBTProc;
     m_Array4Hooks[M_CBT].nType  =  WH_CBT;
     / / m_Array4Hooks[M_DEBUG] - >hkproc  =  m_DebugProc;
     / / m_Array4Hooks[M_DEBUG] - >nType  =  WH_DEBUG;
     / / m_Array4Hooks[M_GETMESSAGE] - >hkproc  =  m_GetMsgProc;
     / / m_Array4Hooks[M_GETMESSAGE] - >nType  =  WH_GETMESSAGE;
     m_Array4Hooks[M_KEYBOARD].hkproc  =  m_KeyboardProc;
     m_Array4Hooks[M_KEYBOARD].nType  =  WH_KEYBOARD;
     m_Array4Hooks[M_MOUSE].hkproc  =  m_MouseProc;
     m_Array4Hooks[M_MOUSE].nType  =  WH_MOUSE;
     / / m_Array4Hooks[M_MSGFILTER] - >hkproc  =  m_MessageFilterProc;
     / / m_Array4Hooks[M_MSGFILTER] - >nType  =  WH_MSGFILTER;
     for  ( int  =  0 ; i < NHOOKNUMS;  + + i)
     {
         m_Array4Hooks[i].hhook  =  0 ;
     }
}

  • 钩子安装及卸载函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void InstallHook(DWORD dwProcessID)
{
     g_dwThreadID  =  GetThreadInfo(dwProcessID); / / 由注入器传入指定注入进程 ID ,遍历获得主线程 ID
 
     InitHooks(); / / 初始化钩子
 
     for  ( int  =  0 ; i < NHOOKNUMS;  + + i)
     {
         if  (m_Array4Hooks[i].hkproc  = =  NULL) continue ;
         m_Array4Hooks[i].hhook  =  SetWindowsHookEx(m_Array4Hooks[i].nType, m_Array4Hooks[i].hkproc, g_Module, g_dwThreadID);
     }
}
 
void UnInstallHook()
{
     for  ( int  =  0 ; i < NHOOKNUMS;  + + i)
     {
         UnhookWindowsHookEx(m_Array4Hooks[i].hhook);
     }
}

  • 线程遍历函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DWORD GetThreadInfo(DWORD dwProcessID)
{
     HANDLE hSnThread  =  CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,  0 );
     if  (hSnThread  = =  INVALID_HANDLE_VALUE) {
         return  0 ;
     }
 
     / /  开始遍历线程
     THREADENTRY32 threadEntry  =  { sizeof(THREADENTRY32) };
 
     / /  获取快照中的第一个线程的信息
     Thread32First(hSnThread, &threadEntry);
 
     DWORD dwSuspendCount  =  0 ;
     do {
 
         / /  判断遍历到的线程是否属于这个进程的.
         if  (threadEntry.th32OwnerProcessID  = =  dwProcessID) {
             return  threadEntry.th32ThreadID;
         }
         / /  获取快照中的下一个线程信息
     while  (Thread32Next(hSnThread, &threadEntry));
     return  1 ;
}
  • 函数导出
1
2
EXTERN_C _declspec(dllexport) void InstallHook(DWORD dwProcessID);
EXTERN_C _declspec(dllexport) void UnInstallHook();

至此DLL部分结束,开始回显程序的消息处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
BOOL  CHookAppDlg::OnCopyData(CWnd *  pWnd, COPYDATASTRUCT *  pCopyDataStruct)
{
     / /  TODO:  在此添加消息处理程序代码和 / 或调用默认值
     static  int  nIndex  =  0 ;
     PCOMMPACK pCP  =  new COMMPACK;
     pCP  =  (PCOMMPACK)pCopyDataStruct - >lpData;
 
     switch (pCP - >nType) / / 根据消息的类型进行定义
     {
     case M_CALLWNDPROC:
     {
 
     } break ;
 
     case M_CBT:
     {
         static  int  nIndex  =  0 ;
         CString strCallWndProc;
         switch (pCP - >wParam)
         {
         case IDHCBT_ACTIVATE:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_ACTIVATE" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_CLICKSKIPPED:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_CLICKSKIPPED" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_CREATEWND:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_CLICKSKIPPED" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_DESTROYWND:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_DESTROYWND" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_KEYSKIPPED:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_KEYSKIPPED" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_MINMAX:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_MINMAX" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_MOVESIZE:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_MOVESIZE" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_QS:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_QS" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_SETFOCUS:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_SETFOCUS" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDHCBT_SYSCOMMAND:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "HCBT_SYSCOMMAND" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         case IDUnknown:
         {
             strCallWndProc. Format (L "CBT [%d] - nCode: %s, tsk: %ld  " , nIndex, L "Unknown" , pCP - >wParam);
             strCallWndProc  + =  L "\r\n" ;
             m_StrCBT  + =  strCallWndProc;
             UpdateData(FALSE);
             + + nIndex;
         } break ;
         default:
             break ;
         }
     } break ;
     case M_DEBUG:
     {
 
     } break ;
     case M_GETMESSAGE:
     {
 
     } break ;
     case M_KEYBOARD:
     {
         static  int  nIndex  =  0 ;
         CString strCallWndProc;
         strCallWndProc. Format (L "KEYBOARD [%d] - VK: %c  " , nIndex, char(pCP - >wParam));
         strCallWndProc  + =  L "\r\n" ;
         m_StrKeyBoard  + =  strCallWndProc;
         UpdateData(FALSE);
         + + nIndex;
         
     } break ;
     case M_MOUSE:
     {
         
     } break ;
     case M_MSGFILTER:
     {
 
     } break ;
 
     default:
         break ;
     }
 
     return  CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

注入器部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL  _Inject_::m_WindowsHook(PWCHAR pszDllName, LPCSTR pszDllProc, DWORD dwPID)
{
     typedef void *  ( * HookOnProto)(DWORD); / / 声明导出函数原型
     HookOnProto HookOn;
     hInstDll  =  LoadLibrary(pszDllName);
     if  (hInstDll  = =  NULL) return  FALSE;
 
     HookOn  =  (HookOnProto)GetProcAddress(hInstDll, pszDllProc); / / 从指定Dll导出所需函数
     if  (HookOn  = =  NULL) return  FALSE;
 
     HookOn(dwPID); / / 钩子安装
 
     return  TRUE;
}
 
BOOL  _Inject_::m_UnWinHook(PWCHAR pszDllName, LPCSTR pszDllProc)
{
     typedef void *  ( * HookOffProto)(void);
     HookOffProto HookOff;
 
     HookOff  =  (HookOffProto)GetProcAddress(hInstDll, pszDllProc);
     if  (HookOff  = =  NULL) return  FALSE;
     HookOff();

效果演示:(以注入notepad++为例)

安装消息钩子前


安装消息钩子后




由于钩子种类繁多,我暂时没有全部实现,有兴趣的各位可以在我的基础上试试其他的,代码写的不好,让各位见笑了,如有错误,也恳请各位指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值