7- Windows訊息鉤取

有毒發表於2020-03-27

Windows訊息鉤取

一、鉤子和訊息鉤子

鉤子,英文Hook,泛指偷看或擷取資訊時所用的手段或工具。

 

Windows作業系統向使用者提供GUI,它是以事件驅動(Event Driven)方式工作。事件發生後,OS將事先定義好的訊息傳送給相應的應用程式,應用程式分析收到的訊息後執行相應動作。以敲擊鍵盤為例,

 

常規Windows訊息流:

  1. 發生鍵盤輸入事件,WM_KEYDOWN訊息被新增到OS訊息佇列;
  2. OS判斷哪個應用程式發生了事件,從OS訊息佇列中取出訊息,新增到相應應用程式的app訊息佇列;
  3. 應用程式監視自身的訊息佇列,發現新新增的WM_KEYDOWN訊息,呼叫相應的事件處理程式進行處理。

附帶鉤子的資訊流:

  1. 發生鍵盤輸入事件,WM_KEYDOWN訊息被新增到OS訊息佇列;
  2. OS判斷哪個應用程式發生了事件,從OS訊息佇列中取出訊息,傳送給應用程式;
  3. 鉤子程式擷取資訊,對訊息採取一定的動作(因鉤子目的而定);
  4. 如鉤子程式不攔截訊息,訊息最終傳輸給應用程式,此時的訊息可能經過了鉤子程式的修改。

二、SetWindowsHookEx()

這是一個實現訊息鉤子的API,其定義如下:

HHOOK SetWindowsHookEx(
    int idHook,                        // hook type
    HOOKpROC lpfn,                // hook procedure
    HINSTANCE hMod,                //hook procedure所屬的DLL控制程式碼
    DWORD dwThreadId            //需要掛鉤的執行緒ID,為0時表示為全域性鉤子(Global Hook)
);

hook proceduce是由作業系統呼叫的回撥函式;安裝訊息鉤子時,鉤子過程需要存在於某個DLL內部,且該DLL的示例控制程式碼即為hMod。

 

使用SetWindowsHookEx()設定好鉤子後,在某個程式中生成指定訊息時,OS就會將相關的DLL檔案強制注入(injection)相應程式,然後呼叫註冊的鉤子程式。

三、鍵盤訊息鉤取

以下以書上例子進行練習,首先過程原理圖如下:

 

21-1

 

KeyHook.dll檔案是一個含有鉤子過程(KeyboardProc)的DLL檔案,HookMain.exe是最先載入KeyHook.dll並安裝鍵盤鉤子的程式。HookMain.exe載入KeyHook.dll後使用SetWindowsHookEx()安裝鍵盤鉤子;若其他程式(如圖中所示)發生鍵盤輸入事件,OS就會強制將KeyHook.dll載入到像一個程式的記憶體,然後呼叫KeyboardProc()函式。

 

實驗:HookMain.exe

 

關於實驗操作部分建議跟隨書上走一遍流程,體驗Hook的魅力。

四、原始碼分析

1. HookMain.cpp

HookMain程式的主要原始碼如下所示:

#include "stdio.h"
#include "conio.h"
#include "windows.h"

#define    DEF_DLL_NAME        "KeyHook.dll"
#define    DEF_HOOKSTART        "HookStart"
#define    DEF_HOOKSTOP        "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
    HMODULE    hDll = NULL;
    PFN_HOOKSTART    HookStart = NULL;
    PFN_HOOKSTOP    HookStop = NULL;
    char    ch = 0;

  // 載入KeyHook.dll
    hDll = LoadLibraryA(DEF_DLL_NAME);
    if( hDll == NULL )
    {
        printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
        return;
    }

  // 獲取匯出函式地址
    HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
    HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

  // 開始鉤取
    HookStart();

  // 等待,直到使用者輸入“q”
    printf("press 'q' to quit!\n");
    while( _getch() != 'q' )    ;

  // 終止鉤子
    HookStop();

  // 解除安裝KeyHook.dll
    FreeLibrary(hDll);
}

2. KeyHook.dll

KeyHook.dll原始碼:

#include "stdio.h"
#include "windows.h"

#define DEF_PROCESS_NAME        "notepad.exe"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
    switch( dwReason )
    {
        case DLL_PROCESS_ATTACH:
            g_hInstance = hinstDLL;
            break;

        case DLL_PROCESS_DETACH:
            break;    
    }

    return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    char szPath[MAX_PATH] = {0,};
    char *p = NULL;

    if( nCode >= 0 )
    {
        // bit 31 : 0 => press, 1 => release
        if( !(lParam & 0x80000000) )    //釋放鍵盤按鍵時
        {
            GetModuleFileNameA(NULL, szPath, MAX_PATH);
            p = strrchr(szPath, '\\');

      //比較當前程式名稱是否為notepad.exe,成立則訊息不傳遞給應用程
            if( !_stricmp(p + 1, DEF_PROCESS_NAME) )
                return 1;
        }
    }

  //如果不是notepad.exe,則呼叫CallNextHookEx()函式,將訊息傳遞給應用程式
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C" {
#endif
    __declspec(dllexport) void HookStart()
    {
        g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
    }

    __declspec(dllexport) void HookStop()
    {
        if( g_hHook )
        {
            UnhookWindowsHookEx(g_hHook);
            g_hHook = NULL;
        }
    }
#ifdef __cplusplus
}
#endif

總體上程式碼相對簡單,呼叫匯出函式HookStart()時,SetWindowsHookEx()函式就會將KetyboardProc()新增到鍵盤鉤鏈。

3. 程式碼執行流程分析

安裝好鍵盤鉤子後,無論在哪個程式中,只要發生了鍵盤輸入事件,OS就會強制將KeyHook.dll注入到程式中,載入了KeyHook.dll的程式,發生鍵盤事件時會首先呼叫執行KeyHook.KetyboardProc()。

 

KetyboardProc()函式中發生鍵盤輸入事件時,會比較當前程式的名稱與“notepad.exe”是否相同,相同返回1,終止KetyboardProc()函式,意味著截獲並刪除了訊息,這樣鍵盤訊息就不會傳遞到notepad.exe程式的訊息佇列。

五、除錯

使用OD開啟HookMain.exe檔案:

 

21-2

 

###1. 查詢核心程式碼

 

我們關心的是核心的鍵盤鉤取部分的程式碼,如何查詢核心程式碼?

  1. 逐步跟蹤(除非迫不得已!)
  2. 檢索相關API
  3. 檢索相關字串

我們已經知道程式的功能,會在控制檯顯示字串“press ‘q’ to quit!”,所以先檢查程式匯入的字串(Search for -All referencen text strings):

 

21-3

 

地址40104d處引用了要查詢的字串,雙擊跳轉:

 

21-4

 

來到main函式處。

2. 除錯main函式

在401000處下斷,開始除錯,瞭解main函式中主要的程式碼流。401006地址處呼叫LoadLibraryA(Keyhook.dll),然後由40104b地址處的CALL EBX指令呼叫KeyHook.HookStart()函式。跟進檢視:

 

21-5

 

這裡的程式碼是被載入到HookMain.exe程式中的KeyHook.dll的HookStart()函式,第一句就是呼叫SetWindowsHookExW()函式,在進行引數入棧操作後,我們可以在棧中看到函式的4個引數值。

參考

《逆向工程核心原理》

相關文章