使用Windows鉤子獲取丟失的密碼

字號:

簡介
     幾年前我在CodeGuru 上下載了一個叫 Eureka的程序,如果你忘記了密碼,你可以用程序把密碼“取”回來。它不是密碼*程序,相反,它利用了一個Windows的安全漏洞來拷另外一個運行中的程序的密碼。我對這個程序很感興趣,決定寫一個自己的版本。后來,Windows 2000 發(fā)布,我失望地發(fā)現(xiàn),微軟修補了那個漏洞,這樣一來那個程序在Windows 2000上也就不靈了。經(jīng)過一番嘗試,我終于找到一個方法拷貝任何在32-位Windows 系統(tǒng)上運行程序的密碼。
     使用方法:
     PasswordSpy程序的使用非常簡單。你只要運行包含忘記了密碼的程序,再運行PasswordSpy。然后將放大鏡拖動到密碼輸入域上,PasswordSpy則會將密碼顯示出來。PasswordSpy程序并沒有惡意,開發(fā)它的目的只是想把密碼找回來,該程序在Win95/98 /ME and WinNT/2K/XP/Windows 2003 上測試通過。
     功能說明:
     除了PasswordSpy本身的用途之外,它還示范了一些有用和有趣的代碼:
     單實例應(yīng)用——如果用戶啟動了PasswordSpy的第二個實例,系統(tǒng)會找到第一個實例,并在所有窗口的最前端顯示出PasswordSpy界面;Always on top——總是在最頂層,只用一行代碼就可以在你的程序中啟動和禁用這個功能;進程間通訊——PasswordSpy 使用幾種形式的IPC,包括WM_COPYDATA消息以及內(nèi)存映射文件;設(shè)置窗口鉤子——為了在Windows 2000/Windows XP中吸取密碼,你得使用在遠程進程中置入一個鉤子。
     代碼實現(xiàn)細節(jié):
     到目前為止,PasswordSpy 程序最有趣的部分其實是使用 SetWindowsHookEx API.函數(shù)設(shè)置Windows 鉤子。利用該函數(shù)你可以將鉤子安裝到操作系統(tǒng)中或者某個特定的進程中。鉤子的種類有很多種,每種鉤子作用也不盡相同,用來監(jiān)視特定的一組事件。當某一類事件發(fā)生時,鉤子代碼被調(diào)用。PasswordSpy使用WH_GETMESSAGE鉤子,它監(jiān)視對GetMessage 和PeekMessage 的調(diào)用。關(guān)于鉤子更詳細的信息請參考MSDN庫的SetWindowsHookEx。
     我在網(wǎng)上、書本以及MSDN上找到幾個有關(guān)鉤子的例子,每個都至少有一個Bug。本文我用自己的方案解決了這些問題。使用Windows鉤子最難的部分是妥善存儲鉤子句柄。設(shè)置鉤子之前,你需要做兩件事情:
     包含鉤子函數(shù)的DLL;
     鉤子要作用的線程ID;
     現(xiàn)在假設(shè)進程A要在進程B中設(shè)置鉤子。鉤子注入進程B以后,該鉤子句柄被返回到進程A,同時DLL被映射到進程B的地址空間。當進程B中某個鉤子事件發(fā)生時,你的鉤子代碼便在進程B中被調(diào)用。(應(yīng)該注意的一點是你的鉤子代碼是從遠程進程空被調(diào)用的!鉤子代碼里,如果調(diào)用 GetCurrentProcessId 函數(shù),那么獲得的是鉤子進程的ID,而不是設(shè)置鉤子的進程ID)。你可以在鉤子代碼中做任何想做的事情,但是在鉤子代碼退出之前,你應(yīng)該調(diào)用 CallNextHookEx。如果這個函數(shù)調(diào)用失敗,其它任何已經(jīng)安裝的鉤子無法獲得消息。因為CallNextHookEx需要該鉤子句柄,但我們當前是在進程B中,而句柄被返回到進程A,因此,此時需要進程間通訊來傳輸鉤子句柄。
     通常解決這個問題的方法是通過在DLL中創(chuàng)建一個“共享”內(nèi)存區(qū):
     #pragma data_seg("Shared")
     HHOOK g_hHook = NULL;
     #pragma data_seg()
     #pragma comment(linker, "/section:Shared,rws")
     其實,此處創(chuàng)建了一個變量,所有已經(jīng)被加載的DLL實例共享這個變量。但這個方法有幾個問題,第一,有些編譯器不支持這個選項,第二,如果微軟要是在未來的Windows中改變“共享”內(nèi)存區(qū)的工作模式該怎么辦?這意味著這個技術(shù)不再適用。此外,這個方法不是線程同步的,由于有多個線程要訪問這個變量,線程同步就變得很重要。為了解決這些問題,我在進程間通訊(IPC)過程中使用了內(nèi)存映射文件,并利用互斥機制進程線程同步。我將這方面的代碼全部封裝在一個類中,這個類就是 CIPC。通過使用內(nèi)存映射文件,我解決了特定編譯器選項問題,不用再考慮編譯器是否支持共享內(nèi)存區(qū);僅僅借助Win32 API調(diào)用以及用MMFs來提供多進程間共享數(shù)據(jù)的機制就可以解決問題,在未來的Windows版本中,這些東西是不太可能改變的?;コ獗WC了線程存取的同步: //*********************************************** // IPC.h
     //***********************************************
     #ifndef _IPC_H_
     #define _IPC_H_
     #define IPC_SHARED_MMF _T("{34F673E0-878F-11D5-B98A-00B0D07B8C7C}")
     #define IPC_MUTEX _T("{34F673E1-878F-11D5-B98A-00B0D07B8C7C}")
     //使用內(nèi)存映射文件進行進程間通訊的封裝類
     class CIPC
     {
     public:
     CIPC();
     virtual ~CIPC();
     bool CreateIPCMMF(void);
     bool OpenIPCMMF(void);
     void CloseIPCMMF(void);
     bool IsOpen(void) const {return (m_hFileMap != NULL);}
     bool ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize); bool WriteIPCMMF(const LPBYTE pBuf,
     const DWORD dwBufSize);