有關windows鉤子使用的兩篇文章

飛翔的黃瓜發表於2017-08-21

ps:一下兩篇文章程式碼都出現部分錯誤,稍加修改即可執行

一.截獲 Windows socket API

原文地址http://blog.csdn.net/glliuxueke/article/details/2702608

1前言

本文主要介紹瞭如何實現替換Windows上的API函式,實現Windows API Hook(當然,對於socket的Hook只是其中的一種特例)。這種Hook API技術被廣泛的採用在一些領域中,如螢幕取詞,個人防火牆等。
這種API Hook技術並不是很新,但是涉及的領域比較寬廣,要想做好有一定的技術難度。本文是採集了不少達人的以前資料並結合自己的實驗得出的心得體會,在這裡進行總結髮表,希望能夠給廣大的讀者提供參考,達到拋磚引玉的結果。


2問題
最近和同學討論如何構建一個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章。不過我使用的方法與其不近相同,應該相對比較簡單易懂。


3原理
我們知道,系統函式都是以DLL封裝起來的,應用程式應用到系統函式時,應首先把該DLL載入到當前的程式空間中,呼叫的系統函式的入口地址,可以通過 GetProcAddress函式進行獲取。當系統函式進行呼叫的時候,首先把所必要的資訊儲存下來(包括引數和返回地址,等一些別的資訊),然後就跳轉到函式的入口地址,繼續執行。其實函式地址,就是系統函式“可執行程式碼”的開始地址。那麼怎麼才能讓函式首先執行我們的函式呢?呵呵,應該明白了吧,把開始的那段可執行程式碼替換為我們自己定製的一小段可執行程式碼,這樣系統函式呼叫時,不就按我們的意圖乖乖行事了嗎?其實,就這麼簡單。Very very簡單。 :P
實際的說,就可以修改系統函式入口的地方,讓他調轉到我們的函式的入口點就行了。採用彙編程式碼就能簡單的實現Jmp XXXX, 其中XXXX就是要跳轉的相對地址。
我們的做法是:把系統函式的入口地方的內容替換為一條Jmp指令,目的就是跳到我們的函式進行執行。而Jmp後面要求的是相對偏移,也就是我們的函式入口地址到系統函式入口地址之間的差異,再減去我們這條指令的大小。用公式表達如下:
int nDelta = UserFunAddr – SysFunAddr - (我們定製的這條指令的大小);
Jmp nDleta;
為了保持原程式的健壯性,我們的函式裡做完必要的處理後,要回撥原來的系統函式,然後返回。所以呼叫原來系統函式之前必須先把原來修改的系統函式入口地方給恢復,否則,系統函式地方被我們改成了Jmp XXXX就會又跳到我們的函式裡,死迴圈了。

那麼說一下程式執行的過程。
我們的dll“注射”入被hook的程式 -> 儲存系統函式入口處的程式碼 -> 替換掉程式中的系統函式入口指向我們的函式 -> 當系統函式被呼叫,立即跳轉到我們的函式 -> 我們函式進行處理 -> 恢復系統函式入口的程式碼 -> 呼叫原來的系統函式 -> 再修改系統函式入口指向我們的函式(為了下次hook)-> 返回。
於是,一次完整的Hook就完成了。

好,這個問題明白以後,講一下下個問題,就是如何進行dll“注射”?即將我們的dll注射到要Hook的程式中去呢?
很簡單哦,這裡我們採用呼叫Windows提供給我們的一些現成的Hook來進行注射。舉個例子,滑鼠鉤子,鍵盤鉤子,大家都知道吧?我們可以給系統裝一個滑鼠鉤子,然後所有響應到滑鼠事件的程式,就會“自動”(其實是系統處理了)載入我們的dll然後設定相應的鉤子函式。其實我們的目的只是需要讓被注射程式載入我們的dll就可以了,我們可以再dll例項化的時候進行函式注射的,我們的這個滑鼠鉤子什麼都不幹的。


4簡單的例子OneAddOne
講了上面的原理,現在我們應該實戰一下了。先不要考慮windows系統那些繁雜的函式,我們自己編寫一個API函式來進行Hook與被Hook的練習吧,哈哈。

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

#include "stdafx.h"

int WINAPI add(int a,int b){  //千萬別忘記宣告WINAPI 否則呼叫的時候回產生宣告錯誤哦!
 return a+b;
}

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
      )
{
    return TRUE;
}


然後別忘了在add.def裡面輸出函式:

LIBRARY  Add
DESCRIPTION "ADD LA"
EXPORTS
 add  @1;

編譯,ok,我們獲得了add.dll

////////////////////
第二步,編寫1+1主程式
新建一個基於對話方塊的工程One,
在 OnOK()裡面呼叫add函式:
ConeDlg.h裡面加入一些變數的宣告:
….
Public:
 HINSTANCE hAddDll;
 typedef int (WINAPI*AddProc)(int a,int b);
 AddProc add;

ConeDlg.cpp裡進行呼叫:

void COneDlg::OnOK() 
{
 // TODO: Add extra validation here
 if (hAddDll==NULL)
  hAddDll=::LoadLibrary("add.dll");

 add=(AddProc)::GetProcAddress(hAddDll,"add");

 int a=1;
 int b=2;
 int c=add(a,b);
 CString tem;
 temp.Format("%d+%d=%d",a,b,c);
 AfxMessageBox(temp);
}

OK,編譯執行,正確的話就會顯示1+2=3咯
////////////////////
第3步,要動手Hook咯,爽阿
新建一個MFC的 dll工程,Hook
在Hook.dll工程裡:

新增一個滑鼠Hook MouseProc,滑鼠hook什麼也不做
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
 LRESULT RetVal= CallNextHookEx(hhk,nCode,wParam,lParam);
 return RetVal;
}

新增滑鼠鉤子的安裝和解除安裝函式:
BOOL InstallHook()
{
 
 hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);

 ….
 return true;
}

void UninstallHook()
{
 ::UnhookWindowsHookEx(hhk);
}

再例項化中獲得一些引數
BOOL CHookApp::InitInstance() 
{
 獲得dll 例項,程式控制程式碼
hinst=::AfxGetInstanceHandle();
 DWORD dwPid=::GetCurrentProcessId();
 hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); 
//呼叫注射函式
 Inject();
 return CWinApp::InitInstance();
}

好,最重要的注射函式:
void Inject()
{
 
 if (m_bInjected==false)
 { //保證只呼叫1次
  m_bInjected=true;

  //獲取add.dll中的add()函式
  HMODULE hmod=::LoadLibrary("add.dll");
  add=(AddProc)::GetProcAddress(hmod,"add");
  pfadd=(FARPROC)add;
  
  if (pfadd==NULL)
  {
   AfxMessageBox("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(); //可以開啟鉤子了
 }
}

開啟鉤子的函式
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()函式
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  
 MouseProc
 Myadd
 UninstallHook 
四個函式。
(全部的程式會貼在最後面的)

好到這裡基本上大功告成咯

////////////////////

第4步,我們就可以修改前面的One的測試程式了

增加一個安裝鉤子的函式/按鈕
void COneDlg::doHook() 
{
 hinst=LoadLibrary("hook.dll");
 if(hinst==NULL)
 {
  AfxMessageBox("no hook.dll!");
  return;
 }
 typedef BOOL (CALLBACK *inshook)(); 
 inshook insthook;

 insthook=::GetProcAddress(hinst,"InstallHook");
 if(insthook==NULL)
 {
  AfxMessageBox("func not found!");
  return;
 }

 DWORD pid=::GetCurrentProcessId();
 BOOL ret=insthook();
}

別忘了退出時卸掉鉤子
void COneDlg::OnCancel() 
{
 // TODO: Add extra cleanup here
 typedef BOOL (CALLBACK *UnhookProc)(); 
 UnhookProc UninstallHook;

 UninstallHook=::GetProcAddress(hinst,"UninstallHook");
 if(UninstallHook==NULL) UninstallHook();
 if (hinst!=NULL)
 {
  ::FreeLibrary(hinst);
 }
 if (hAddDll!=NULL)
 {
  ::FreeLibrary(hAddDll);
 }
 CDialog::OnCancel();
}

////////////////////

好了,大功告成咯,現在執行一下One,
先點Ok,測試1+2=3 沒問題,
然後點Hook,安裝鉤子,,顯示installhook ok,然後再點一下ok,
哈哈,發現結果1+2=5 !!!???
Hook成功啦!
附上Hook.cpp的原碼
/////////////////////
Hook.cpp

// Hook.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "Hook.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CHookApp

BEGIN_MESSAGE_MAP(CHookApp, CWinApp)
 //{{AFX_MSG_MAP(CHookApp)
  // NOTE - the ClassWizard will add and remove mapping macros here.
  //    DO NOT EDIT what you see in these blocks of generated code!
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CHookApp construction

CHookApp::CHookApp()
{
 // TODO: add construction code here,
 // Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CHookApp object

CHookApp theApp;

//變數定義

//不同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進行了Hook
BYTE 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 變數定義

//函式定義
void HookOn(); //開啟鉤子
void HookOff(); //關閉鉤子
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam); //滑鼠鉤子函式
void Inject(); //具體進行注射,替換入口的函式
int WINAPI Myadd(int a,int b); //我們定義的新的add()函式
BOOL InstallHook(); //安裝鉤子函式
void UninstallHook(); //解除安裝鉤子函式
//end of 函式定義


BOOL CHookApp::InitInstance() 
{
 //獲取自身dll控制程式碼
 hinst=::AfxGetInstanceHandle();
 //獲取所屬程式id和控制程式碼
 DWORD dwPid=::GetCurrentProcessId();
 hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwid); 
 //呼叫函式注射
 Inject();
 
 return CWinApp::InitInstance();
}

int CHookApp::ExitInstance() 
{
 // 如果add鉤子開著,則將其關閉
 if (bHook)
  HookOff();

 return CWinApp::ExitInstance();
}

BOOL InstallHook()
{
 
 hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,hinst,0);

 if (hhk==NULL)
 {
  DWORD err=::GetLastError();
  temp.Format("hook install failed!:%d",err);
  AfxMessageBox(temp);
  return false;
 }

 AfxMessageBox("hook installed!");
 return true;
}

void UninstallHook()
{
 if (hhk!=NULL)
 ::UnhookWindowsHookEx(hhk);
 AfxMessageBox("Unhooked!");
}

void Inject()
{
 
 if (m_bInjected==false)
 {
  m_bInjected=true;


  //讀取add.dll並查詢到add()函式
  HMODULE hmod=::LoadLibrary("add.dll");
  add=(AddProc)::GetProcAddress(hmod,"add");
  pfadd=(FARPROC)add;
  
  if (pfadd==NULL)
  {
   AfxMessageBox("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();
 }
}

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;
}

LRESULT CALLBACK MouseProc(
  int nCode,      // hook code
  WPARAM wParam,  // message identifier
  LPARAM lParam   // mouse coordinates
  )
{

 LRESULT RetVal= CallNextHookEx(hhk,nCode,wParam,lParam);
 return RetVal;
}


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; 
}

//////////////////////
hook.def

LIBRARY      "Hook"
DESCRIPTION  'Hook Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can Go here
 InstallHook  
 MouseProc
 Myadd
 UninstallHook

5 實戰Hook Win Socket API

如果對於上面這章的的理解沒有什麼問題的化,我想對於windows API的Hook應該也就是按部就班的做就可以了,不會有太大的問題,只要足夠細心耐心,善於處理髮現一些細節問題就行了。

當然,為了使我們的程式使用起來更靈活方便,必須要改進我們的程式結構,因為我們要Hook很多的API,不可能為每個API制定一套注射、截獲的函式,
所以,我設計了一個類來儲存某個API的一些資訊:
class CHookFuncInfo
{
public:
 CHookFuncInfo(LPCTSTR szLibName,LPCTSTR szOldname,LPCTSTR szNewName)
 {
  //建構函式必須設定三個引數
  strcpy(LibraryName,szLibName);
  strcpy(OldFuncName,szOldname);
  strcpy(NewFuncName,szNewName);
  Injected=false;
 }
public:
 HINSTANCE hinstLib; //該函式所在dll模組的例項控制程式碼
 FARPROC fpFunc;  //指向原函式的指標
 BYTE OldCode[5]; //原函式入口處的程式碼
 BYTE NewCode[5]; //跳轉到我們函式入口地址的程式碼Jmp xxxx
 FARPROC fpMyFunc; //指向我們函式地址的指標
 char LibraryName[256]; //該函式所屬的dll模組的名稱
 char OldFuncName[256]; //原API函式名
 char NewFuncName[256]; //我們程式中定義的新API函式名
 bool Injected; //該函式是否被Hook
};

對某API函式的注射都可以用Inject()來處理
void Inject(CHookFuncInfo* phinfo);
開/關某個API可以用HookOn/HookOff來處理
void HookOff(CHookFuncInfo * phinfo);
void HookOn(CHookFuncInfo * phinfo);

需要注意的是,在彙編程式碼裡,不可以直接對物件的指標進行呼叫
即 mov eax, phinfo->fpFunc;諸如此類的程式碼是不被允許的。
所以我們要加入新的變數,處理完畢,將變數值傳回給傳入的物件指標:
void Inject(CHookFuncInfo * phinfo)
{
 …….

 FARPROC fpFunc=phinfo->fpFunc;
 BYTE OldCode[5],NewCode[5];
……
 彙編程式碼
……
  memcpy(phinfo->OldCode,OldCode,5);
  memcpy(phinfo->NewCode,NewCode,5);  
  HookOn(phinfo);//開啟鉤子
 return;
}


舉個例子:
CHookFuncInfo hinfoSendto("wsock32.dll","sendto","MySendto");
//建立一個sendto函式的API鉤子
然後注射-〉開啟鉤子-〉…

下面是我定義的MySendto()的程式碼:
int WINAPI MySendto(
  SOCKET s,                        
  const char FAR *buf,            
  int len,                         
  int flags,                       
  const struct sockaddr FAR *to,  
  int tolen                        
  )
{
 int ret;
 HookOff(&hinfoSendto);//關鉤子

 //下面這段就是通過WM_COPYDATA傳遞訊息的方法,
//將一些sendto()的資料通過字串方式傳遞給
//名為”XsockSpy”的窗體。
//XsockSpy窗體只需要響應對WM_COPYDA他的處理和顯示就OK了,很簡單。

 HWND hwnd=::FindWindow("#32770","XSockSpy");
 CString temp;
 temp.Format("pid:%d sending %d bytes",g_pid,len);
 if(hwnd!=NULL)
 {
  COPYDATASTRUCT cpdata;
  cpdata.dwData=0;
  cpdata.lpData=temp.GetBuffer(0);
  cpdata.cbData=temp.GetLength();
  ::SendMessage(hwnd,WM_COPYDATA,0,(LPARAM)&cpdata);
 }
 
 //呼叫真正的sendto()函式
 ret=::sendto(s,buf,len,flags,to,tolen);
 if (ret==SOCKET_ERROR
 {
  temp.Format("send error:%d",WSAGetLastError());
  AfxMessageBox(temp);
 }
 //開鉤子
 HookOn(&hinfoSendto);
 return ret;
};

好了,是不是很簡單啊?
打得很累了,我想大家基本生可以明白了吧?

如果需要程式碼的可以向我來索要,發信到 yoyohon@etang.com註明“索要程式碼”就可以了。
不過由於時間關係,截獲socket的程式碼我僅僅實現了sendto()這個函式的截獲,而且程式碼可能也比較臨亂吧,大家見諒了!

歡迎大家討論,發表看法,提出異議!

最後感謝一下對本文又幫助的人:

pingfanxin
Win2K下的Api函式的攔截
http://www.yourblog.org/Data/20044/62890.html


TopLevel
有關API HOOK方面的一些淺釋 
http://dev.csdn.net/develop/article/27/27732.shtm


二。使用windows鉤子捕獲程式的啟動和關閉訊息

原文地址http://blog.csdn.net/maoenpei002/article/details/5358601

2012年12月13日補充:

這篇文章寫的時候是我還在上學的時候,所以不管是從技術實現角度還是文筆都顯得很嫩,在此向所有無意間看到這篇文章的人表示抱歉。我寫了這篇文章之後2年有人想問我要原始碼,唉,如果我下次寫文章一定貼上原始碼,不過那麼老的程式碼我實在是不大情願找出來了。

我希望這篇文章已經把實現原理的每一個細節都點到了,雖然講了很多廢話,但是細節都埋在廢話當中。

我自己簡單看了一下我自己的文章(說實話我也有點忘記了),然後把實現流程做一個歸納:

1、在想要收到通知的專案(簡稱“原專案”)中,至少要包含一個視窗用以接受訊息,並且必須保證在別的程式中能找到這個視窗控制程式碼。

2、建立一個dll專案(簡稱“新專案”),在dllMain函式中根據DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH向原視窗傳送訊息。

3、新專案中匯出兩個函式,一個用來增加hook一個用來刪除hook,這兩個函式的實現就在程式碼中(StartupHook/CloseHook),這是實現hook的基本技術。

4、想一個辦法載入一次這個dll,比方說在原專案中呼叫LoadLibrary,然後去調StartupHook,這樣在執行時,windows系統會把這個dll強制插入到所有的程式中(除了系統認為沒有許可權插入dll的程式),以及會為新建立的程式插入此dll。

5、第一次載入dll是手動的,之後為絕大部分程式“注射”dll是由系統完成的,最後解除安裝也是手動的,此時系統會把此dll從那些強制注射的程式中解除安裝掉。這其實就是整個程式的全部執行流程了。


以下是原文章內容:


第二次寫文章,不好意思,和第一次理由相同,就是網上沒有找到符合此問題的滿意答案。

 

在一開頭,我們需要統一語言,本人使用C++開發(確切的說是練習開發)windows程式,不是MFC。

還有本人使用Visual Studio 2008作為整合開發環境。

 

不敢稱此為技術,只能叫方法。前面的三點全是不同情況的分析(意味著廢話),需要馬上知道方法的請直接看第四點。

我的需求是製作一個工作管理員。

方法有幾種,可供參考:

 

第一,用視窗的計時器機制,不斷列舉程式。

(順便提及一下,關於如何列舉程式,建議學習CreateToolhelp32Snapshot及相關函式的使用,網上亦有文可考,在此不做累述,因為不是重點。)

這麼做當然很方便,但是會出現一些問題,首先,設定一個計時器(SetTimer)的開銷比較大,一個工作管理員本來就是小程式,如果為了偶爾發生的一些CreateProcess函式而設定一個不停地做無用功的計時器,過於浪費系統資源;其次,你的計時器時間間隔設定成多少?如果時間長了,就不能及時的反應程式資訊,如果時間短了,自然頻率就上去了,開銷增大。

所以此方法適合widows程式的初學者嘗試學習,不應該作為一個軟體成品的一部分。

 

第二,使用windows的訊息鉤子技術,鉤取程式啟動和關閉訊息。

當然,此方法完全不能使用,只是個人在一段時期內曾經用過,但是比第一種方法還要差。

首先,訊息鉤子只適用與“存在訊息傳送和接受”的程式,換句話說,就是視窗程式,windows這個作業系統並沒有提供一種機制,可以在核心模式下(程式一級)捕獲訊息,當然是出於安全性考慮。所以只能傳送訊息給視窗,那麼就馬上否定了能夠抓到後臺執行程式的可能性。

其次,對於一個視窗程式而言,什麼訊息代表它的建立和滅亡?顯然WM_CREATE和WM_QUIT訊息(或者相關訊息),但是很明顯,有一條訊息不是windows鉤子能夠抓到的,那就是WM_CREATE訊息,因為windows訊息鉤子只能鉤那些存在於訊息佇列中的訊息,而WM_CREATE並不進訊息佇列,所以適用範圍又減小了一半。

 

前兩種方法是我最開始使用的方法,2個方法結合了使用,所以既沒能保住效率,也沒能保住即時性。

 

第三種方法比較難理解,但是是一種“正確”的方法,就是說它完全可以實現我們需要的功能,就是使用API Hook技術。

當然,這個方法需要深刻理解PE格式還有windows的核心工作原理。

我們知道windows啟動一個程式,一定會呼叫一個API函式就是CreateProcess(),而結束一個程式則可能用到TerminateProcess()和ExitProcess(),那麼我只要知道程式什麼時候呼叫了這些函式,就可以明確地知道開啟程式、關閉程式事件。

(當然這裡也沒法詳細講解API Hook技術,因為不是本文重點,所以需要了解的朋友請自己參考《windows核心程式設計》上的內容,裡面有詳細的解釋說明,網上也有,但是講得都不是很能說明問題(個人觀點),因此建議還是直接看書比較好。我的書是第四版,在第四部分,22章裡面講到這個技術。)

此技術當然也有問題,我掛接了一個API且不說很煩,我還需要手動把dll插到各個能夠呼叫CreateProcess的函式中,還好我們一般執行程式都是用雙擊點開的,那麼只要掛接explorer.exe就行了;但是萬一有人使用命令列模式開啟呢?還需要掛接cmd.exe和taskmgr.exe(預設的那個工作管理員也具有命令列功能)。

 

 

第四種方法不是我最近想到的,但是是我最近忽然意識到的,只能怪我原來對於windows鉤子和dll的理解不夠深刻,所以沒能發現原來這麼簡單就可以了。

需要一節基本的預備知識:dll對映機制、鉤子原理,當然還有windows程式如何呼叫一個dll和視窗機制。

 

1、把沒用的先去掉,視窗機制是為了能把捕獲到的事件傳送給我需要的程式,我前面說了,windows並沒有提供一種機制能夠使得程式與程式之間能夠自由通訊,所以需要依賴視窗,好在工作管理員不可能沒有視窗(不然拿什麼現實給使用者?總不好自己寫顯示卡驅動吧^_^),所以只要捕獲到之後對著視窗傳送訊息就OK了。

 

2、dll是一種“程式碼共享”機制,為了節省實體記憶體。dll本身不能執行任何程式碼,它屬於“應用程式”,但是沒有程式入口函式;它可以分配記憶體但是隻能在對映到某一個程式地址空間之後才能分配,而分配的記憶體也不屬於dll而是屬於載入dll的程式和執行緒;可以說,當dll被載入了之後幾乎失去了它作為dll的所有特徵標誌(引用《windows核心程式設計》原話)。我的理解就是如果dll沒有被對映,它沒有任何可利用價值。

 

3、鉤子的原理也在核心程式設計中講到,用一句話來簡單描述,就是“一個強勢插入的dll”。

比如對於一個訊息鉤子,如果訊息佇列中一條訊息準備傳送到某程式的某視窗,那麼系統會首先檢查是否為這個程式插入了訊息鉤子,如果有,那麼檢查是否將鉤子所在的dll對映到了程式地址空間,如果沒有,那麼進行一次對映,如果有,那麼需要檢查資料一致性(這個由系統完成),接下來就是呼叫我們提供給訊息鉤子的那個回撥函式,直到回撥函式執行完畢,那麼訊息才最終傳送給應用程式。

 

4、剛剛我已經講了,dll不會“自動”地執行程式碼,而是隻能由程式呼叫LoadLibrary()函式載入到地址空間裡面之後才能夠體現它的價值。但是雖然它沒有入口函式,卻有一個訊息通知函式,函式原型為:BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad)    這個函式的原始碼可以由整合開發環境自動生成。

這個函式的唯一功能就是提供了程式鉤取、程式釋放、執行緒鉤取、執行緒釋放4個事件的訊息通知,此函式非常強大,在此不予多講。

 

好了,關鍵在這第四點裡,我們現在所需要的不就是程式啟動通知嗎,而一個程式載入一個dll的事件是可以被我們捕捉到的。那麼自然可以想到:

我們的目的是:我希望設計一個程式,這個程式能夠讓作業系統做出一個改變,這個改變使得每當一個程式啟動/關閉的時候都能夠給我的程式傳送一個訊息以指明發生的啟動/關閉事件。

有了第四點,這個問題等價於:我希望能夠設計一個程式,這個程式能夠讓作業系統做出一種改變,這種改變使得每當一個程式啟動的時候,都能夠載入一個指定的dll,這下不就行了?

那麼怎麼保證後面說的那種情況呢?顯然,“鉤子”可以實現這個功能啊。

 

綜上所述,最關鍵的一句話就是:我只要設計一個dll,讓這個dll成為一個鉤子,插入到所有的程式,當我在這個dll的程式鉤取、釋放通知裡面實現各一個函式,這個函式的功能是傳送一條訊息到我所指定的視窗中去。

 

講到現在,問題解決了,如果上面講的話能夠完全明白,以下的程式碼就沒有用處了,以便於不浪費一些理解能力非常強的人的寶貴的時間。

 

//---------------------------DLL部分--------------------------------------------

//---------------------------ProcHook.h---------------------------------------

extern "C"
{
__declspec(dllexport)
void StartupHook(void);

__declspec(dllexport)
void CloseHook(void);

}

 

void ProcessHookUp();

void ProcessHookDelete();

//--------------------------End Of File----------------------------------------

 

//--------------------------dllmain.cpp---------------------------------------
#include"ProcHook.h"

int count = 0;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
  ProcessHookUp();
  break;
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
  break;
 case DLL_PROCESS_DETACH:
  ProcessHookDelete();
  break;
 }
 return TRUE;
}
//--------------------------End Of File----------------------------------------

 

//--------------------------ProcHook.cpp--------------------------------
#include"ProcHook.h"

HHOOK hThis = NULL;

LRESULT CALLBACK ProcessClose(int nCode, WPARAM wParam, LPARAM lParam)
{
 return CallNextHookEx(hThis, nCode, wParam, lParam);
}

void StartupHook(void)
{
 hThis = SetWindowsHookExW(WH_GETMESSAGE, ProcessClose, GetModuleHandleW(L"ProcHook.dll"), 0);
}

void CloseHook(void)
{
 UnhookWindowsHookEx(hThis);
}

void ProcessHookUp()
{
 DWORD proId = GetCurrentProcessId();
   HWND hWnd = FindWindow(L"MyClass", L"MyWindow");
   if (hWnd)
    PostMessage(hWnd, WM_WINDOW, 0, proId);
   else
    CloseHook();
}

void ProcessHookDelete()
{
 DWORD proId = GetCurrentProcessId();
   HWND hWnd = FindWindow(L"MyClass", L"MyWindow");
   if (hWnd)
    PostMessage(hWnd, WM_WINDOW, 1, proId);
   else
    CloseHook();
}

//--------------------------End Of File----------------------------------------

 

//--------------------------呼叫部分-------------------------------------------

#include"ProcHook.h"

#pragma comment(lib, "ProcHook.lib")

 

class A()

{

public:

A();

~A();

};

 

A::A()

{

StartupHook();

}

 

A::~A()

{

CloseHook();

}

//-------------------------End Of File----------------------------------------

 

最後在WinMain函式(或者其他的程式切入函式)中宣告一個A類的物件即可,當函式執行完畢自動撤銷。或者視窗被清除了也可以自動撤銷。

 

注意:為了儘可能不讓大家看到忒長的程式碼,我只是從我的程式程式碼中抽取了有用的部分,由於不是編譯過的,所以難免會發生失誤,語法錯誤或者保留了我自己的程式上面的一部分,使得有些地方看不明白什麼用,如果發生了那也請大家原諒。


相關文章