【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"..\MessageHook\MessageHook.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
【windows核心编程】系统消息与自定义钩子(Hook)使用的更多相关文章
- 《windows核心编程系列》十八谈谈windows钩子
windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...
- 《Windows核心编程系列》十四谈谈默认堆和自定义堆
堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...
- C++Windows核心编程读书笔记
转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...
- 【转】《windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- Windows核心编程随笔
最近在学习Windows底层原理,准备写个系列文章分享给大家,Michael Li(微软实习期间的Mentor,为人超好)在知乎回答过一些关于学习Windows原理的书籍推荐,大家可以拜读其中一本来入 ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...
- 《Windows核心编程》读书笔记 上
[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
- 《windows核心编程系列》十七谈谈dll
DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...
- 《windows核心编程系列》十六谈谈内存映射文件
内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...
随机推荐
- 【POI每日题解 #9】SKA-Piggy Banks
题目链接 题意: 有一棵环套树 求最少从多少个节点出发能沿边走过整棵树 环套树 并查集求联通块 有几块就砸几个 太简单不发代码了 不过某大佬的环套树找环dfs让我研究了好久… 贴一下以Orz #inc ...
- 自学Zabbix3.12-动作Action
点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix3.12-动作Action介绍 1. 动作action 在配置好监控项和触发器之后 ...
- luogu2149 Elaxia的路线 (dijkstra+拓扑dp)
先标记上一个人所有最短路上的边(同时也要标记反向边) 然后拿着另一个人最短路上的边(会构成一个DAG)去做拓扑dp,记从原点到某个点的最大的某个路径的被标记的边的个数 #include<bits ...
- BSGS&EXBSGS 大手拉小手,大步小步走
大步小步走算法处理这样的问题: A^x = B (mod C) 求满足条件的最小的x(可能无解) 其中,A/B/C都可以是很大的数(long long以内) 先分类考虑一下: 当(A,C)==1 即A ...
- (转)Java transient关键字使用小记
背景:最近在看java底层的源码实现,看到一个关键字,不是很熟悉,专门做个记录. 原文出处:http://www.importnew.com/21517.html#comment-637072 哎,虽 ...
- Java: |(或运算) 与 多选判断
今天需要在程序中做一个多选判断,突然想起以前经常遇到的 x |= y | z; 这样的,我也明白这个是多选的用意,但为什么能达到我们希望的多选操作,我还真的没去研究过. 今天早上,百度了一下,搜索到了 ...
- react与fetch
JavaScript 中的 ajax 很早之前就有一个诟病----复杂业务下的 callback 嵌套的问题.promise 正是 js 中解决这一问题的钥匙. 接下来我们在react项目中应用到的f ...
- 线程的加入.join()
格式:以加入A线程为例 线程对象B.join() 无参数,则A线程一直暂停,直到B线程运行结束. 线程对象B.join(时间t) 有参数,则A线程每隔t时间暂停一次,直到B线程运行结束. 关于 ...
- 让maven项目使用nexus作为远程仓库
让maven项目使用nexus作为远程仓库有两种方式,第一种是在项目的pom.xml中进行更改,让单个项目使用nexus仓库:另一种是通过修改maven的配置文件settings.xml进行更改,让所 ...
- 重新打开Eclipse出现“An internal error has occurred. java.lang.NullPointerException”
如果出现了上述的错误按照如下的3个步骤解决:1.首先关闭MyEclipse工作空间.2.然后删除工作空间下的. “/.metadata/.plugins/org.eclipse.core.runtim ...