- Windows程序员对于HOOK技术应该都很熟悉,HOOK俗称:钩子。即将自己想实现的功能,挂钩到系统的函数上,达到调用系统的函数时能自动执行我们实现的功能。
- 对于HOOK,也分为:消息钩子,API钩子,内核钩子。消息钩子和API钩子都是在应用层(Ring3)上实现,内核钩子则是在内核层(Ring0)上实现的。此次开篇,逐重讨论下Inline Hook的实现方式,Inline Hook也是API钩子的一种实现方式。
- 搭建这个Hook框架时,我们需要先简单了解下消息钩子,因为消息钩子可以让我们编写的DLL轻松的注入到各个窗体当中。消息钩子安装涉及到一个API:
HHOOK WINAPI SetWindowsHookEx(
In int idHook,
In HOOKPROC lpfn,
In HINSTANCE hMod,
In DWORD dwThreadId
);
HHOOK SetWindowsHookEx(
int idHook, // 钩子的类型,即它处理的消息类型
HOOKPROC lpfn, // 钩子子程的地址指针。如果dwThreadId参数为0
// 或是一个由别的进程创建的线程的标识,
// lpfn必须指向DLL中的钩子子程。
// 除此以外,lpfn可以指向当前进程的一段钩子子程代码。
// 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。
// 如果dwThreadId 标识当前进程创建的一个线程,
// 而且子程代码位于当前进程,hMod必须为NULL。
// 可以很简单的设定其为本应用程序的实例句柄。
DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
// 如果为0,钩子子程与所有的线程关联,即为全局钩子。
);
//钩子类型 idHook 选项:
WH_MSGFILTER = -1; {线程级; 截获用户与控件交互的消息}
WH_JOURNALRECORD = 0; {系统级; 记录所有消息队列从消息队列送出的输入消息, 在消息从队列中清除时发生; 可用于宏记录}
WH_JOURNALPLAYBACK = 1; {系统级; 回放由 WH_JOURNALRECORD 记录的消息, 也就是将这些消息重新送入消息队列}
WH_KEYBOARD = 2; {系统级或线程级; 截获键盘消息}
WH_GETMESSAGE = 3; {系统级或线程级; 截获从消息队列送出的消息}
WH_CALLWNDPROC = 4; {系统级或线程级; 截获发送到目标窗口的消息, 在 SendMessage 调用时发生}
WH_CBT = 5; {系统级或线程级; 截获系统基本消息, 譬如: 窗口的创建、激活、关闭、最大最小化、移动等等}
WH_SYSMSGFILTER = 6; {系统级; 截获系统范围内用户与控件交互的消息}
WH_MOUSE = 7; {系统级或线程级; 截获鼠标消息}
WH_HARDWARE = 8; {系统级或线程级; 截获非标准硬件(非鼠标、键盘)的消息}
WH_DEBUG = 9; {系统级或线程级; 在其他钩子调用前调用, 用于调试钩子}
WH_SHELL = 10; {系统级或线程级; 截获发向外壳应用程序的消息}
WH_FOREGROUNDIDLE = 11; {系统级或线程级; 在程序前台线程空闲时调用}
WH_CALLWNDPROCRET = 12; {系统级或线程级; 截获目标窗口处理完毕的消息, 在 SendMessage 调用后发生}
- 消息钩子卸载API:
BOOL WINAPI UnhookWindowsHookEx( In HHOOK hhk );//函数成功返回TRUE,否则返回FALSE。
- 系统钩子与线程钩子:
SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。 0为系统钩子
线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含”钩子回调函数”的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。 - 几点说明:
(1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。
(2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。
(3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。 - 介绍完消息,我们初步框架的实现方式也发现了:利用挂钩系统级消息,安装一个全局钩子,达到注入各个进程的目的。
- 我们直接编写一个DLL(VS2008,选择MFC DLL项目),并开放:注册和释放两个接口。
HINSTANCE g_hInstance = NULL; // 模块实例句柄
//钩子回调,默认不处理直接传递给系统的下一个钩子
LRESULT WINAPI HookProc( int nCode, WPARAM wParam, LPARAM lParam )
{
// 传给系统中的下一个钩子
return CallNextHookEx( g_hHook, nCode, wParam, lParam );
}
/********************************************************************/
/* 安装钩子 */
/* 参数: (无) */
/* 返回值: TRUE 成功, FALSE 失败 */
/********************************************************************/
BOOL WINAPI StartWork()
{
// 如果已经安装钩子则返回 FALSE
if (g_hHook != NULL) return FALSE;
// 安装钩子
g_hHook = SetWindowsHookEx( WH_CALLWNDPROC, HookProc, g_hInstance, NULL);
if (g_hHook == NULL)
{
return FALSE;
}
return TRUE;
}
/********************************************************************/
/* 释放钩子 */
/* 参数: (无) */
/* 返回值: (无) */
/********************************************************************/
void WINAPI StopWork()
{
if (NULL != g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
- 在DLL被载入到内存的时候,进行一次g_hInstance的值设置。
BOOL CFileTransferApp::InitInstance()
{
g_hInstance = this->m_hInstance;
return CWinApp::InitInstance();
}
int CFileTransferApp::ExitInstance()
{
return CWinApp::ExitInstance();
}
到此,全局消息钩子注入已经完成。看似很简单,但是这是我们进行API HOOK的基础,达到了能注入各个进程,我们就可以在InitInstance函数里面,进行API的挂钩操作。下一章节,我们将实现API挂钩(修改系统API前几个字节,实现JMP到我们自定义的API当中)。