zoukankan      html  css  js  c++  java
  • 【windows核心编程】系统消息与自定义钩子(Hook)使用

    一、HOOk

    Hook是程序设计中最为灵活多变的技巧之一,在windows下,Hook有两种含义:
    1、系统提供的消息Hook机制
    2、自定义的Hook编程技巧
    其中,由系统提供的消息钩子机制是由一系列的API提供的一种服务,这个系统的API可以完成对大多数应用程序关键节点的Hook操作,为此,windows为每种Hook类型维护了一个钩子链表,我们可以通过一个系统API来完成对整个系统所有符合此机制的关键点的Hook。

    另一种自定义的Hook编程技巧则是基于特定系统结构、文件结构、汇编语言的一种高级技术,运用自如后犹如手握屠龙刀倚天剑。

    二、系统消息钩子的使用

    windows操作系统是以事件驱动的。事件被包装成了消息发送给窗口,比如点击菜单,按钮,移动窗口,按下键盘,正常消息:

     当按下键盘,产生一个消息,按键消息加入到系统消息队列
     
     操作系统从消息队列中取出消息,添加到相应的程序的消息队列中
     
     应用程序使用消息磊从自身的消息队列中取出消息WM_KEYDOWN,调用消息处理函数。
     
     我们可以在系统消息队列之间添加消息钩子,从而使得在系统消息队列消息发给应用程序之前捕获到消息。
     
     可以多次添加钩子,从而形成一个钩子链,可以依次调用函数。
     
     消息钩子是windows操作系统提供的机制,SPY++截获窗口消息的功能就是基于这样的机制。
    

    SetWindowsHookExW

    设置钩子

    WINUSERAPI
    HHOOK
    WINAPI
    SetWindowsHookEx(
         //钩子类型
        _In_ int idHook,
        
        //回调函数地址
        _In_ HOOKPROC lpfn,
        
        //实例句柄(包含有钩子函数)
        _In_opt_ HINSTANCE hmod,
        
        //线程ID,欲勾住的线程(为0则不指定,全局)
        _In_ DWORD dwThreadId);
        
    

    能够设置的钩子类型

    宏值 含义
    WH_MSGFILTER 截获用户与控件交互的消息
    WH_KEYBOARD 截获键盘消息
    WH_GETMESSAGE 截获从消息队列送出的消息
    WH_CBT 截获系统基本消息,激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件
    WH_MOUSE 截获鼠标消息
    WH_CALLWNDPROCRET 截获目标窗口处理完毕的消息

    CallNextHookEx

    为钩子链中的下一个子程序设置钩子。在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个 SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。

    
    WINUSERAPI
    LRESULT
    WINAPI
    CallNextHookEx(
        //钩子句柄,由SetWindowsHookEx()函数返回。
        _In_opt_ HHOOK hhk,
        
        //钩子事件代码,回调函数的钩子过程的事件代码
        _In_ int nCode,
        
        //传给钩子子程序的wParam值
        _In_ WPARAM wParam,
        
        //传给钩子子程序的lParam值
        _In_ LPARAM lParam);
    
    

    UnhookWindowsHookEx

    卸载钩子API,钩子在使用完之后需要用UnhookWindowsHookEx()卸载,否则会造成麻烦。

    
    WINUSERAPI
    BOOL
    WINAPI
    UnhookWindowsHookEx(
         //要删除的钩子的句柄。这个参数是上一个函数SetWindowsHookEx的返回值.
        _In_ HHOOK hhk);
    
    

    返回值

    类型: BOOL

    如果函数成功,返回值为非零值。

    如果函数失败,返回值为零。

    要获得更多的错误信息,调用GetLastError函数.

    GetKeyboardState

    256个虚拟键的状态复制到指定的缓冲区。

    
    WINUSERAPI
    _Check_return_
    BOOL
    WINAPI
    GetKeyboardState(
         //指向一个256字节的数组,数组用于接收每个虚拟键的状态。
        _Out_writes_(256) PBYTE lpKeyState);
    
    

    返回值

    若函数调用成功,则返回非0值。若函数调用不成功,则返回值为0。若要获得更多的错误信息,可以调用GetLastError函数。

    操作方法

    将设置消息钩子的函数写在一个DLL中,当钩住一个GUI线程后,产生消息时,假如系统发现包含钩子函数的DLL不在本进程中,就会将此DLL强行的加载到对方的进程中。

    1、新建一个DLL项目,DllMain函数中将模块地址保存在一个模块变量中

    dllmain.cpp内容

     #include "stdafx.h"
    
    HMODULE g_Module = 0;
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    		g_Module = hModule;
    		break;
    	case DLL_THREAD_ATTACH:
    	case DLL_THREAD_DETACH:
    	case DLL_PROCESS_DETACH:
    		break;
    	}
    	return TRUE;
    }
    
    
    

    新建MessageHook.h头文件,声明C语法的函数名

    MessageHook.h 内容

    #pragma once
    
    extern"C" _declspec(dllexport) bool OnHook();
    extern"C" _declspec(dllexport) bool UnHook();
    
    

    新建MessageHook.cpp文件,实现OnHook()与UnHook()函数的用法

    #include "stdafx.h"
    #include "MessageHook.h"
    #include "wchar.h"
    #include "stdlib.h"
    extern HMODULE g_Module;
    HHOOK g_Hook = 0;
    
    //钩子回调函数
    LRESULT CALLBACK KeyboardProc(
    	_In_  int code,
    	_In_  WPARAM wParam,
    	_In_  LPARAM lParam
    	)
    {
    	// 判断是否wParam与lParam都有键盘消息,是的话则执行打印操作
    	if (code == HC_ACTION){
    		// 将256个虚拟键的状态拷贝到指定的缓冲区中,如果成功则继续
    		BYTE KeyState[256] = { 0 };
    
    
    		
            //虚拟键盘码存储
    		if (GetKeyboardState(KeyState)) {
    			// 得到第16–23位,键盘虚拟码
    			LONG  KeyInfo = lParam;
    			UINT  keyCode = (KeyInfo >> 16) & 0x00ff;
    			WCHAR wKeyCode = 0;
    			ToAscii((UINT)wParam, keyCode, KeyState, (LPWORD)&wKeyCode, 0);
    			// 将其打印出来
    			WCHAR szInfo[512] = { 0 };
    			swprintf_s(szInfo, _countof(szInfo), L"Hook--键盘记录-->%c", (char)wKeyCode);
    			//将内容输出到debug信息中
    			OutputDebugString(szInfo);
    			return 0;
    		}
    	}
    	return CallNextHookEx(g_Hook, code, wParam, lParam);
    }
    
    bool OnHook()
    {
    	if (g_Hook == 0)
    	{
    		g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Module, 0);
    		return true;
    	}
    	return false;
    }
    
    bool UnHook()
    {
    	if (g_Hook!=0)
    	{
    		return UnhookWindowsHookEx(g_Hook);
    	}
    	return false;
    }
    

    2、新建VS控制台项目

    调用前一个DLL项目的头文件MessageHook.h与生成后的lib文件MessageHook.lib

    mian.cpp内容

    #include "stdafx.h"
    #include"..MessageHookMessageHook.h"
    #pragma comment(lib,"../Debug/MessageHook.lib")
    int _tmain(int argc, _TCHAR* argv[])
    {
    	OnHook();
    	printf("按任意键停止");
    	getchar();
    	UnHook();
    	return 0;
    }
    

    演示图:

    三、自定义钩子的使用

    钩子的主要含义其实是改变程序原有的执行流程,让程序执行我们自己的代码。我们可以通过修改程序代码的方式来实现这一点。 还有一种情况是要调用的函数存储在某一个地方,需要调用这个函数的时候,去相应的位置找到函数地址。

    假如们能够提前修改掉某些位置存储的函数地址,将其改为我们自己的函数u,那么当调用目标函数的时候,就会调用我们自己的函数。

    而代码修改跳转地址需要代入一个公式:

    • JMP 指令地址换算公式
    • 地址偏移 = 目标地址 - JMP所在地址 -5

    操作方法

    DLL主要是为了截获exe里的所有调用MessageBox API的按钮,HOOK后调用的是我们自己的自定义函数

    dllmain.cpp内容

    // dllmain.cpp : 定义 DLL 应用程序的入口点。
    #include "stdafx.h"
    
    //关闭HOOK函数
    void OffHook();
    
    //HOOK函数
    void OnHook();
    
    char NewCode[5] = {};
    
    char OldCode[5] = {};
    
    //劫持MessageBox,替换的自定义函数
    int WINAPI MyMessageBoxW(
    	_In_opt_ HWND hWnd,
    	_In_opt_ LPCWSTR lpText,
    	_In_opt_ LPCWSTR lpCaption,
    	_In_ UINT uType)
    {
    
    	DWORD dwResault = 0;
    	lpText = L"你的按钮已经被Hook";
    	OffHook();
    	dwResault = MessageBox(hWnd, lpText, lpCaption, uType);
    	OnHook();
    	return dwResault;
    }
    
    
    void OnHook()
    {
    	DWORD dwOld = 0;
    	//修改一块虚拟内存的属性,设置为可写可执行
    	VirtualProtect(MessageBoxW, 1, PAGE_EXECUTE_READWRITE, &dwOld);
    	memcpy(MessageBoxW, NewCode, 5);
    	VirtualProtect(MessageBoxW, 1, dwOld, &dwOld);
    }
    
    
    void OffHook()
    {
    	DWORD dwOld = 0;
    	VirtualProtect(MessageBoxW, 1, PAGE_EXECUTE_READWRITE, &dwOld);
    	memcpy(MessageBoxW, OldCode, 5);
    	VirtualProtect(MessageBoxW, 1, dwOld, &dwOld);
    }
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
    					 )
    {
    	switch (ul_reason_for_call)
    	{
    	case DLL_PROCESS_ATTACH:
    	{
    		//准备基本工作
    		NewCode[0] = 0xE9; //实际上0xe9就相当于jmp指令  
    		//地址偏移 = 目标地址-JMP所在地址-5
    		DWORD dwOffset = (DWORD)MyMessageBoxW - (DWORD)MessageBoxW - 5;
    
    		//*(DWORD*)(NewCode + 1) = dwOffset;
    		memcpy(NewCode + 1, &dwOffset, 4);
    		memcpy(OldCode, MessageBoxW, 5);
    		//
    		OnHook();
    	}
    	break;
    	case DLL_THREAD_ATTACH:
    		break;
    	case DLL_THREAD_DETACH:
    		break;
    	case DLL_PROCESS_DETACH:
    		//OffHook();
    		break;
    	}
    	return TRUE;
    }
    
    

    演示图:

    使用15PB的tSourceCounter做MessageBox hook测试

    参考:

    HOOK学习笔记与心得

    http://bbs.pediy.com/thread-193729.htm

  • 相关阅读:
    3名百度 ,京东,腾讯被辞退的高级Android工程师现在怎么了?30岁真的是“罪”吗
    Python 开发者在迁移到 Go 前需要知道的事情
    centos7 常用命令--查看当前用户的4种方法
    Centos7找不到ifconfig和netstat命令
    Centos 7 修改日期和时间的命令
    如何利用MobaX同时处理多台虚拟机输入相同命令如何利用MobaX同时处理多台虚拟机输入相同命令
    配置坑了我好久:Logback按天生成文件失效
    quartz系列文章
    SpringBoot使用多实例QUARTZ出现重复执行问题
    IDEA多个springboot项目启动修改端口
  • 原文地址:https://www.cnblogs.com/17bdw/p/6533065.html
Copyright © 2011-2022 走看看