Windows API Hook
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
原文地址:http://blog.sina.com.cn/s/blog_628821950100xmuc.html
原文對我的幫助極大,正是因為看了原文,我才學會了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的內容:
//千萬別忘記宣告WINAPI,否則呼叫的時候回產生宣告錯誤哦!int WINAPI add(int a,int b){ return a+b;}BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ return TRUE;}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
然後別忘了在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,
程式介面即執行效果截圖如下:
主要程式碼如下:
//呼叫dll函式 add(int a,int b)void CCallAddDlg::OnBnClickedBtnCallAdd(){ HINSTANCE hAddDll=NULL; typedef int (WINAPI*AddProc)(int a,int b);//函式原型定義 AddProc add; if (hAddDll==NULL) { hAddDll=::LoadLibrary(_T("Add.dll"));//載入dll } add=(AddProc)::GetProcAddress(hAddDll,"add");//獲取函式add地址 int a=1; int b=2; int c=add(a,b);//呼叫函式 CString tem; tem.Format(_T("%d+%d=%d"),a,b,c); AfxMessageBox(tem);}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下來我們進行HOOK,即HOOK我們的Add.dll檔案中的函式int add(int a,int b)
新建一個MFC的 dll工程,工程名為Hook,然後我們在Hook.cpp檔案裡面編寫程式碼如下:
首先在頭部宣告如下變數:
//變數定義//不同Instance共享的該變數#pragma data_seg("SHARED")static HHOOK hhk=NULL; //滑鼠鉤子控制代碼static HINSTANCE hinst=NULL; //本dll的例項控制代碼 (hook.dll)#pragma data_seg()#pragma comment(linker, "/section:SHARED,rws")//以上的變數共享哦!CString temp; //用於顯示錯誤的臨時變數bool bHook=false; //是否Hook了函式bool m_bInjected=false; //是否對API進行了HookBYTE OldCode[5]; //老的系統API入口程式碼BYTE NewCode[5]; //要跳轉的API程式碼 (jmp xxxx)typedef int (WINAPI*AddProc)(int a,int b);//add.dll中的add函式定義AddProc add; //add.dll中的add函式HANDLE hProcess=NULL; //所處程序的控制代碼FARPROC pfadd; //指向add函式的遠指標DWORD dwPid; //所處程序ID//end of 變數定義
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
編寫滑鼠鉤子安裝、解除安裝和處理函式:
//滑鼠鉤子過程,什麼也不做,目的是注入dll到程式中LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam){ return CallNextHookEx(hhk,nCode,wParam,lParam);}//滑鼠鉤子安裝函式:BOOL InstallHook(){ hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0); return true;}//解除安裝滑鼠鉤子函式void UninstallHook(){ ::UnhookWindowsHookEx(hhk);}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在dll例項化函式InitInstance()中,初始化變數和進行注入:
//在dll例項化中獲得一些引數BOOL CHookApp::InitInstance(){ CWinApp::InitInstance(); //獲得dll 例項,程序控制代碼 hinst=::AfxGetInstanceHandle(); DWORD dwPid=::GetCurrentProcessId(); hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); //呼叫注射函式 Inject(); return TRUE;}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
編寫注射函式,即HOOK函式Inject()了:
//好,最重要的HOOK函式:void Inject(){ if (m_bInjected==false) { //保證只調用1次 m_bInjected=true; //獲取add.dll中的add()函式 HMODULE hmod=::LoadLibrary(_T("Add.dll")); add=(AddProc)::GetProcAddress(hmod,"add"); pfadd=(FARPROC)add; if (pfadd==NULL) { AfxMessageBox(L"cannot locate add()"); } // 將add()中的入口程式碼儲存入OldCode[] _asm { lea edi,OldCode mov esi,pfadd cld movsd movsb } NewCode[0]=0xe9;//實際上0xe9就相當於jmp指令 //獲取Myadd()的相對地址 _asm { lea eax,Myadd mov ebx,pfadd sub eax,ebx sub eax,5 mov dword ptr [NewCode+1],eax } //填充完畢,現在NewCode[]裡的指令相當於Jmp Myadd HookOn(); //可以開啟鉤子了 }}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
編寫HOOK開啟和停止函式HookOn()和HookOff()
//開啟鉤子的函式void HookOn() { ASSERT(hProcess!=NULL); DWORD dwTemp=0; DWORD dwOldProtect; //將記憶體保護模式改為可寫,老模式儲存入dwOldProtect VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); //將所屬程序中add()的前5個位元組改為Jmp Myadd WriteProcessMemory(hProcess,pfadd,NewCode,5,0); //將記憶體保護模式改回為dwOldProtect VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp); bHook=true; }//關閉鉤子的函式void HookOff()//將所屬程序中add()的入口程式碼恢復{ ASSERT(hProcess!=NULL); DWORD dwTemp=0; DWORD dwOldProtect; VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfadd,OldCode,5,0); VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp); bHook=false; }
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
編寫我們自己的Myadd函式()
//然後,寫我們自己的Myadd()函式int WINAPI Myadd(int a,int b){ //截獲了對add()的呼叫,我們給a,b都加1 a=a+1; b=b+1; HookOff();//關掉Myadd()鉤子防止死迴圈 int ret; ret=add(a,b); HookOn();//開啟Myadd()鉤子 return ret;}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
然後別忘記在hook.def裡面匯出我們的兩個函式 :
InstallHook
UninstallHook
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下來就可以進行HOOK的測試了,給前面的對話方塊程式,再新增兩個按鈕,一個用於安裝鉤子,另一個用於解除安裝鉤子,
程式和執行效果截圖如下:
//未HOOK之前
//HOOK之後
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
安裝鉤子和解除安裝鉤子主要程式碼如下:
HINSTANCE hinst=NULL;//安裝滑鼠鉤子,進行HOOKvoid CCallAddDlg::OnBnClickedBtnStartHook(){ typedef BOOL (CALLBACK *inshook)(); //函式原型定義 inshook insthook; hinst=LoadLibrary(_T("Hook.dll"));//載入dll檔案 if(hinst==NULL) { AfxMessageBox(_T("no Hook.dll!")); return; } insthook=::GetProcAddress(hinst,"InstallHook");//獲取函式地址 if(insthook==NULL) { AfxMessageBox(_T("func not found!")); return; } insthook();//開始HOOK}//解除安裝滑鼠鉤子,停止HOOKvoid CCallAddDlg::OnBnClickedBtnStopHook(){ if (hinst==NULL) { return; } typedef BOOL (CALLBACK *UnhookProc)(); //函式原型定義 UnhookProc UninstallHook; UninstallHook=::GetProcAddress(hinst,"UninstallHook");//獲取函式地址 if(UninstallHook!=NULL) { UninstallHook(); } if (hinst!=NULL) { ::FreeLibrary(hinst); }}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上就是之前我看的那篇文章的主要內容了,關於HOOK系統API,我會在其它的文章裡面進行說明。
這裡再說一下原文的缺點,我認為其有兩個缺點:
1.停止HOOK時,沒有恢復被HOOK函式的入口。
2.沒有處理dll退出事件,沒有在dll退出事件中恢復被HOOK函式入口。
以上兩個缺點,很容易導致程式的崩潰,因此在我的例子程式中,都對它們進行了處理:
//解除安裝滑鼠鉤子函式void UninstallHook(){ if (hhk!=NULL) { ::UnhookWindowsHookEx(hhk); } HookOff();//記得恢復原函式入口}//dll退出時int CHookApp::ExitInstance(){ HookOff();//記得恢復原函式入口 return CWinApp::ExitInstance();}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上我這個例子工程的下載地址:hook dll檔案中的函式add.zip
http://download.csdn.net/detail/friendan/6348209
友情提示:我在Debug模式執行程式時,HOOK會失敗,在Release模式執行程式則HOOK成功。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
您的十分滿意是我追求的宗旨。
您的一點建議是我後續的動力。