揭示Win32 API拦截细节/API hooking revealed (3)

  

[cpp]  view plain copy
1. // Activate/Deactivate hooking engine   
2.  BOOL   CModuleScope::InstallHookMethod( BOOL bActivate,  HWND hWndServer)  
3. ...{  
4.            BOOL bResult;  
5.            if (bActivate)  
6.           ...{  
7.                     *m_phwndServer = hWndServer;  
8.                     bResult = m_pInjector->InjectModuleIntoAllProcesses();  
9.           }  
10.           else  
11.          ...{  
12.                    m_pInjector->EjectModuleFromAllProcesses();  
13.                    *m_phwndServer = NULL;  
14.                    bResult = TRUE;  
15.          }  
16.           return bResult;  
17.}  

 
         
    CwinHookInjector类用Windows钩子实现注入机制。它通过如下调用安装过滤函数(Filter Function):正如你在上面看到的,它向系统注册了WM_GETMESSAGE类型的钩子。钩子服务器只调用这个函数一次。SetWindowsHookEx() 的最后一个参数是0,因为在这里GetMsgProc() 是作为一个全局钩子来使用的。当某个窗口即将接收一条特定消息时,回调函数就被调用。这里有趣的是,我们仅为回调函数GetMsgProc() 提供了几近空的(什么也不做的)实现,因为我们不需要监控窗口的消息处理。我们提供该函数的实现仅仅是要用到操作系统提供的注入机制。

[cpp]  view plain copy
1. // Inject the DLL into all running processes  
2.  BOOL CWinHookInjector::InjectModuleIntoAllProcesses()  
3. ...{  
4.           *sm_pHook = ::SetWindowsHookEx(  
5.                     WH_GETMESSAGE,  
6.                     (HOOKPROC)(GetMsgProc),  
7.                     ModuleFromAddress(GetMsgProc),   
8.                     0  
9.                     );  
10.           return (NULL != *sm_pHook);  
11.}  
    
 

调用SetWindowsHookEx()后,操作系统将检查导出GetMsgProc()的dll(例如HookTool.dll)是否已经被映射到GUI进程的地址空间。如果未被加载,Windows将强制该进程映射它。有趣的是,全局钩子的DllMain()函数不应该返回FALSE。这是因为windows会不断验证DllMain()的返回值并加载该dll直到DllMain()返回TRUE。
    另一个完全不同的实现来自CRemThreadInjector类。此处的注入机制基于产生远程线程。CRemThreadInjector通过接收进程产生和终止的消息来扩展Windows的进程列表维护机制。也就是产生一个CNtInjectorThread对象来监视进程产生。CntInjectorThread从内核驱动接收进程产生的相关消息。因此每次有进程产生,CNtInjectorThread ::OnCreateProcess() 都被调用,相应的,当进程终止将自动调用CNtInjectorThread ::OnTerminateProcess()。与windows钩子不同的是,每次有进程产生的时候,采用远程线程机制都要手动向新线程注入dll。这需要我们提供一种发送进程产生事件的简便方法。CntDriverController类实现了一些列管理服务和驱动的api。它被设计成加载和卸载内核模式驱动NTProcDrv.sys。后面将会讨论它的实现。 
    成功向特定进程注入HookTool.dll后,该dll的DllMain()将调用ManageModuleEnlistment()。在前面我已经阐述过了重复调用该函数的原因。它通过CmoduleScope的成员m_pbHookInstalled检查共享变量sg_bHookInstalled。由于钩子服务器在初始化时已经把sg_bHookInstalled置TRUE,于是它便检查当前进程是否需要被拦截,如果需要,钩子服务器将为该进程激活拦截引擎。
    在CmoduleScope ::InitializeHookManagement()的实现中,拦截引擎被激活。这个函数用于拦截LoadLibrary()系列的函数和GetProcAddress() 函数。用这种方法,我们可以在进程初始化后监视dll的加载。每当一个新的钩子dll被映射到目标进程,它都要修改进程的导入地址表,因此我们可以保证系统不会丢失对目标函数的任何调用。
    在CmoduleScope ::InitializeHookManagement() 末尾,我们对目标函数的钩子函数作了初始化。
    由于示例代码拦截了多于一个的用户提供的win32 api函数,因此我们应该对每个被拦截的函数提供独立的钩子函数。就是说,用上述更改iat拦截api的方法,不仅需要把原目标函数在导入表中的地址更改为一个“通用的”钩子函数的地址。拦截系统需要知道某个函数调用指向哪个函数。另外一点很重要的是,钩子函数必须有跟原api函数相同的函数原型,否则堆栈将会崩溃。例如CModuleScope实现了MyTextOutA()、MyTextOutW()和MyExitProcess()3个静态函数。一旦HookTool.dll被加载到目标进程,并且拦截系统被激活,每次对原TextOutA()的调用都回变成对CmoduleScope ::MyTextOutA()的调用。
    前面提及的拦截系统是非常灵活高效的。尽管如此,该系统一般只适合于拦截那些名称和原型已知且数量有限的api函数。
    如果你需要拦截新的函数,你应该简单声明并实现它的钩子函数,就如同前面实现MyTextOutA/w()和MyExitProcess()一样。然后你应该用前面在InitializeHookManagement()中实现的方法注册被拦截的函数。
    拦截和跟踪进程产生对实现那些需要处理外部进程的系统十分有用。获取进程产生过程中的有用信息向来是开发进程监视系统和拦截系统过程中的一个经典问题。Win32 api提供了一系列的库([参考16]PSAPI和ToolHelp库)用来枚举当前系统中运行的进程。尽管它们功能很强大,却没有提供接收进程产生/撤销事件的方法。幸运的是,windowsNT/2K提供了一系列api,被详细记录在windowsDDK文档的“进程结构规则(Process Structure Routines)”下,并被NTOSKRNL导出。其中之一的PsSetCreateProcessNotifyRoutine()允许注册一个全局回调函数,该函数在进程产生、退出、终止时被操作系统调用。通过写一个NT内核模式驱动和win32用户模式控制程序,就可以简单的利用上述函数来实现进程监视。该驱动的作用是检测进程产生并把相关事件通知控制程序。示例代码中的windows进程监视器NTProcDrv提供了在基于NT内核的操作系统上监视进程的最基本功能。更多细节请参见文章[参考11]和[参考15]。相关代码在NTProcDrv.c中。由于该驱动的加载和释放是动态的,因此当前登录用户必须拥有管理员权限。否则驱动安装将会失败同时进程监视也会出错。解决办法是用管理员权限手动安装驱动,或使用windows2k提供的“作为其他用户运行(run as different user)”选项来运行HookSrv.exe。
 
    最后要注意的一点是,可以通过简单修改ini文件(HookTool..ini)中的相关设置来选用上述所有的工具。这个文件决定了使用windows钩子(在win9x和nt/2k系统上)还是远程线程(仅在windowsnt/2k上)来做dll注入。也可以在该文件里面指定要监视的进程和不被监视的进程。如果需要查看被拦截进程的详细活动,打开[Trace]节下面的Enabled选项就可以记录下拦截系统的所有活动。通过调用由ClogFile类导出的方法,这个选项能让用户看到发生错误的内容。实际上,ClogFile提供了线程安全的实现,而且访问共享资源的同步问题也不需要操心了(日志文件)。更多信息请参看ClogFile类的实现以及HookTool.ini的内容。

示例源代码
    编译本工程需要vc++6 sp4和Platform SDK。在windowsNT平台上需要提供PSAPI.DLL以运行CtaskManager的实现。在运行示例代码之前必须确保已经被设置好了HookTool.ini文件。对于那些要扩展NTProcDrv代码和对本工程底层内容感兴趣的开发人员,他们应该安装windowsDDK。

延伸内容
     出于简化问题的目的,在本文中我忽略了一些内容:
         1)监视内部的api(Native API)调用
         2)windows9x系统上监视进程产生的驱动
         3)支持UNICODE,但你还是可以通过拦截UNICODE导入函数来达到相同目的。

总结
    到目前为止,本文尚未对拦截任意api的问题给出完整的指南,毫无疑问,本文遗漏了一些细节。尽管如此,我仍在这少数的有限篇幅中给出了足够重要的信息,也许对那些在win32用户模式做api拦截的开发人员有帮助。
 
 
参考文章
 
[1] "Windows 95 System Programming Secrets", Matt Pietrek
[2] "Programming Application for MS Windows" , Jeffrey Richter 
[3] "Windows NT System-Call Hooking" , Mark Russinovich and Bryce Cogswell, Dr.Dobb's Journal January 1997
[4] "Debugging applications" , John Robbins 
[5] "Undocumented Windows 2000 Secrets" , Sven Schreiber 
[6] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format" by Matt Pietrek, March 1994 
[7] MSDN Knowledge base Q197571
[8] PEview Version 0.67 , Wayne J. Radburn
[9] "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" MSJ May 1994
[10] "Programming Windows Security" , Keith Brown
[11] "Detecting Windows NT/2K process execution" Ivo Ivanov, 2002 
[12] "Detours" Galen Hunt and Doug Brubacher 
[13a] "An In-Depth Look into the Win32 PE file format" , part 1, Matt Pietrek, MSJ February 2002
[13b] "An In-Depth Look into the Win32 PE file format" , part 2, Matt Pietrek, MSJ March 2002
[14] "Inside MS Windows 2000 Third Edition" , David Solomon and Mark Russinovich 
[15] "Nerditorium", James Finnegan, MSJ January 1999 
[16] "Single interface for enumerating processes and modules under NT and Win9x/2K." , Ivo Ivanov, 2001
[17] "Undocumented Windows NT" , Prasad Dabak, Sandeep Phadke and Milind Borate
[18] Platform SDK: Windows User Interface, Hooks

 

你可能感兴趣的:(Win32,api,Hooking,API拦截,revealed)