SetWindowsHookEx() 是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。
Windows 窗口程序是靠消息驱动的,一切过程皆是消息,原本的消息传递过程是:系统接收到消息-将消息放入系统消息队列-将消息放入线程消息队列-线程中处理该消息,而钩子函数**SetWindowsHookEx()**的作用就是拦截消息,用自定义的函数处理消息
设置全局钩子会拦截所有窗口线程的消息
设置线程钩子则只会拦截特定线程的消息
注意:当拦截当前线程消息时可以在当前代码中写回调函数,当拦截其它线程消息时则必须调用dll中回调函数
下面是利用SetWindowsHookEx函数在WinProc处理消息之前插入一个消息处理的过程,以及截获、处理和传递MSG的过程:
可以看到,MSG被新的消息处理过程接收,然后通过CallNextHookEx函数向下传递MSG。第一个CallNextHookEx如果返回"True",表示这个MSG已经被处理,如果返回"False",系统将继续把MSG传递WinProc进行处理。
HHOOK WINAPI SetWindowsHookEx(
int idHook, //设置钩子的类型,意思就是我要设置的钩子是什么钩子,可以是监视窗口过程,可以是监视消息队列。
HOOKPROC lpfn, //根据钩子类型.设置不同的回调函数
HINSTANCE hMod, //钩子设置的Dll实例句柄,就是DLL的句柄
DWORD dwThreadId; //设置钩子的线程ID. 如果为0 则设置为全局钩子.
)
//HHOOK 返回值. 是一个钩子过程句柄
idHook | 解释 |
---|---|
WH_CALLWNDPROC | 监视到达窗口前的消息 |
WH_CALLWNDPROCRET | 监视窗口处理后的消息 |
WH_DEBUG | 监视系统调用其他HOOK关联的HOOK子程 |
WH_GETMESSAGE | 监视发送到窗体消息队列里的消息 |
WH_JOURNALPLAYBACK | 全局HOOK,可以插入消息到消息队列 |
WH_JOURNALRECORD | 全局HOOK,监视输入事件(键盘、鼠标等) |
WH_KEYBOARD | 键盘钩子 |
WH_MOUSE | 鼠标钩子 |
WH_MSGFILTER | 监视菜单、滚动条、消息框、对话框消息和切换窗口的组合键(Alt+Tab等) |
WH_SHELL | 接收系统中重要的通知(如窗口被产生、摧毁等) |
BOOL WINAPI UnhookWindowsHookEx(
HHOOK hhk //参数是SetWindowHookEx的返回值.也就是钩子过程句柄.
);
//返回值: 返回值是BOOL类型.表示设置是否成功或者失败.
HMODULE WINAPI GetModuleHandle(
LPCTSTR lpModuleName //获取的实例句柄的文件名,可以是Dll可以使exe,如果为NULL这是当前dll/exe的实例句柄
);
// 返回值 返回实例句柄.
LRESULT WINAPI CallNextHookEx(
HHOOK hhk, //保存的钩子过程,也就是SetWindowsHookEx返回值.
int nCode, //根据SetWindowsHookEx设置的钩子回调而产生的不同的nCode代码. 什么意思? 意思就是如果设置的钩子类型是鼠标消息.那么那个nCode就是鼠标消息.如果是键盘这是键盘
WPARAM wParam, //同2参数一样.附加参数. 根据钩子回调类型.附加参数有不同的意义.比如如果是鼠标.那么这个有可能代表的就是鼠标的x位置.键盘就可能是键代码
LPARAM lParam //同3参数一样.附加参数.
);
钩子回调根据SetWindowsHookEx参数1来设定的.比如如果我们设置WH_CBT 那么我们设置的回调函数就是CBT回调. 具体查询MSDN https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexa
//这个回调函数里面写我们的代码就可以了.
LRESULT CALLBACK CBTProc(
int nCode,
WPARAM wParam,
LPARAM lParam
);
/*注入DLL代码*/
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include
#include
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(NULL , _T("DLL加载成功") , _T("DLL窗口提示") , IDOK);
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
{
MessageBox(NULL , _T("DLL卸载成功") , _T("DLL窗口提示") , IDOK);
break;
}
}
return TRUE;
}
//导出回调函数
extern "C" __declspec(dllexport) LRESULT MyMessageProcess(int Code, WPARAM wParam, LPARAM lParam)
{
MessageBox(NULL, _T("Windows消息钩子注入成功"), _T("DLL窗口提示"), 0);
return 0;
}
/*安装挂钩程序核心代码*/
//根据进程名称获取进程PID
DWORD GetProcessIDByName(const wchar_t* pName);
//根据进程PID获取进程主线程ID
DWORD GetThreadIdByProID(ULONG32 ulTargetProcessID);
//Windows挂钩创建
BOOL InjectDllBySetWindowsHook();
DWORD GetProcessIDByName(const wchar_t* pName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS , 0);
if (INVALID_HANDLE_VALUE == hSnapshot) {
return NULL;
}
PROCESSENTRY32 pe = { sizeof(pe) };
for (BOOL ret = Process32First(hSnapshot, &pe) ; ret ; ret = Process32Next(hSnapshot , &pe))
{
if (wcscmp(pe.szExeFile , pName) == 0)
{
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
}
CloseHandle(hSnapshot);
return 0;
}
DWORD GetThreadIdByProID(ULONG32 ulTargetProcessID)
{
HANDLE Handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (Handle != INVALID_HANDLE_VALUE)
{ THREADENTRY32 te;
te.dwSize = sizeof(te);
if (Thread32First(Handle, &te))
{
do
{
if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID))
{
if (te.th32OwnerProcessID == ulTargetProcessID)
{
HANDLE hThread = OpenThread(READ_CONTROL, FALSE, te.th32ThreadID);
if (!hThread)
{
OutputDebugString(_T("主线程句柄获取失败"));
}
else
{
return te.th32ThreadID;
}
}
}
} while (Thread32Next(Handle, &te));
}
} CloseHandle(Handle);
return 0;
}
BOOL InjectDllBySetWindowsHook()
{
HMODULE m_hmDll = LoadLibrary(_T("WindowsHookInjectDLL.dll"));
if (m_hmDll == NULL)
{
OutputDebugString(_T("DLL加载失败"));
return FALSE;
}
//获取DLL中到处函数的地址
HOOKPROC sub_address = NULL;
sub_address = (HOOKPROC)GetProcAddress(m_hmDll,"MyMessageProcess");
if(sub_address == NULL)
{
OutputDebugString(_T("获取函数地址失败"));
return FALSE;
}
DWORD hPid;
hPid=GetProcessIDByName(_T("notepad++.exe"));
if(hPid == NULL)
{
OutputDebugString(_T("获取目标进程PID失败"));
return FALSE;
}
DWORD dwThreadID;
dwThreadID = GetThreadIdByProID(hPid);
if(dwThreadID == NULL)
{
OutputDebugString(_T("获取目标进程主线程句柄失败"));
return FALSE;
}
m_hHook = SetWindowsHookEx(WH_KEYBOARD , sub_address , m_hmDll , dwThreadID);
if(m_hHook == NULL)
{
OutputDebugString(_T("建立钩子失败"));
return FALSE;
}
return TRUE;
}
代码和原理简单,易于实现,可以对所有消息循环的程序进行注入。
容易被发现和防御,对没有消息循环的纯后台程序无能为力。