http://blog.csdn.net/friendan/article/details/12226201

xringm發表於2016-03-28

原文地址:http://blog.csdn.net/friendan/article/details/12226201

原文對我的幫助極大,正是因為看了原文,我才學會了HOOK,鑑於原文的排版不是很好,

又沒有原工程例子原始碼下載,因此我決定對其重新整理,文章後面附有我測試時的工程原始碼下載地址。

注:我測試的環境為Win7+VS2008+MFC

原文出處,好像是這篇:http://blog.csdn.net/glliuxueke/article/details/2702608      //後來才看到的

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

前言
       本文主要介紹瞭如何實現替換Windows上的API函式,實現Windows API Hook

(當然,對於socket的Hook只是其中的一種特例)。這種Hook API技術被廣泛的採用在一些領域中,

如螢幕取詞,個人防火牆等。這種API Hook技術並不是很新,但是涉及的領域比較寬廣,

要想做好有一定的技術難度。本文是採集了不少達人的以前資料並結合自己的實驗得出的心得體會,

在這裡進行總結髮表,希望能夠給廣大的讀者提供參考,達到拋磚引玉的結果。

-------------------------------------------------------------------------------------------------------------------------------------------------------------

問題
       最近和同學討論如何構建一個Windows上的簡單的個人防火牆。後來討論涉及到了如何讓程式關聯套接字埠,

替換windows API,螢幕取詞等技術。其中主要的問題有:

1) 採用何種機制來截獲socket的呼叫?

一般來說,實現截獲socket的方法有很多很多,最基本的,可以寫驅動,驅動也有很多種,TDI驅動, NDIS驅動,Mini port驅動…

由於我使用的是Win2000系統,所以截獲socket也可以用Windows SPI來進行。另外一種就是Windows API Hook技術。

由於我沒什麼硬體基礎,不會寫驅動,所以第一種方法沒有考慮,而用SPI相對比較簡單。

但是後來覺得Windows API Hook適應面更廣,而且覺得自己動手能學到不少東西,

就決定用Windows API Hook來嘗試做socket Hook.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2) API Hook的實現方法?

       實際上就是對系統函式的替換,當然實現替換的方法大概不下5,6種吧,可以參考《Windows核心程式設計》第22章。

不過我使用的方法與其不近相同,應該相對比較簡單易懂。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原理

       我們知道,系統函式都是以DLL封裝起來的,應用程式應用到系統函式時,應首先把該DLL載入到當前的程式空間中,

呼叫的系統函式的入口地址,可以通過 GetProcAddress函式進行獲取。當系統函式進行呼叫的時候,

首先把所必要的資訊儲存下來(包括引數和返回地址,等一些別的資訊),然後就跳轉到函式的入口地址,繼續執行。

其實函式地址,就是系統函式“可執行程式碼”的開始地址。那麼怎麼才能讓函式首先執行我們的函式呢?

呵呵,應該明白了吧,把開始的那段可執行程式碼替換為我們自己定製的一小段可執行程式碼,這樣系統函式呼叫時,

不就按我們的意圖乖乖行事了嗎?其實,就這麼簡單。Very very簡單。 :P

       實際的說,就可以修改系統函式入口的地方,讓他調轉到我們的函式的入口點就行了。

採用彙編程式碼就能簡單的實現Jmp XXXX, 其中XXXX就是要跳轉的相對地址。

我們的做法是:把系統函式的入口地方的內容替換為一條Jmp指令,目的就是跳到我們的函式進行執行。

而Jmp後面要求的是相對偏移,也就是我們的函式入口地址到系統函式入口地址之間的差異,再減去我們這條指令的大小。

用公式表達如下:(1)int nDelta = UserFunAddr – SysFunAddr - (我們定製的這條指令的大小);(2)Jmp nDleta;

為了保持原程式的健壯性,我們的函式裡做完必要的處理後,要回撥原來的系統函式,然後返回。

所以呼叫原來系統函式之前必須先把原來修改的系統函式入口地方給恢復,否則,

系統函式地方被我們改成了Jmp XXXX就會又跳到我們的函式裡,死迴圈了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
那麼說一下程式執行的過程。
       我們的dll“注射”入被hook的程式 -> 儲存系統函式入口處的程式碼 -> 替換掉程式中的系統函式入口指向我們的函式 -> 當系統函式被

呼叫,立即跳轉到我們的函式 -> 我們函式進行處理 -> 恢復系統函式入口的程式碼 -> 呼叫原來的系統函式 -> 再修改系統函式入口指向

我們的函式(為了下次hook)-> 返回。於是,一次完整的Hook就完成了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        好,這個問題明白以後,講一下下個問題,就是如何進行dll“注射”?即將我們的dll注射到要Hook的程式中去呢?

很簡單哦,這裡我們採用呼叫Windows提供給我們的一些現成的Hook來進行注射。舉個例子,滑鼠鉤子,

鍵盤鉤子大家都知道吧?我們可以給系統裝一個滑鼠鉤子,然後所有響應到滑鼠事件的程式,

就會“自動”(其實是系統處理了)載入我們的dll然後設定相應的鉤子函式。其實我們的目的只是需要讓被注射程式

載入我們的dll就可以了,我們可以再dll例項化的時候進行函式注射的,我們的這個滑鼠鉤子什麼都不幹的。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

簡單的例子OneAddOne

      講了上面的原理,現在我們應該實戰一下了。先不要考慮windows系統那些繁雜的函式,

我們自己編寫一個API函式來進行Hook與被Hook的練習吧,哈哈。

第一步,首先編寫一個Add.dll,很簡單,這個dll只輸出一個API函式,就是add啦。
新建一個win32 dll工程,


dllmain.cpp的內容:

  1. //千萬別忘記宣告WINAPI,否則呼叫的時候回產生宣告錯誤哦!  
  2. int WINAPI add(int a,int b)  
  3. {    
  4.     return a+b;  
  5. }  
  6.   
  7. BOOL APIENTRY DllMain( HANDLE hModule,   
  8.                       DWORD  ul_reason_for_call,   
  9.                       LPVOID lpReserved  
  10.                       )  
  11. {  
  12.     return TRUE;  
  13. }  
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然後別忘了在add.def裡面輸出函式add:
LIBRARY  Add
DESCRIPTION "ADD LA"
EXPORTS
 add  @1;


建完工程後,你會發現沒有Add.def檔案,這時我們自己新建一個Add.def檔案,然後新增到工程中即可,

新增Add.def檔案到工程後,我們還需要設定工程的屬性,將Add.def新增到【專案】-->【Add屬性】-->

【連結器】-->【輸入】-->【模組定義檔案】,如下圖所示,不這樣設定的話,我們新增的Add.def檔案是

不起作用的哦。


設定好後,編譯,ok,我們獲得了Add.dll

-----------------------------------------------------------------------------------------------------------------------------------------------------

得到Add.dll後,我們可以用一個小工具【dll函式檢視器】來開啟我們的Add.dll檔案,如果函式匯出成功的話,我們就可以

在裡面看到匯出的函式名字了,如下圖所示:


該工具下載地址:http://download.csdn.net/detail/friendan/6347455       //dll函式檢視器

----------------------------------------------------------------------------------------------------------------------------------------------------------

有了dll檔案後,接下來我們新建一個MFC對話方塊程式來呼叫該dll中匯出的函式add,

程式介面即執行效果截圖如下:


主要程式碼如下:

  1. //呼叫dll函式 add(int a,int b)  
  2. void CCallAddDlg::OnBnClickedBtnCallAdd()  
  3. {  
  4.     HINSTANCE hAddDll=NULL;  
  5.     typedef int (WINAPI*AddProc)(int a,int b);//函式原型定義  
  6.     AddProc add;  
  7.     if (hAddDll==NULL)  
  8.     {  
  9.         hAddDll=::LoadLibrary(_T("Add.dll"));//載入dll  
  10.     }  
  11.     add=(AddProc)::GetProcAddress(hAddDll,"add");//獲取函式add地址  
  12.   
  13.     int a=1;  
  14.     int b=2;  
  15.     int c=add(a,b);//呼叫函式  
  16.     CString tem;  
  17.     tem.Format(_T("%d+%d=%d"),a,b,c);  
  18.     AfxMessageBox(tem);  
  19. }  

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下來我們進行HOOK,即HOOK我們的Add.dll檔案中的函式int add(int a,int b)

新建一個MFC的 dll工程,工程名為Hook,然後我們在Hook.cpp檔案裡面編寫程式碼如下:

首先在頭部宣告如下變數:

  1. //變數定義  
  2. //不同Instance共享的該變數  
  3. #pragma data_seg("SHARED")  
  4. static HHOOK  hhk=NULL; //滑鼠鉤子控制程式碼  
  5. static HINSTANCE hinst=NULL; //本dll的例項控制程式碼 (hook.dll)  
  6. #pragma data_seg()  
  7. #pragma comment(linker, "/section:SHARED,rws")  
  8. //以上的變數共享哦!  
  9.   
  10. CString temp; //用於顯示錯誤的臨時變數  
  11. bool bHook=false//是否Hook了函式  
  12. bool m_bInjected=false//是否對API進行了Hook  
  13. BYTE OldCode[5]; //老的系統API入口程式碼  
  14. BYTE NewCode[5]; //要跳轉的API程式碼 (jmp xxxx)  
  15. typedef int (WINAPI*AddProc)(int a,int b);//add.dll中的add函式定義  
  16. AddProc add; //add.dll中的add函式  
  17. HANDLE hProcess=NULL; //所處程式的控制程式碼  
  18. FARPROC pfadd;  //指向add函式的遠指標  
  19. DWORD dwPid;  //所處程式ID  
  20. //end of 變數定義  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

編寫滑鼠鉤子安裝、解除安裝和處理函式:

  1. //滑鼠鉤子過程,什麼也不做,目的是注入dll到程式中  
  2. LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)  
  3. {  
  4.     return CallNextHookEx(hhk,nCode,wParam,lParam);  
  5. }  
  6.   
  7. //滑鼠鉤子安裝函式:  
  8. BOOL InstallHook()  
  9. {  
  10.   
  11.     hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);  
  12.   
  13.     return true;  
  14. }  
  15.   
  16. //解除安裝滑鼠鉤子函式  
  17. void UninstallHook()  
  18. {  
  19.     ::UnhookWindowsHookEx(hhk);  
  20. }  

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

在dll例項化函式InitInstance()中,初始化變數和進行注入:

  1. //在dll例項化中獲得一些引數  
  2. BOOL CHookApp::InitInstance()  
  3. {  
  4.     CWinApp::InitInstance();  
  5.   
  6.     //獲得dll 例項,程式控制程式碼  
  7.     hinst=::AfxGetInstanceHandle();  
  8.     DWORD dwPid=::GetCurrentProcessId();  
  9.     hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);   
  10.     //呼叫注射函式  
  11.     Inject();  
  12.     return TRUE;  
  13. }  

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

編寫注射函式,即HOOK函式Inject()了:

  1. //好,最重要的HOOK函式:  
  2. void Inject()  
  3. {  
  4.   
  5.     if (m_bInjected==false)  
  6.     { //保證只呼叫1次  
  7.         m_bInjected=true;  
  8.   
  9.         //獲取add.dll中的add()函式  
  10.         HMODULE hmod=::LoadLibrary(_T("Add.dll"));  
  11.         add=(AddProc)::GetProcAddress(hmod,"add");  
  12.         pfadd=(FARPROC)add;  
  13.   
  14.         if (pfadd==NULL)  
  15.         {  
  16.             AfxMessageBox(L"cannot locate add()");  
  17.         }  
  18.   
  19.         // 將add()中的入口程式碼儲存入OldCode[]  
  20.         _asm   
  21.         {   
  22.             lea edi,OldCode   
  23.                 mov esi,pfadd   
  24.                 cld   
  25.                 movsd   
  26.                 movsb   
  27.         }  
  28.   
  29.         NewCode[0]=0xe9;//實際上0xe9就相當於jmp指令  
  30.         //獲取Myadd()的相對地址  
  31.         _asm   
  32.         {   
  33.             lea eax,Myadd  
  34.                 mov ebx,pfadd   
  35.                 sub eax,ebx   
  36.                 sub eax,5   
  37.                 mov dword ptr [NewCode+1],eax   
  38.         }   
  39.         //填充完畢,現在NewCode[]裡的指令相當於Jmp Myadd  
  40.         HookOn(); //可以開啟鉤子了  
  41.     }  
  42. }  

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

編寫HOOK開啟和停止函式HookOn()和HookOff()

  1. //開啟鉤子的函式  
  2. void HookOn()   
  3. {   
  4.     ASSERT(hProcess!=NULL);  
  5.   
  6.     DWORD dwTemp=0;  
  7.     DWORD dwOldProtect;  
  8.   
  9.     //將記憶體保護模式改為可寫,老模式儲存入dwOldProtect  
  10.     VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);   
  11.     //將所屬程式中add()的前5個位元組改為Jmp Myadd   
  12.     WriteProcessMemory(hProcess,pfadd,NewCode,5,0);  
  13.     //將記憶體保護模式改回為dwOldProtect  
  14.     VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);  
  15.   
  16.     bHook=true;   
  17. }  
  18. //關閉鉤子的函式  
  19. void HookOff()//將所屬程式中add()的入口程式碼恢復  
  20. {   
  21.     ASSERT(hProcess!=NULL);  
  22.   
  23.     DWORD dwTemp=0;  
  24.     DWORD dwOldProtect;  
  25.   
  26.     VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);   
  27.     WriteProcessMemory(hProcess,pfadd,OldCode,5,0);   
  28.     VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);   
  29.     bHook=false;   
  30. }  

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

編寫我們自己的Myadd函式()

  1. //然後,寫我們自己的Myadd()函式  
  2. int WINAPI Myadd(int a,int b)  
  3. {  
  4.     //截獲了對add()的呼叫,我們給a,b都加1  
  5.     a=a+1;  
  6.     b=b+1;  
  7.   
  8.     HookOff();//關掉Myadd()鉤子防止死迴圈  
  9.   
  10.     int ret;  
  11.     ret=add(a,b);  
  12.   
  13.     HookOn();//開啟Myadd()鉤子  
  14.   
  15.     return ret;  
  16. }  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然後別忘記在hook.def裡面匯出我們的兩個函式 :

InstallHook  
UninstallHook 


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下來就可以進行HOOK的測試了,給前面的對話方塊程式,再新增兩個按鈕,一個用於安裝鉤子,另一個用於解除安裝鉤子,

程式和執行效果截圖如下:


//未HOOK之前


//HOOK之後


-------------------------------------------------------------------------------------------------------------------------------------------------------------------

安裝鉤子和解除安裝鉤子主要程式碼如下:

  1. HINSTANCE hinst=NULL;  
  2. //安裝滑鼠鉤子,進行HOOK  
  3. void CCallAddDlg::OnBnClickedBtnStartHook()  
  4. {  
  5.     typedef BOOL (CALLBACK *inshook)(); //函式原型定義  
  6.     inshook insthook;  
  7.       
  8.     hinst=LoadLibrary(_T("Hook.dll"));//載入dll檔案  
  9.     if(hinst==NULL)  
  10.     {  
  11.         AfxMessageBox(_T("no Hook.dll!"));  
  12.         return;  
  13.     }  
  14.     insthook=::GetProcAddress(hinst,"InstallHook");//獲取函式地址  
  15.     if(insthook==NULL)  
  16.     {  
  17.         AfxMessageBox(_T("func not found!"));  
  18.         return;  
  19.     }  
  20.     insthook();//開始HOOK  
  21. }  
  22.   
  23. //解除安裝滑鼠鉤子,停止HOOK  
  24. void CCallAddDlg::OnBnClickedBtnStopHook()  
  25. {  
  26.     if (hinst==NULL)  
  27.     {  
  28.         return;  
  29.     }  
  30.     typedef BOOL (CALLBACK *UnhookProc)(); //函式原型定義  
  31.     UnhookProc UninstallHook;  
  32.   
  33.     UninstallHook=::GetProcAddress(hinst,"UninstallHook");//獲取函式地址  
  34.     if(UninstallHook!=NULL)   
  35.     {  
  36.         UninstallHook();  
  37.     }  
  38.     if (hinst!=NULL)  
  39.     {  
  40.         ::FreeLibrary(hinst);  
  41.     }  
  42. }  

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上就是之前我看的那篇文章的主要內容了,關於HOOK系統API,我會在其它的文章裡面進行說明。

這裡再說一下原文的缺點,我認為其有兩個缺點:

1.停止HOOK時,沒有恢復被HOOK函式的入口。

2.沒有處理dll退出事件,沒有在dll退出事件中恢復被HOOK函式入口。

以上兩個缺點,很容易導致程式的崩潰,因此在我的例子程式中,都對它們進行了處理:

  1. //解除安裝滑鼠鉤子函式  
  2. void UninstallHook()  
  3. {  
  4.     if (hhk!=NULL)  
  5.     {  
  6.         ::UnhookWindowsHookEx(hhk);  
  7.     }  
  8.     HookOff();//記得恢復原函式入口  
  9. }  
  10.   
  11. //dll退出時  
  12. int CHookApp::ExitInstance()  
  13. {  
  14.     HookOff();//記得恢復原函式入口  
  15.     return CWinApp::ExitInstance();  
  16. }  

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以上我這個例子工程的下載地址:hook dll檔案中的函式add.zip

http://download.csdn.net/detail/friendan/6348209

友情提示:我在Debug模式執行程式時,HOOK會失敗,在Release模式執行程式則HOOK成功。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

相關文章