• C++实现wins后台监控键盘输入(Hook)


    Hook定义

    钩子(Hook)实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

    Hook分类

    钩子技术分为系统钩子技术和线程钩子技术

    • 系统钩子:是用于监视系统中的消息的钩子技术,因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 中。做好之后使用其他程序将钩子挂载到系统的进程中。
    • 线程钩子:指的是对指定线程进行监视。

    Hook处理流程

    • 阶段1:定义Hook。使用SetWindowsHookEx。
    • 阶段2:在Hook链表中传递Hook。使用CallNextHookEx。
    • 阶段3:卸载Hook。系统必须对每个消息处理, 钩子程序因此增加了处理的负担,因此也降低了系统的性能。UnhookWindowsHookEx可以将钩子句柄给删除。

    定义Hook

    HHOOK SetWindowsHookExA(
      [in] int       idHook,
      [in] HOOKPROC  lpfn,
      [in] HINSTANCE hmod,
      [in] DWORD     dwThreadId
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    idHook:钩子类型(-1~14)
    lpfn:指向钩子进程的指针。如果dwThreadId参数为零或指定了由其他进程创建的线程的标识符,那么lpfn参数必须指向DLL中的钩子进程。否则,lpfn可以指向与当前进程关联的代码中的钩子进程。
    hmod:lpfn形参指向的钩子子程所在DLL的句柄。 如果dwThreadId参数指定了一个由当前进程创建的线程,并且钩子子程在与当前进程相关的代码中,那么hMod参数必须设置为NULL。
    dwThreadId:钩子子程要关联的线程的标识符。对于桌面应用程序,如果该参数为0,则钩子子程与调用线程在同一桌面中运行的所有现有线程相关联。对于Windows Store应用程序,请参见备注部分。
    HHOOK:如果函数执行成功,返回值是钩子子程的句柄。
    如果函数失败,返回值是NULL。

    Hook类型

    在winuser.h中定义了

    #define WH_MSGFILTER (-1)
    #define WH_JOURNALRECORD 0 // 记录从系统消息队列中取出的各种事件消息
    #define WH_JOURNALPLAYBACK 1
    #define WH_KEYBOARD 2 // 键盘输入
    #define WH_GETMESSAGE 3
    #define WH_CALLWNDPROC 4 // 监视所有从系统消息队列发往目标窗口的消息
    #define WH_CBT 5
    #define WH_SYSMSGFILTER 6
    #define WH_MOUSE 7 // 鼠标输入
    #define WH_HARDWARE 8
    #define WH_DEBUG 9
    #define WH_SHELL 10 // 监视各种Shell事件消息。比如启动和关闭应用程序
    #define WH_FOREGROUNDIDLE 11
    #define WH_CALLWNDPROCRET 12 // 监视所有从系统消息队列发往目标窗口的消息
    
    #define WH_KEYBOARD_LL 13 // 底层键盘输入(不依赖本程序)
    #define WH_MOUSE_LL 14 // 底层鼠标输入(不依赖本程序)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    传递Hook

    LRESULT CallNextHookEx(
      [in, optional] HHOOK  hhk,
      [in]           int    nCode,
      [in]           WPARAM wParam,
      [in]           LPARAM lParam
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    hhk:忽略
    nCode:传递给当前钩子进程的钩子代码。下一个钩子进程使用此代码来确定如何处理该钩子信息。
    wParam:传递给当前钩子进程的wParam值。该参数的含义取决于与当前钩子链关联的钩子类型。
    lParam:传递给当前钩子进程的lParam值。该参数的含义取决于与当前钩子链关联的钩子类型。
    LRESULT:返回值,值由链表中的下一个钩子进程返回。当前钩子进程也必须返回该值。返回值的含义取决于钩子的类型。

    删除Hook

    BOOL UnhookWindowsHookEx(
      [in] HHOOK hhk
    );
    
    • 1
    • 2
    • 3

    hhk:前一个调用SetWindowsHookEx获得的钩子句柄。
    BOOL:删除成功与否

    后台监控键盘输入

    注:为了程序简洁,只监控键盘输入0和1。
    完整程序参考:
    https://blog.csdn.net/Simon798/article/details/98848050

    #include <stdio.h>
    #include <tchar.h>
    #include <windows.h>
    #include <iostream>
    #include <stdio.h>
    #include <conio.h>
    
    using namespace std;
    
    HHOOK keyboardHook = 0;		// 钩子句柄、
    
    LRESULT CALLBACK LowLevelKeyboardProc(
    	_In_ int nCode,		// 规定钩子如何处理消息,小于 0 则直接 CallNextHookEx
    	_In_ WPARAM wParam,	// 消息类型
    	_In_ LPARAM lParam	// 指向某个结构体的指针,这里是 KBDLLHOOKSTRUCT(低级键盘输入事件)
    	){
        KBDLLHOOKSTRUCT *ks = (KBDLLHOOKSTRUCT*)lParam;		// 包含低级键盘输入事件信息
    	/*
    	typedef struct tagKBDLLHOOKSTRUCT {
    		DWORD     vkCode;		// 按键代号
    		DWORD     scanCode;		// 硬件扫描代号,同 vkCode 也可以作为按键的代号。
    		DWORD     flags;		// 事件类型,一般按键按下为 0 抬起为 128。
    		DWORD     time;			// 消息时间戳
    		ULONG_PTR dwExtraInfo;	// 消息附加信息,一般为 0。
    	}KBDLLHOOKSTRUCT,*LPKBDLLHOOKSTRUCT,*PKBDLLHOOKSTRUCT;
    	*/
        if(ks->flags == 128 || ks->flags == 129)
        {
    		// 监控键盘
    		switch(ks->vkCode){
    		case 0x30: case 0x60:
    			cout << "检测到按键:" << "0" << endl;
    			break;
    		case 0x31: case 0x61:
    			cout << "检测到按键:" << "1" << endl;
    			break;
    		}
    		
            //return 1;		// 使按键失效
        }
    
    	// 将消息传递给钩子链中的下一个钩子
        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        SetConsoleOutputCP(65001);// 更改cmd编码为utf8
    	// 安装钩子
    	keyboardHook = SetWindowsHookEx(
    		WH_KEYBOARD_LL,			// 钩子类型,WH_KEYBOARD_LL 为键盘钩子
    		LowLevelKeyboardProc,	// 指向钩子函数的指针
    		GetModuleHandleA(NULL),	// Dll 句柄
    		NULL					
    		);
        if (keyboardHook == 0){cout << "挂钩键盘失败" << endl; return -1;}
    
        //不可漏掉消息处理,不然程序会卡死
        MSG msg;
        while(1)
        {
    		// 如果消息队列中有消息
            if (PeekMessageA(
    			&msg,		// MSG 接收这个消息
    			NULL,		// 检测消息的窗口句柄,NULL:检索当前线程所有窗口消息
    			NULL,		// 检查消息范围中第一个消息的值,NULL:检查所有消息(必须和下面的同时为NULL)
    			NULL,		// 检查消息范围中最后一个消息的值,NULL:检查所有消息(必须和上面的同时为NULL)
    			PM_REMOVE	// 处理消息的方式,PM_REMOVE:处理后将消息从队列中删除
    			)){
    				// 把按键消息传递给字符消息
    				TranslateMessage(&msg);
    
    				// 将消息分派给窗口程序
    				DispatchMessageW(&msg);
    		}
            else
                Sleep(0);    //避免CPU全负载运行
        }
    	// 删除钩子
        UnhookWindowsHookEx(keyboardHook);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    程序运行情况参考:(支持后台监控)
    在这里插入图片描述

  • 相关阅读:
    从零到使用vue工程
    部暑nginx digest auth
    WPF动画(2)
    Java多线程
    如何走出长期精神内耗的状态?
    玩转Vue3之shallowRef和shallowReactive
    性能测试常见的测试指标
    PHP代码审计--百家CMS4.1.4项目实战(上)
    从实际需求方案整理记录分布式锁的使用
    java计算机毕业设计疗养院管理源码+系统+mysql数据库+lw文档
  • 原文地址:https://blog.csdn.net/GDUT_xin/article/details/125418475