1、引言
上世紀90年紀使用過windows3.x的人可能很少有人了解這類作業系統中存在著密碼保護的漏洞,如果選擇密碼控件中的“****”文本然後複製到剪貼板上,那麼看到的將不是“****”而是密碼的原始文本。微軟髮現了windows3.x這個問題並在新的版本window95中修改了這個漏洞。但是windows95存在著新的安全漏洞,可以設計出間諜程式從當前運行的程式中得到密碼控件中的密碼,這些間諜程式並非是如同softice一樣的破解程式。然而,微軟在window2000中又修補了這個問題,如何通過MMF與HOOK技術獲取任何版本windows 密碼控件的內容,這正是本文討論的重點問題。

圖1 Windows 2K/XP密碼校驗
獲取Windows密碼技術主要是利用了windows的安全漏洞。在Windows NT/95/98/ME等作業系統下,如果在間諜程式中發送WM_GETTEXT消息到密碼控件,返回的文本將不再是“****”而是實際的文本內容,而在windows2K/XP系統中微軟加了安全控制,如果發送WM_GETTEXT到密碼控件,系統將校驗請求的進程判斷該進程是否有許可權,如圖1所示:如果請求進程與密碼控件所在進程是同一進程,那麼WM_GETTEXT消息將仍舊返回密碼的真實文本。如果兩個進程不一樣,就返回一個ERROR_Access_DENIED的錯誤。所以獲取windows2K/XP密碼的關鍵技術在於:從密碼控件所在的進程中獲取WM_GETTEXT消息,而不是在滲透進程中得到。而這種在其他進程中運行用戶代碼的技術完全可以利用windows 鉤子(hook)技術來實現。首先我們需要了解一下什麼是鉤子。
2、Windows鉤子
Windows系統是建立在事件驅動的機制上的,即整個系統都是通過消息的傳遞來實現的。鉤子(hook)是一種特殊的消息處理機制,鉤子可以監視系統或進程中的各種事件消息,截獲發往目標窗口的消息並進行處理。這樣,我們就可以在系統中安裝自定義的鉤子,監視系統中特定事件的發生,完成特定的功能,比如截獲鍵盤、滑鼠的輸入,螢幕取詞,日誌監視等等。鉤子的種類很多,每種鉤子可以截獲並處理相應的消息,如鍵盤鉤子可以截獲鍵盤消息,外殼鉤子可以截取、啟動和關閉應用程式的消息等。如圖2是一全局鉤子示意圖。
在實例程式中運用WH_GETMESSAGE鉤子,這個鉤子監視投遞到消息隊列中的Windows消息。

圖2 全局鉤子的原理圖
3、Windows鉤子在此處的應用
安裝鉤子的函數為SetWindowsHookEx,利用這個函數可以為整個系統或為某一特定進程安裝鉤子,不同的鉤子監視特定鉤子事件的發生,當某一事件觸發後,與之對應的代碼就會被系統調用。
運用windows鉤子的一個難點是如何妥善保存鉤子的句柄。在設置鉤子前需要解決兩件事:
1) 一個包括了鉤子函數的動態鏈結庫;
2) 要注入鉤子的進程ID。
現在假設進程A為進程B注入了一個鉤子。鉤子注入後,鉤子的句柄返回給了進程A並且動態鏈結庫映射到了進程B的地址空間。當進程B中觸發了一個鉤子事件,鉤子代碼被進程B調用(需要特別指出的是,鉤子代碼被一個遠程進程所調用,被調用的鉤子代碼中如果調用GetCurrentProcessId這個函數,得到的是被注入了鉤子進程的進程ID,而不是注入進程)。在鉤子代碼中通過發消息獲取密碼真實文本,在鉤子代碼結束前需調用CallNextHookEx函數,如果這個函數調用失敗,安裝的其他鉤子將得不到消息。現在出現的問題是CallNextHookEx需要一個鉤子的句柄,但那個所需的句柄已返回給了進程A而鉤子程式目前運行在進程B的代碼段內。這樣就需要用到進程間通信IPC(Inter Process Communication)來傳遞鉤子句柄。
4、進程間通信的一般方法
解決以上問題的一般方法是在動態鏈結庫中創建一個“共用”部分,寫入如下代碼:
#pragma data_seg(“Shared”) HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, “/section:Shared,rws”) |
簡單地說這幾行代碼創建了一個共用的變數,如果5個進程載入這個動態鏈結庫,5個進程都有訪問的許可權。但這個方法有一些問題:一是一些編譯器可能並不支援這種方法。二是如果未來的windows版本發生了改變,這種方法就行不通。三是這個方法沒有規定線程同步,如果有多線程訪問這個變數,線程同步是非常重要的,沒有線程間的同步可能會觸發諸如衝突等一些問題。解決這個問題的方法是利用記憶體映像文件(MMF)。
5、記憶體映像文件(MMF)
在WIN32中,通過使用映像文件在進程間實現共用文件或記憶體共用,如果利用相同的映像名字或文件句柄,則不同的進程可以通過一個指針來讀寫同一個文件或者同一記憶體數據塊,並把他們當成該進程記憶體空間的一部分。記憶體映像文件可以映射一個文件、一個文件中的指定區域或者指定的記憶體塊,其中的數據就可以用記憶體讀取指令來直接訪問,而不用頻繁的使用操作文件的I/O系統函數,從而提高文件的存取速度和效率。
映像文件的另一個重要作用就是用來支援永久命名的共用記憶體。要在兩個應用程式之間共用記憶體,可以在一個應用程式中創建一個文件並映射,然後另外一個程式通過打開和映射此文件,並把它當作自己進程的記憶體來使用。
6、建立基於MMF的類CIPC
運用記憶體映像文件解決進程間通信問題,並且創建一個互斥變數來解決線程的同步問題。所有這些封裝在一個CIPC的類中。通過運用記憶體映像文件解決了不同編譯器的相容問題,因為使用的都是標準Win32 API。加上MMF支援進程間的數據共用,在未來的windows版本中微軟不會改變MMF的定義及方法。並且互斥變數保持了線程訪問的同步。以下給出CIPC的類定義。
class CIPC { public: CIPC(); virtual ~CIPC(); bool CreateIPCMMF(void);//創建一個進程間通信的MMF bool OpenIPCMMF(void);//打開一個進程間通信的MMF void CloseIPCMMF(void);//關閉一個進程間通信的MMF bool IsOpen(void) const {return (m_hFileMap != NULL);}//判斷MMF是否打開 bool ReadIPCMMF(LPBYTE pBuf, DWORD &dwBufSize);//讀MMF bool WriteIPCMMF(const LPBYTE pBuf, const DWORD dwBufSize);//寫MMF bool Lock(void);//進入臨界區,創建互斥信號量 void Unlock(void);//退出臨界區,撤消互斥信號量 protected: HANDLE m_hFileMap;//MMF文件句柄 HANDLE m_hMutex;//互斥變數句柄 }; |
7、利用WM_COPYDATA消息來解決進程間的通信
在解決進程間的通信問題方面,WM_COPYDATA消息是一個非常好的工具,可以節省程式員的許多時間。
當記憶體映像文件被建立時,系統就發送消息來填充它。然後系統再轉回到最初調用SendMessage的進程,從共用記憶體映像文件中將數據複製到所指定的緩衝區中,然後從SendMessage調用返回。
對於系統已經知道的消息,發送消息時都可以按相應的方式來處理。如果要建立自己的(WM_USER+x)消息,並從一個進程向另一個進程的窗口發送,那又會怎麼樣?系統並不知道用戶要用記憶體映像文件並在發送消息時改變指針。為此,微軟建立了一個特殊的窗口消息, WM_COPYDATA以解決這個問題:
COPYDATASTRUCT cds; SendMessage(hwndReceiver,WM_COPYDATA,(WPARAM)hwndSender,(LPARAM)&cds); COPYDATASTRUCT是一個結構,定義在winuser.h文件中,形式如下面的樣子: Typedef struct tagCOPYDATASTRUCT{ ULONG_PTR dwData; DWORD cbData; PVOID lpData; }COPYDATASTRUCT; |
當一個進程要向另一個進程的窗口發送一些數據時,必須先初始化COPYDATASTRUCT結構。數據成員dwData是一個備用的數據項,可以存放任何值。例如,用戶有可能向另外的進程發送不同類型或不同類別的數據。可以用這個數據來指出要發送數據的內容。cbData數據成員規定了向另外的進程發送的字節數,lpData數據成員指向要發送的第一個字節。lpData所指向的地址,當然在發送進程的地址空間中。
當SendMessage看到要發送一個WM_COPYDATA消息時,它建立一個記憶體映像文件,大小是cbData字節,並從發送進程的地址空間中向這個記憶體映像文件中複製數據。然後再向目的窗口發送消息。在接收消息的窗口過程處理這個消息時,lParam參數指向已在接收進程地址空間的一個COPYDATASTRUCT結構。這個結構的lpData成員指向接收進程地址空間中的共用記憶體映像文件的視圖。
8、關於WM_COPYDATA消息,應該注意三個重要問題
1)只能發送這個消息,不能登記這個消息。不能登記一個WM_COPYDATA消息,因為在接收消息的窗口過程處理完消息之後,系統必須釋放記憶體映像文件。如果登記這個消息,系統不知道這個消息何時被處理,所以也不能釋放複製的記憶體塊。
2)系統從另外的進程的地址空間中複製數據要花費一些時間。所以不應該讓發送程式中運行的其他線程修改這個記憶體塊,直到SendMessage調用返回。
3)利用WM_COPYDATA消息,可以實現1 6位和3 2位之間的通信。它也能實現3 2位與6 4位之間的通信。這是使新程式同舊程式交流的便捷方法。
9、重要代碼及注解
9.1鉤子函數void ExtractPassword(const HWND hWnd, const HWND hPwdSpyWnd),該函數是獲取密碼的主要函數
TCHAR szBuffer[256] = {_T('\0')};//分配一個緩衝區 SendMessage(hWnd,WM_GETTEXT,//向注入鉤子進程發消息獲得密碼文本 sizeof(szBuffer)/sizeof(TCHAR), (LPARAM)szBuffer);//保存在緩衝區中 COPYDATASTRUCT cds = {0};//定義一個cds結構體 cds.dwData = (DWORD)hWnd;//dwData保存該進程句柄 cds.cbData = (lstrlen(szBuffer) + 1) * sizeof (TCHAR); //cbData保存數據長度 cds.lpData = szBuffer;//lpData指向緩衝首地址 SendMessage(hPwdSpyWnd,WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);//利用WM_COPY DATA消息給獲取密碼進程發送密碼 |
9.2鉤子過程LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam),該過程主要完成從記憶體映像文件中讀出保存的鉤子句柄。
if(g_hHook == NULL) { //從共用資源中讀數據,最終獲取鉤子句柄 DWORD dwData = 0, dwSize = sizeof (DWORD); g_obIPC.Lock();//g_obIPC為CIPC對象,進入線程的同步 g_obIPC.OpenIPCMMF();//打開MMF文件 g_obIPC.ReadIPCMMF((LPBYTE)&dwData, dwSize);//讀數據給dwData g_obIPC.Unlock();//取消線程同步,退出臨界區 g_hHook = (HHOOK)dwData;//將讀到的數據賦值給鉤子句柄,本文的關鍵所在 }
if(nCode >= 0)//忽略小于0的值 { HWND hWnd = NULL; //密碼控件所在的窗口句柄 HWND hPwdSpyWnd = NULL;//獲取密碼進程的窗口句柄 MSG *pMsg = (MSG*)lParam; if(pMsg->message == g_wmScanPassword)//是否我們登記的消息 { hWnd = (HWND)pMsg->wParam; hPwdSpyWnd = (HWND)pMsg->lParam; ExtractPassword(hWnd, hPwdSpyWnd); //通過發送消息得到密碼 } }
return CallNextHookEx(g_hHook, nCode, wParam, lParam);//返回下一鉤子過程
|
10、演示介面
如圖3所示:實例是MFC下基於對話方塊的工程。在window XP下,拖動圖片控件放大鏡來檢索密碼控件中的密碼。下面的文本框顯示一些相關的窗口資訊以及密碼文本。
11、反密碼滲透應對策略
通過以上介紹的原理、方法及實現我們了解了如何得到Windows系列密碼的方法,一個邏輯的問題是如何防止別人利用這樣的間諜程式複製我們的密碼?例如我們上網時如果被這樣的鉤子程式入侵,怎麼才能保護密碼的安全。首選的解決方法是欺騙間諜程式,在用戶程式中不要顯示真實的密碼,最好是在密碼控件中顯示一條虛假的密碼。這樣如果有人用以上的程式來獲取密碼,那他得到的是虛假的密碼而不是真實密碼。
當然還有其他的應對策略,一種可行的方法是攔截WM_GETTEXT。但用虛假密碼還有其他的好處,通過利用虛假密碼代替真密碼,那麼看到的密碼控件上***長度就不能判斷密碼到底有多長。如果一個程式在其密碼控件中顯示了“***”的文本,我們立即知道密碼只有三個字符的長度,密碼的安全性大大降低。但如果通過一些加密演算法將密碼控件的顯示變為一長串的“*”,這種方法常見於微軟的密碼保護策略。那麼密碼攻擊者將無從下手。

圖3 實例演示
12、小結
本文以評論windows系列的密碼特點為起點,主要針對2k/XP下的密碼滲透進行了分析。通過在進程內注入windows鉤子以及利用記憶體映像文件安全傳遞鉤子句柄等技術,找出一條獲取2K/XP中密碼的途徑。但不可否認這都是建立在利用windows安全漏洞的基礎之上的。為了彌補這些安全漏洞,本文在最後也提出一些設想供讀者參考。當然只是在技術上解決一些問題是不夠的,關鍵還是人的因素,加強保密觀念對於密碼的保護起到了至關重要的作用。
文章定位: