1. 程式人生 > >利用鍵盤鉤子捕獲Windows鍵盤動作

利用鍵盤鉤子捕獲Windows鍵盤動作

轉自:http://www.yesky.com/328/1890328.shtml

        引言

  在科研生產中對研製、除錯操作的記錄是非常有必要而且是有很重要價值的。通過對記錄資訊的分析,可以在事故發生後準確的分析出事故的起因、操作是否存在失誤等許多重要線索。通常需要記錄的資訊是多種多樣的,如環境溫度記錄、軟體執行記錄、檔案訪問記錄等等。這裡將以鍵盤資訊記錄為例來講述類似的實驗資訊自動記錄的一般實現方法。

  由於需要記錄當前系統下所有應用程式的鍵盤錄入記錄,因此必須採取某種特殊的技術來實現本程序(監視程式)對外部程序鍵盤操作資訊的獲取。這種技術便是本文將要論述的核心--系統全域性鉤子。本文下面將對Win32平臺下全域性鉤子的執行機制進行介紹並給出了一個具體的由VC++6.0編寫的捕獲鍵盤動作的鍵盤鉤子示例程式。

  系統鉤子和DLL



  鉤子的本質是一段用以處理系統訊息的程式,通過系統呼叫,將其掛入系統。鉤子的種類有很多,每種鉤子可以截獲並處理相應的訊息,每當特定的訊息發出,在到達目的視窗之前,鉤子程式先行截獲該訊息、得到對此訊息的控制權。此時在鉤子函式中就可以對截獲的訊息進行加工處理,甚至可以強制結束訊息的傳遞。

  在本程式中我們需要捕獲在任意視窗上的鍵盤輸入,這就需要採用全域性鉤子以便攔截整個系統的訊息,而全域性鉤子函式必須以DLL(動態連線庫)為載體進行封裝,VC6中有三種形式的MFC DLL可供選擇,即Regular statically linked to MFC DLL(標準靜態連結MFC DLL)、Regular using the shared MFC DLL(標準動態連結MFC DLL)以及Extension MFC DLL(擴充套件MFC DLL)。 在本程式中為方便起見採用了標準靜態連線MFC DLL。

  鍵盤鉤子程式示例


  本示例程式用到全域性鉤子函式,程式分兩部分:可執行程式KeyKook和動態連線庫LaunchDLL。首先建立一個MFC AppWizard(DLL)工程,並選擇Regular statically linked to MFC DLL(標準靜態連結MFC DLL)選項,以建立MFC擴充套件動態連線庫LaunchDLL.dll。之後,在相應的標頭檔案中新增巨集定義和待匯出函式的宣告:

#define DllExport __declspec(dllexport)
……
DllExport void WINAPI InstallLaunchEv();
……
class CLaunchDLLApp : public CWinApp
{
 public:
  CLaunchDLLApp();
  //{{AFX_VIRTUAL(CLaunchDLLApp)
  //}}AFX_VIRTUAL
  //{{AFX_MSG(CLaunchDLLApp)
  // NOTE - the ClassWizard will add and remove member functions here.
  // DO NOT EDIT what you see in these blocks of generated code !
  //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

  同時在實現檔案中新增全域性變數Hook和全域性函式LauncherHook()、SaveLog():

HHOOK Hook;
LRESULT CALLBACK LauncherHook(int nCode,WPARAM wParam,LPARAM lParam);
void SaveLog(char* c);

  最後,完成以上提到的這幾個函式的具體編碼實現:

CLaunchDLLApp theApp;
……
DllExport void WINAPI InstallLaunchEv()
{
 Hook=(HHOOK)SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)LauncherHook,theApp.m_hInstance,0);
}

  在此我們實現了Windows的系統鉤子的安裝,首先要呼叫SDK中的API函式SetWindowsHookEx()來安裝這個鉤子函式,其原型是:

HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);

  其中,第一個引數指定鉤子的型別,常用的有WH_MOUSE、WH_KEYBOARD、WH_GETMESSAGE等,在此我們只關心鍵盤操作所以設定為WH_KEYBOARD;第二個引數標識鉤子函式的入口地址,當鉤子鉤到任何訊息後便呼叫這個函式,即當不管系統的哪個視窗有鍵盤輸入馬上會引起LauncherHook的動作;第三個引數是鉤子函式所在模組的控制代碼,我們可以很簡單的設定其為本應用程式的例項控制代碼;最後一個引數是鉤子相關函式的ID用以指定想讓鉤子去鉤哪個執行緒,為0時則攔截整個系統的訊息,在本程式中鉤子需要為全域性鉤子,故設定為0。

LRESULT CALLBACK LauncherHook(int nCode,WPARAM wParam,LPARAM lParam)
{
 LRESULT Result=CallNextHookEx(Hook,nCode,wParam,lParam);
 if(nCode==HC_ACTION)
 {
  if(lParam & 0x80000000)
  {
   char c[1];
   c[0]=wParam;
   SaveLog(c);
  }
 }
 return Result;
}

  雖然呼叫CallNextHookEx()是可選的,但呼叫此函式的習慣是很值得推薦的;否則的話,其他安裝了鉤子的應用程式將不會接收到鉤子的通知而且還有可能產生不正確的結果,所以我們應儘量呼叫該函式除非絕對需要阻止其他程式獲取通知。

void SaveLog(char* c)
{
 CTime tm=CTime::GetCurrentTime();
 CString name;
 name.Format("c:\\Key_%d_%d.log",tm.GetMonth(),tm.GetDay());
 CFile file;
 if(!file.Open(name,CFile::modeReadWrite))
 {
  file.Open(name,CFile::modeCreate|CFile::modeReadWrite);
 }
 file.SeekToEnd();
 file.Write(c,1);
 file.Close();
}

  當有鍵彈起的時候就通過此函式將剛彈起的鍵儲存到記錄檔案中從而實現對鍵盤進行監控記錄的目的。編譯完成便可得到執行時所需的鍵盤鉤子的動態連線庫和進行靜態連結時用到的lib庫。

  下面開始編寫呼叫此動態連線庫的主程式,並實現最後的整合。另外建立一個單文件應用程式,把所需的動態連結庫標頭檔案、lib庫複製到工程目錄中,將動態連結庫複製到Debug目錄下。然後連結DLL庫:在"Project","Settings…"的"Link"屬性頁內,在"Object/librarymodules:"中填入"LaunchDLL.lib"。再通過"Project","Add To Project","Files…"將LaunchDLL.h新增到工程中來,最後在視類的原始檔KeyHook.cpp中加入對其的引用:

#include "LaunchDLL.h"

  這樣我們就可以象使用本工程內的 函式一樣使用動態連線庫LaunchDLL.dll中的所有匯出函數了。接下來在視類過載虛擬函式OnInitialUpdate(),並新增程式碼完成對鍵盤鉤子的安裝:

InstallLaunchEv();

  到此為止其實已經完成了所有的功能,但由於本程式是作為一個後臺監控軟體執行,因此還應當採取其他措施以隱藏其程式介面。這隻需在應用程式類CkeyHookApp的InitInstance()函式中將m_pMainWnd->ShowWindow(SW_SHOW)改為m_pMainWnd->ShowWindow(SW_HIDE)即可。

  小結


  編譯執行程式,執行起來之後並無什麼現象,但通過Alt+Ctrl+Del在關閉程式對話方塊內可以找到我們剛編寫完畢的程式"KeyHook",隨便在什麼程式中通過鍵盤輸入字元,然後開啟記錄檔案,我們會發現:通過鍵盤鉤子,我們剛才輸入的字元都被記錄到記錄檔案中了。系統鉤子具有相當強大的功能,通過這種技術可以對幾乎所有的Windows系統訊息進行攔截、監視、處理。這種技術廣泛應用於各種自動監控系統中。本文所述程式在Windows 2000 Professional + SP4下由Microsoft Visual C++ 6.0編譯除錯通過。