1. 程式人生 > >C++ Hook(鉤子)程式設計,通過內聯彙編,使類成員函式代替全域性函式(靜態函式)

C++ Hook(鉤子)程式設計,通過內聯彙編,使類成員函式代替全域性函式(靜態函式)

程式語言:C/C++

編譯環境:Visual Studio 2008

核心方法::通過內聯彙編,構造類物件獨享的函式(委託),完成了類成員函式到普通全域性函式的轉化,並在Windows Hook(鉤子)程式設計中得到成功的實踐。

關鍵字:C++,委託,內聯彙編,Hook,成員函式

引文:

前段時間曾編寫了一個自認為很完善的.NET平臺上的Hook(鉤子)動態連結庫(DLL),並進一步完成了GUI介面,但是在部署時卻發現的其侷限性,很多使用者都沒有安裝.NET平臺,其平臺的最小安裝(.NET 2.0)也需要21M,如果使用NOMO(2.0)免安裝釋出的話也需要小10M(而且使用NOMO在自動執行和相容性上也有待商榷)。

因此,我下定決心,準備徹底和託管程式碼決裂,迴歸C/C++,再次實現上述功能,然而真正編寫時才發現,經典的C++果然不是蓋的,昔日被C的各種除錯不通過折磨的記憶還未消退,今日又在開始時陷入苦戰,正如前人所說,C++這種強型別語言,喜歡在繁瑣的符號和語法上做文章。如果處理不好這些,大概做不了大專案,還是去.NET或Java的快樂平臺逍遙去吧~微微感慨,趕快進入正題。

正文:

本文的目的是研究類成員函式與普通函式之區別,以及不同調用方式之間的區別,進而通過內聯彙編語句模仿特定的呼叫,從而完成通過普通函式指標呼叫類成員函式的功能。因此主要部分為研究和嘗試的過程,如希望直接檢視Hook程式設計的結果,可直接檢視尾部程式碼。

使用過Windows Hook程式設計的同志們都知道,在程式中是通過如下語句來載入鉤子,解除安裝鉤子。

C/C++ Code:

  1. HHOOK SetWindowsHookEx( //載入鉤子  
  2.   int idHook,  
  3.     HOOKPROC lpfn,          //鉤子函式的指標  
  4.     HINSTANCE hMod,  
  5.     DWORD dwThreadId  
  6. );  
  7. BOOL UnhookWindowsHookEx( //解除安裝鉤子  
  8.   HHOOK hhk  
  9. );  

其中最重要的就是HOOKPROC lpfn這個函式指標,檢視此函式的形式,為:

C/C++ Code:

  1. LRESULT CALLBACK HookProcess( //鉤子函式  
  2.     int nCode,  
  3.     WPARAM wParam,  
  4.     LPARAM lParam  
  5. );  
 

//其中CALLBACK 的定義為 #define CALLBACK    __stdcall

其中,值得注意的是,這個鉤子函式需要是一個普通__stdcall呼叫的C函式,而在C++中可以理解為某個全域性函式(非類成員函式,全域性訪問)或者是一個類的靜態函式(static,全域性訪問)。

讓我們再觀察一個類成員函式和全域性函式的區別,我建立了一個簡單的演示程式碼,如下程式碼:

C/C++ Code:

  1. #include <windows.h>  
  2. LRESULT CALLBACK HookProcess1( // HookProcess1是一個全域性函式  
  3.                              int nCode,  
  4.                              WPARAM wParam,  
  5.                             LPARAM lParam){ return NULL;}  
  6. class WinHook  
  7. {  
  8. public:  
  9. int value; //定義一個成員變數,以便於測試訪問  
  10. LRESULT CALLBACK HookProcess2(// HookProcess2是一個類的成員函式  
  11.                              int nCode,  
  12.                              WPARAM wParam, LPARAM lParam) {  
  13.         this->value++;        //檢查成員變數的訪問性  
  14.        return NULL;  
  15. }  
  16. };  
  17. int main(){  
  18.      //定義一個函式指標HookProcess,它可以成功作為引數傳遞給SetWindowsHookEx  
  19.      LRESULT (CALLBACK * HookProcess)  
  20.                     (int nCode, WPARAM wParam, LPARAM lParam);  
  21.      //試圖將HookProcess指向HookProcess1  
  22.      HookProcess = HookProcess1; //賦值成功,這個指標能夠指向全域性函式  
  23.      HookProcess (0,0,0);//嘗試呼叫之.  
  24.      //試圖將HookProcess指向HookProcess2  
  25.      WinHook *myHook = new WinHook();  
  26.      HookProcess = myHook->HookProcess2; //編譯失敗,提示型別不能轉化  
  27.                                                                 //此句將在之後修改  
  28.      HookProcess (0,0,0);//嘗試呼叫之.  
  29.      return 0;  
  30. }  
 

檢視錯誤程式碼,終於發現類的成員函式和普通的全域性函式是不一樣的,

// error C2440:

//“=”: 無法從“LRESULT (__stdcall WinHook::* )(int,WPARAM,LPARAM)”轉換為“LRESULT (__stdcall *)(int,WPARAM,LPARAM)”

關鍵問題就在於“WinHook::”的區別,可以看到,無論如何,一個類的成員函式(非靜態)是不能與普通的C全域性函式轉化的。這點在後面再詳細的說明。

然而,當我們使用C++程式設計的時候,往往不希望使用全域性函式;如果使用類的靜態函式,那麼一個類只能實現一個鉤子,且不能利用C++中類的繼承,多型等特性,從實質上淪落為普通C程式設計。

在網上查找了很多方法,發現可以通過內聯彙編語句強制將指標轉化,如下:

C/C++ Code:

  1. //修改上述main中程式碼,將類成員函式指標強制轉化為一般函式指標  
  2. WinHook *myHook = new WinHook();  
  3. //HookProcess = myHook->HookProcess2;  
  4. __asm  //NEW: 內聯彙編語句  
  5. {  
  6.      push eax  
  7.      lea eax, WinHook::HookProcess2  
  8.      mov HookProcess, eax  
  9.      pop eax  
  10. }  
  11.        HookProcess (0,0,0);//嘗試呼叫之.  

然而在使用這種方法時,卻發現了問題,即在底層呼叫WinHook 的HookProcess2中,其中的this指標為0,因此所有this相關的操作均不可行(即不能訪問本物件的其他成員)。此外,在HookProcess2結束後,底層也發現了堆疊不平衡而導致的錯誤。

繼續閱讀網上的文章,如

(成員函式指標與高效能的C++委託)

(普通函式指標變數可以儲存成員函式地址嗎?)

(使非MFC視窗程式的視窗回撥過程成為C++類的成員函式)

等等

我終於理解的類成員函式與普通函式的區別,那就是this指標,當一個類的成員函式被呼叫時,如:

C/C++ Code:

  1. WinHook *myHook = new WinHook();  
  2. HookProcess = myHook->HookProcess2(…);  

此時,當HookProcess2的引數被傳送之後,myHook的地址也被傳送在CPU暫存器中(或堆疊中,這要看傳呼叫方式的不同,在之後說明),並在之後在HookProcess2函式中賦值給this,使得類成員函式能夠知道呼叫的是哪個類的物件。

為了測試這一點,我們可以在強制轉化了HookProcess2指標之後,在HookProcess2中修復this指標,看函式的this功能是否正常。如下程式碼:

C/C++ Code:

  1. class WinHook //修改WinHook類的函式HookProcess2  
  2. {  
  3. public:  
  4. int value; //定義一個成員變數,以便於測試訪問  
  5. LRESULT CALLBACK HookProcess2(// HookProcess2是一個類的成員函式  
  6.                              int nCode,  
  7.                              WPARAM wParam, LPARAM lParam) {  
  8.   //假設主函式中的myHook指標指向0x003d3450,修復this指標  
  9.               __asm  //內聯彙編語句  
  10. {  
  11.       push eax                   //保護eax  
  12.       mov eax, 0x003d3450     
  13.       mov this, eax              //設定本類的this指標(__asm不檢查訪問性)  
  14.       pop eax                    //還原eax  
  15. }  
  16.  //此時檢視this指標,就能夠發現this正常工作  
  17.   this->value++;  
  18.        return NULL;  
  19. }  
  20. };  
 

注:雖然this指標能夠執行成功,但是函式結束會產生如下錯誤:

C++ Hook(鉤子)程式設計-編譯錯誤提示

這是由於__stdcall的this傳送方式是堆疊,強制訪問HookProcess2卻未將this壓棧會導致堆疊不平衡。因此,將CALLBACK這個修飾符登出掉,即:

C/C++ Code:

  1. class WinHook //進一步修改WinHook類的函式HookProcess2  
  2. {  
  3. public:  
  4. int value; //定義一個成員變數,以便於測試訪問  
  5. LRESULT /*CALLBACK*/ HookProcess2(// HookProcess2是一個類的成員函式  
  6.                              int nCode,  
  7.                              WPARAM wParam, LPARAM lParam) {  
  8.   //假設主函式中的myHook指標指向0x003d3450,修復this指標  
  9.               __asm  //內聯彙編語句  
  10. {  
  11.       push eax                   //保護eax  
  12.       mov eax, 0x003d3450     
  13.       mov this, eax              //設定本類的this指標(__asm不檢查訪問性)  
  14.       pop eax                    //還原eax  
  15. }  
  16.  //此時檢視this指標,就能夠發現this正常工作  
  17.   this->value++;  
  18.        return NULL;  
  19. }  
  20. };  
 

此時,發現整個函式能夠順利執行。(注意,在實際測試時,需要在main中加斷點檢查myHook的地址,並動態的修改mov eax, 0x003d3450的數值。否則不能測試通過)

這是因為,登出掉CALLBACK的修飾符(即__stdcall)之後,函式採用預設的__thiscall呼叫方式,此時,this指標是通過CUP暫存器的ecx傳送,此時就不會產生堆疊不平衡的錯誤了。

進一步的,我們遇到的新的問題,儘管能夠成功模擬一個類成員函式的呼叫,修復了this指標,但是對於SetWindowsHookEx來說,每個類的所有成員是共享一個函式空間的。即如下圖所示。

C++ Hook(鉤子)程式設計-原始結構

因此,如果使用HookProcess2的程式入口(記憶體首地址)作為SetWindowsHookEx的引數傳入,在引發鉤子事件的時候就不能夠區分到底引發了哪個類的HookProcess2函式,即不能設定this指標。

為了解決這個問題,我們需要生成一個動態的函式入口,這個入口是每個物件獨享的,因此不同的物件將引發不同的函式,這就能夠區分類的不同物件。根據其他語言對它的描述,我們將這個函式入口暫稱為委託(Delegate)。

在實際上,我們是通過定義一個成員變數(如byte型別的陣列,可便於的賦值)來實現這個委託的,其中實際上儲存了一段機器碼,這段機器碼(根據彙編語法)可以動態的設定this指標,並實現到真正函式首地址的跳轉。這個委託的示意過程如下:

C++ Hook(鉤子)程式設計-委託結構

下面說明這個委託的宣告和設定程式碼:

C/C++ Code:

  1. class WinHook //進一步修改WinHook類的函式HookProcess2  
  2. {  
  3. public:  
  4.        byte DelegateThisCall[10]; //定義委託  
  5.        WinHook(void){  
  6.                //初始化委託  
  7.                      byte * DelegatePoint = DelegateThisCall;  
  8.     DelegateThisCall[0] = 0xb9; //0-4:__asm mov ecx, this  
  9.     DelegateThisCall[5] = 0xe9; //5-9:__asm jmp, CoreHookProcess  
  10.     __asm  
  11.     {  
  12.          push eax                        //保護eax  
  13.          push ebx                        //保護ebx  
  14.          mov eax, this                   //取得this地址  
  15.          mov ebx, DelegatePoint        //獲取DelegateThisCall地址  
  16.          mov dword ptr [ebx+1], eax   //this 地址, 雙字(DWORD)  
  17.     }  
  18.     //計算jmp地址參考:http://zhidao.baidu.com/question/105950930.html  
  19.     __asm  
  20.     {  
  21.          lea eax, HookProcess2         //取得HookProcess2地址  
  22.          mov ebx, DelegatePoint        //獲取jmp地址= DelegatePoint + 5  
  23.          add ebx, 5  
  24.          add ebx, 5       //JMP地址=目標地址-(本句地址+本句指令位元組數)  
  25.          sub eax, ebx     //JMP地址= HookProcess2 – [(DelegatePoint+5) + 5]  
  26.          mov dword ptr [ebx-4], eax   //HookProcess2地址, 雙字(DWORD)  
  27.          pop ebx                         //還原ebx  
  28.          pop eax                         //還原eax  
  29.     }  
  30.        }  
  31.       int value; //定義一個成員變數,以便於測試訪問  
  32.   LRESULT /*CALLBACK*/ HookProcess2(// HookProcess2是一個類的成員函式  
  33.                               int nCode,  
  34.                               WPARAM wParam, LPARAM lParam) {  
  35.       //檢視this指標,就能夠發現this正常工作  
  36.          this->value++;  
  37.          return NULL;  
  38.   }  
  39. };  
 

進一步的,修改主函式:

C/C++ Code:

  1. int main(){  
  2.        //定義一個函式指標HookProcess,它可以成功作為引數傳遞給SetWindowsHookEx  
  3.        LRESULT (CALLBACK * HookProcess)  
  4.                       (int nCode, WPARAM wParam, LPARAM lParam);  
  5.        //將HookProcess指向HookProcess2  
  6.        WinHook *myHook = new WinHook();  
  7.        //HookProcess = myHook->HookProcess2; //賦值失敗,提示型別不能轉化  
  8.        byte * DelegatePoint = myHook->DelegateThisCall; //獲取委託首地址  
  9.      __asm  //內聯彙編語句  
  10.      {  
  11.          push eax  
  12.          mov eax, DelegatePoint  
  13.          mov HookProcess, eax         //強制將委託的地址賦值給函式指標  
  14.          pop eax  
  15.      }  
  16.        HookProcess (0,0,0);//嘗試呼叫之. 呼叫成功    
  17.        return 0;  
  18. }  
 

以上,就成功的完成了類成員函式的鉤子過程,通過一個委託,完成了此功能。

最後,為了演示上述方法在建立Windows Hook程式設計上的使用,特給出編寫的動態連結庫的實現WinHook功能的BaseHook類的程式碼,此類完成了成員函式的Hook載入解除安裝等管理,使用者通過繼承此類,並重寫HookProcess函式(如下),便可完成所有Hook功能。

C/C++ Code:

  1. //BaseHook 類的虛擬函式,當Hook事件發生時會呼叫此函式。  
  2. //使用者通過繼承並重寫此函式完成Hook功能  
  3. virtual LRESULT /*CALLBACK*/ HookProcess  
  4.                                                  (int nCode, WPARAM wParam, LPARAM lParam);  
 

原始碼部分:

C/C++ Code: BaseHook.h

  1. /********************************************************* {COPYRIGHT-TOP} * 
  2. * RealZYC Confidential 
  3. * OCO Source Materials 
  4. * 
  5. * (C) Copyright RealZYC Corp. 2011 All Rights Reserved. 
  6. * 
  7. * The source code for this program is not published or otherwise 
  8. * divested of its trade secrets, irrespective of what has been 
  9. * deposited with the China Copyright Office. 
  10. ********************************************************** {COPYRIGHT-END} */  
  11. #pragma once  
  12. #include <windows.h>  
  13. /*************************************** 
  14. *The basic defination of windows hook 
  15. ****************************************/  
  16. class BaseHook  
  17. {  
  18.     /*************************************** 
  19.     * Enum 
  20.     ****************************************/  
  21. #pragma region Enum  
  22. public:  
  23.     /*************************************** 
  24.     *The available types of windows hook 
  25.     ****************************************/  
  26.     enum HookTypes: int  
  27.     {  
  28.         //Message filter hook - WH_MSGFILTER = -1  
  29.         MsgFilter = -1,  
  30.         //Journal record hook - WH_JOURNALRECORD = 0  
  31.         JournalRecord = 0,  
  32.         //Journal playback hook - WH_JOURNALPLAYBACK = 1  
  33.         JournalPlayback = 1,  
  34.         //Keyboard hook - WH_KEYBOARD = 2  
  35.         Keyboard = 2,  
  36.         //Get message hook - WH_GETMESSAGE = 3  
  37.         GetMessage = 3,  
  38.         //Call wnd proc hook - WH_CALLWNDPROC = 4  
  39.         CallWndProc = 4,  
  40.         //CBT hook - WH_CBT = 5  
  41.         CBT = 5,  
  42.         //System message filter hook - WH_SYSMSGFILTER = 6  
  43.         SysMsgFilter = 6,  
  44.         //Mouse hook - WH_MOUSE = 7  
  45.         Mouse = 7,  
  46.         //Hardware hook - WH_HARDWARE = 8  
  47.         Hardware = 8,  
  48.         //Debug hook - WH_DEBUG = 9  
  49.         Debug = 9,  
  50.         //Shell hook - WH_SHELL = 10  
  51.         Shell = 10,  
  52.         //Fore ground idle hook - WH_FOREGROUNDIDLE = 11  
  53.         ForeGroundIdle = 11,  
  54.         //Call wnd proc ret hook - WH_CALLWNDPROCRET = 12  
  55.         CallWndProcRet = 12,  
  56.         //Keyboard low level hook - WH_KEYBOARD_LL = 13  
  57.         KeyboardLL = 13,  
  58.         //Mouse low level hook - WH_MOUSE_LL = 14  
  59.         MouseLL = 14  
  60.     };  
  61. #pragma endregion  
  62.     /*************************************** 
  63.     * Value 
  64.     ****************************************/  
  65. #pragma region Value  
  66. protected:  
  67.     //The hook type  
  68.     HookTypes int_HookType;   
  69.     //The hook object thread id, give 0 for all thread  
  70.     DWORD dword_ThreadId;  
  71.     //The hook id, give 0 for not set  
  72.     HHOOK point_HookID;  
  73.     //Dll entrance  
  74.     static HINSTANCE hangle_HinstDLL;  
  75. protected:  
  76.     //The this call delegate for CoreHookProcess  
  77.     byte DelegateThisCall[10];  
  78. #pragma endregion  
  79.     /*************************************** 
  80.     * New 
  81.     ****************************************/  
  82. #pragma region New  
  83. public:  
  84.     /*************************************** 
  85.     ''' <summary> 
  86.     ''' Initial function 
  87.     ''' </summary> 
  88.     ''' <param name="HookType">The hook type</param> 
  89.     ''' <param name="ThreadId">The hook object thread id, give 0 for all thread</param> 
  90.     ****************************************/  
  91.     BaseHook(HookTypes HookType, DWORD ThreadId);  
  92.     //Dispose function  
  93.     ~BaseHook(void);  
  94. #pragma endregion  
  95.     /*************************************** 
  96.     * Property 
  97.     ****************************************/  
  98. #pragma region Property  
  99. public:  
  100.     //Set / get the hook type  
  101.     inline HookTypes GetHookType();  
  102.     inline void SetHookType(HookTypes HookType);  
  103.     //Set / get the hook object thread id, give 0 for all thread  
  104.     inline DWORD GetThreadId();  
  105.     inline void SetThreadId(DWORD ThreadId);  
  106.     //Set / get whether the hook is running  
  107.     bool GetEnabled();  
  108.     void SetEnabled(bool Enabled);  
  109.     //Set / get dll hinst  
  110.     static HINSTANCE GetHinstDll();  
  111.     static void SetHinstDll(HINSTANCE HinstDLL);  
  112. #pragma endregion  
  113.     /*************************************** 
  114.     * Sub / Function 
  115.     ****************************************/  
  116. #pragma region Sub / Function  
  117. protected:  
  118.     /*************************************** 
  119.     ///<summary> 
  120.     /// The defination of core hook process 
  121.     ///</summary> 
  122.     ///<param name="nCode">Specifies the hook code passed to the current hook procedure. The next hook procedure uses this code to determine how to process the hook information</param> 
  123.     ///<param name="wParam">Specifies the wParam value passed to the current hook procedure. The meaning of this parameter depends on the type of hook associated with the current hook chain</param> 
  124.     ///<param name="lParam">Specifies the lParam value passed to the current hook procedure. The meaning of this parameter depends on the type of hook associated with the current hook chain</param> 
  125.     ///<returns>The nCode, use 0 to pass the information to next hook, others to ignore the current information</returns> 
  126.     ///<remarks>Use for SetWindowsHookEx</remarks> 
  127.     ****************************************/  
  128.     LRESULT /*CALLBACK*/ CoreHookProcess(int nCode, WPARAM wParam, LPARAM lParam);  
  129. public:  
  130.     /*************************************** 
  131.     /// <summary> 
  132.     /// Set the hook data 
  133.     /// </summary> 
  134.     /// <param name="HookType">The hook type</param> 
  135.     /// <param name="ThreadId">The hook object thread id, give 0 for all thread</param> 
  136.     /// <remarks>Restart the hook after the hook data changed</remarks> 
  137.     ****************************************/  
  138.     void SetHook(HookTypes HookType, DWORD ThreadId);  
  139.     /*************************************** 
  140.     /// <summary> 
  141.     /// Start the hook 
  142.     /// </summary> 
  143.     /// <remarks>Check the Enabled for operation result</remarks> 
  144.     ****************************************/  
  145.     void Start();  
  146.     /*************************************** 
  147.     /// <summary> 
  148.     /// Stop the hook 
  149.     /// </summary> 
  150.     /// <remarks>Check the Enabled for operation result</remarks> 
  151.     ****************************************/  
  152.     void Stop();  
  153.     /*************************************** 
  154.     /// <summary> 
  155.     /// The user defined hook process 
  156.     /// </summary> 
  157.     /// <param name="nCode">Specifies the hook code passed to the current hook procedure. The next hook procedure uses this code to determine how to process the hook information</param> 
  158.     /// <param name="wParam">Specifies the wParam value passed to the current hook procedure. The meaning of this parameter depends on the type of hook associated with the current hook chain</param> 
  159.     /// <param name="lParam">Specifies the lParam value passed to the current hook procedure. The meaning of this parameter depends on the type of hook associated with the current hook chain</param> 
  160.     /// <returns>The nCode, use 0 to pass the information to next hook, others to ignore the current information</returns> 
  161.     /// <remarks>Use for CoreHookProcess</remarks> 
  162.     ****************************************/  
  163.     virtual LRESULT /*CALLBACK*/ HookProcess(int nCode, WPARAM wParam, LPARAM lParam);  
  164. #pragma endregion  
  165. };  

C/C++ Code: BaseHook.cpp

  1. /********************************************************* {COPYRIGHT-TOP} * 
  2. * RealZYC Confidential 
  3. * OCO Source Materials 
  4. * 
  5. * (C) Copyright RealZYC Corp. 2011 All Rights Reserved. 
  6. * 
  7. * The source code for this program is not published or otherwise 
  8. * divested of its trade secrets, irrespective of what has been 
  9. * deposited with the China Copyright Office. 
  10. ********************************************************** {COPYRIGHT-END} */  
  11. #include "BaseHook.h"  
  12. /*************************************** 
  13. *class BaseHook 
  14. ****************************************/  
  15. /*************************************** 
  16. * Static 
  17. ****************************************/  
  18. #pragma region Static  
  19. HINSTANCE BaseHook::hangle_HinstDLL = NULL;  
  20. #pragma endregion  
  21. /*************************************** 
  22. * Property 
  23. ****************************************/  
  24. #pragma region Property  
  25. //Set / get the hook type  
  26. BaseHook::HookTypes BaseHook::GetHookType(){return int_HookType;}  
  27. void BaseHook::SetHookType(HookTypes HookType){int_HookType = HookType;}  
  28. //Set / get the hook object thread id, give 0 for all thread  
  29. DWORD BaseHook::GetThreadId(){return dword_ThreadId;}  
  30. void BaseHook::SetThreadId(DWORD ThreadId){dword_ThreadId = ThreadId;}  
  31. //Set / get whether the hook is running  
  32. bool BaseHook::GetEnabled(){return ( point_HookID != NULL );}  
  33. void BaseHook::SetEnabled(bool Enabled)  
  34. {  
  35.     if(Enabled != GetEnabled())  
  36.     {  
  37.         if(Enabled)Start();  
  38.         else Stop();  
  39.     }  
  40. }  
  41. //Set / get dll hinst  
  42. HINSTANCE BaseHook::GetHinstDll()  
  43. {  
  44.     try  
  45.     {  
  46.         return (hangle_HinstDLL != NULL)? hangle_HinstDLL:   
  47.                         GetModuleHandle