00 使用windows鈎子捕獲進程的啓動和關閉消息

Post date: 2012/3/27 上午 07:55:56

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

在一開頭,我們需要統一語言,本人使用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類的物件即可,當函數執行完畢自動撤銷。或者窗口被清除了也可以自動撤銷。

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