本文接上篇,继续探讨“Windows挂钩”,包括:跨进程的“窗口子类化”,使用Windows Hook注入DLL,跨进程窗口通讯,进程间共享内存和示例程序调试总结。
一、跨进程的“窗口子类化” subclass a window created by another process
上一篇最后,我们讲到通过“窗口子类化”来扩展窗口的行为。其关键是将自定义的窗口处理函数(NewWndProc)通过调用user32.dll中的SetWindowLongPtr函数与指定窗口关联起来。当线程调用DispatchMessage时,将取得的是NewWndProc函数的地址。原本发往OldWndProc函数的消息,会转发给NewWndProc。
LONG DispatchMessage(CONST MSG * msg)
{
LONG lResult;
WNDPROC lpfnWndProc = (WNDPROC)GetWindowLongPtr(msg.hwnd, msg.message);
lResult = lpfnWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
return(lResult);
}
当然,这一切的前提是,NewWndProc函数也位于被子类化的窗口所在的进程空间。因为,DispatchMessage同大多数Windows APIs一样,默认只操作调用它的线程所属的进程空间。
如果我们需要子类化其他进程的窗口,即NewWndProc函数位于我们的进程空间,而不是位于被子类化的窗口所在的进程空间,那么DispatchMessage在引用我们传入的函数地址值时,实际访问的是目标进程的某个地址,根本就不可能调到NewWndProc函数。
因此,我们要想子类化其他进程的窗口的窗口,第一步就是要把NewWndProc函数注入到目标进程的地址空间。《Windows核心编程》一书中介绍了多种方法来讲NewWndProc函数注入到目标进程地址空间。
二、Windows Hook
1,Windows Hook
Windows系统提供了一种从进程A将一个函数注入到进程B并截取进程B某个窗口的消息的方法——Windows Hook。
先来看MSDN怎么说:点击打开链接
A hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.
A hook is a mechanism by which an application can intercept events, such as messages, mouse actions, and keystrokes. A function that intercepts a particular type of event is known as a hook procedure. A hook procedure can act on each event it receives, and then modify or discard the event.
The following some example uses for hooks:
- Monitor messages for debugging purposes
- Provide support for recording and playback of macros
- Provide support for a help key (F1)
- Simulate mouse and keyboard input
- Implement a computer-based training (CBT) application
Installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread.
Syntax
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
要从我们的进程(进程A)向目标进程(进程B)的某个窗口安装挂钩,就需要在A进程中调用SetWindowsHookEx函数。例如:
Hook hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hInstDll, 0);
第1个参数WH_MESSGAE表示要安装的挂钩的类型。第2个参数GetMsgProc是一个函数的地址(位于进程A的地址空间),在目标窗口即将处理一条消息的时候,系统应该调用这个函数。第3个参数hInstDll表示一个DLL,这个DLL中包含了GetMsgProc函数。在Windows中,hInstDll的值是进程地址空间中DLL被映射到的虚拟内存地址。最后一个参数0表示要给哪个线程安装挂钩。一个线程可能会调用SetWindowsHookEx并传入系统中另一个线程的线程标识符(一般是线程ID)。通过给这个参数传0,我们告诉系统要给系统中的所有GUI线程安装挂钩。
从SetWindowsHookEx的原型,我们可以推测:
1)Hook Procedure函数既要载入进程A的地址空间,又要载入进程B的地址空间,且进程A要知道该函数在进程B中的虚拟内存地址。第2个参数表示的就是Hook Procedure函数。
2)Hook Procedure函数要放在一个独立的DLL中。一个函数要同时载入两个进程空间,那么最好的设计是将该函数放到一个DLL中,作为一个导出函数,让进程A和进程B都载入该DLL。第3个参数表示的就是这个DLL。
3)进程A需要自己去加载InstDll,进程B不需要。SetWindowsHookEx会将InstDll加载到指定GUI线程所在进程(进程B)的地址空间。严格来说,SetWindowsHookEx只是注册,真正的加载过程如下:
1)进程B中的一个线程准备向一个窗口派送一条消息(dispatch message)。
2)系统检查该线程是否已经安装了WH_GETMESSAGE挂钩。
3)系统检查GetMsgProc所在的DLL是否已经被映射到进程B的地址空间中。
4)如果DLL尚未被映射,那么系统会强制将该DLL映射到进程B,并将进程B中该DLL的锁计数器(lock count)递增。
5)由于DLL的hInstDll是在进程B中映射的,因此系统会对它进行检查,看它与该DLL在进程A中的位置是否相同。
如果hInstDLL在进程A和进程B的位置相同,那么GetMsgProc函数在这两个进程空间的虚拟内存地址也就会相同。这种情况,系统就可以直接在进程B中调用GetMsgProc。
如果hInstDLL不同,系统会通过以下公式换算,来获取GetMsgProc在进程B中的地址
GetMsgProc B = hInstDll B + (GetMsgProc A - hInstDll A)
6)系统在进程B中调用GetMsgProc函数,返回后,系统会递减该DLL在进程B中的锁计数器。
Hook Procedure函数如下:
LRESULT CALLBACK HookProc( int nCode, WPARAM wParam, LPARAM lParam ) { // process event ... return CallNextHookEx(NULL, nCode, wParam, lParam); }
3,Monitoring System Events
SetWindowsHookEx函数的第一个参数表示Hook的类型,它对应一种系统事件(event)。Monitoring System Events示例展示了对它支持的7种系统事件的监视和处理。可以查看MSDN文档,了解这7种事件的具体含义。
《Windows核心编程》中的DIPS示例展示的就是其中的“WM_GETMESSAGE”事件的处理。MSDN说:Installs a hook procedure that monitors messages posted to a message queue.它监视所有发送给被监视线程的消息。
另一个值得注意的是“WM_KEYBOARD”事件。它配合GetKeyNameText函数,可以监视用户敲击的键盘按键。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
......
case WM_KEYDOWN:
//MessageBox(NULL, TEXT("key down"), NULL, MB_OK);
WCHAR keyname[128];
MessageBox(NULL, keyname, NULL, MB_OK);
break;
......
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
4,Subclass a window created by another process
注意,SetWindowsHookEx会将整个DLL映射到目标进程,而仅仅是Hook Procedure函数。这就意味着,该DLL内的所有函数都存在进程B中,可以为进程B调用。
回到之前“跨进程窗口子类化”的话题,我们可以先给创建窗口(目标窗口)的线程设置一个WH_GETMESSAGE挂钩,然后当GetMsgProc被调用的时候,我们可以在它内部调用SetWindowLongPtr来派送子类窗口。当然,子类窗口的过程函数必须和GetMsgProc函数在同一个DLL中。