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

2019年12月04日 阅读数:72
这篇文章主要向大家介绍关于Windows消息钩子的理解与测试项目,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
  • 前奏

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

  • 消息钩子简介

消息钩子简言之就是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):spa

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类型的句柄。code

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

参数:SetWindowsHookEx的返回值。orm

  • 键盘消息钩子及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++为例)

安装消息钩子前


安装消息钩子后




因为钩子种类繁多,我暂时没有所有实现,有兴趣的各位能够在个人基础上试试其余的,代码写的很差,让各位见笑了,若有错误,也恳请各位指正。