windows消息钩取


windows是一个消息驱动式系统。windows消息提供在应用程序与应用程序之间,应用程序与windows系统之间通信的手段。应用程序想要实现的功能由消息触发,通过对消息的响应和处理完成。

windows消息机制

windows是一个消息驱动式系统。windows消息提供在应用程序与应用程序之间,应用程序与windows系统之间通信的手段。应用程序想要实现的功能由消息触发,通过对消息的响应和处理完成。

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

进程可以看做程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都有独立的地址空间。一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。同一进程内的多个线程可以共享部分状态,多个线程可以读写同一块内存,进程无法访问其他进程的内存。

线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。是进程的一条执行路径。

线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.。

  • 进程是一个容器,线程在进程这个容器中执行,是实际执行的代码。一 个进程至少拥有一个线程,而这个线程通常被称之为主线程,主线程的入口点也是应用程序的起始点。线程除了必不 可少的资源(程序计数器 一组寄存器和栈)之外,不拥有系统资源,所有进程内 的线程共享分配给这个进程拥有的所有资源。
事件

在操作系统中借助键盘,鼠标,选择菜单,按钮,以及移动鼠标,改动窗口大小和位置都是事件。

句柄

句柄在程序设计中是一种特殊的指针,当一个程序需要引用其他系统时,就要使用句柄。

句柄是windows编程的基础。一个句柄是指使用一个唯一的整数值(4字节,64位 程序中是8字节),来标识应用程序中的不同对象和同类中的不同实例。

钩子

  • 钩子(Hook)是windows消息处理机制的一个平台,应用程序可以在上面设置子进程以监视指定窗口的某种消息,监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它,钩子机制允许应用程序截获处理windows消息或特定事件。
  • 钩子是一个处理消息的程序段,通过系统调用,把他们挂入系统。当特定的消息发出,在没有达到目的窗口前,钩子程序就先捕获该消息,钩子函数先得到控制权,就可以加工处理或改变消息,也可以不处理继续传递消息,也可强制结束消息。

常规windows消息流

1.发生键盘输入时,WM_KEYDOMN消息被添加到[OS message queue]

2.OS判断那个应用发生了事件,然后从[OS message queue]取出消息,添加到相应的[application message queue]中。

3.应用程序监视自身的[application message queue],发现新添加的WM_KEYDOMN消息后,调用相应的事件处理程序处理。

消息钩取工作原理

windows操作系统向用户提供GUI( 图形用户界面),以事件驱动的方式工作。
当事件发生时,OS会把事先定义好的消息发送给响应的应用程序,应用程序收到消息后执行相应的动作。

OS消息队列与应用程序消息队列之间存在一条钩链(HookChain)处于钩链的钩子会比应用程序先看到相应的信息。在键盘钩子函数的内部,除了可以查看消息外,还可以修改消息本身,对消息拦截或阻止消息传递。可以同时设置多个键盘钩子,按照设置顺序依次调用这些钩子

windows消息钩取的实现——SetWindowsHookEx()

使用SetWindowsHookEx()API可以轻松实现消息钩子

SetWindowsHookEx()API定义
HHOOK SetWindowsHookEx(
    int idHook,			//hook type
    HOOKPROC lpfn,		//hook procedure
    HINSTANCE hMod,		//hook procedure所属的DLL句柄
    DWORD dwThreadId	//将要挂钩的目标线程ID
);

idHook: 指示将要安装的挂钩处理过程的类型

Lpfn: 消息的回调函数地址

hMod: 钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL。对于系统钩子,该参数为钩子函数所在的DLL句柄

dwThreadId: 钩子所监视的线程的线程号,可通过GetCurrentThreadId()获得线程号。若dwThreadId参数设置为0,安装的钩子时全局钩子(Global Hook)

钩子过程是由操作系统调用的回调函数。安装钩子时,钩子过程需要存在某个DLL的内部,这个DLL的实例句柄是hMod

使用SetWindowsHookEx()设置好钩子后,在某个进程中生成指定消息时,OS会将相关的DLL文件强制注入相应的进程,然后调用注册的钩子过程。

键盘消息钩子

Keyhook.cpp

#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 =key press, 1 =key 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()函数,消息将传递给下一个程序
//_stricmp()函数用于比较字符串,i表示不区分大小写,若两个值相等则返回0
	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

安装好键盘钩子,无论什么进程,只要键盘输入事件,OS就会强行将KeyHook.dll注入进程,加载了KeyHook.dll的进程中,发生键盘事件时会首先调用执行KeyHook.KeyboardProc()

KeyHook.KeyboardProc()函数中发生键盘输入事件时,会比较进程的名字是否与notepad.exe相同,相同返回1,终止KeyHook.KeyboardProc()函数,截获且删除信息,键盘消息就不会传递到notepad.exe程序的消息队列中。

HookMain.cpp

调用导出函数HookStrat()时,SetWindowsHookEx()函数会将KeybroadProc()添加到键盘钩链

KeybroadProc()函数定义

LRESULT CALLBACK KeyboardProc(
    int code,           //HC_ACTION(0), HC_NOREMOVE(3)
    WPARAM wParam,      //virtual-key code
    LPARAM lParam       //extra information
);
//wParam是按下键盘按键的虚拟键值
//使用ToAscii()API函数可以获得实际按下键盘的ASCII值

先加载KeyHook.DLL文件,然后调用HookStart()函数钩取输入q时,调用HookStop()函数终止钩取。

#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);
}

运行HookMain.exe可安装键盘钩子

输入q可停止键盘钩取

调试HookMain.exe

通过字符串检索找到main函数

程序在00C2140E地址处调用LoadLibraryA(KeyHook.dll)

调用了SetWindowsHookExW()函数时,先有两条PUSH指令将该函数的第一、第二两个参数压入栈。其中第一个参数idHook值为2,即WH_KEYBOARD,第二个参数lpfn值为为钩子过程的地址。

调试notepad.exe进程中的KeyHook.dll

运行notpead.exe后弹出Executable modules窗口,KeyHook.dll已被加载

操作过程


文章作者: 大茗茗のblog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 大茗茗のblog !
  目录