揭示Win32 API攔截細節/API hooking revealed (3)

  

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() 提供了幾近空的(什麼也不做的)實現,因爲我們不需要監控窗口的消息處理。我們提供該函數的實現僅僅是要用到操作系統提供的注入機制。

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

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章