【windows核心编程】系统消息与自定义钩子(Hook)使用
2017-03-10 21:00
555 查看
一、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
相关文章推荐
- OCX中使用钩子HOOK消息
- Windows Mobile/Windows CE开发技巧 —— 使用钩子实现自定义系统功能键
- 使用回调函数发送自定义“消息”
- 使用回调函数发送自定义“消息”
- C#中使用Hook(钩子)
- 初次尝试使用自定义消息
- MFC中自由使用自定义消息
- 使用xmlhttp和Java session监听改善站内消息系统
- 在C#中使用自定义消息
- VC中如何使用自定义消息?
- [Delphi]钩子(HOOK)机制的使用
- 使用xmlhttp和Java session监听改善站内消息系统
- 使用xmlhttp和Java session监听改善站内消息系统
- MFC中使用自定义消息
- MFC中使用自定义消息
- 使用回调函数发送自定义“消息”
- 使用自定义消息
- 日志钩子(JournalRecord Hook)的使用
- 关于如何使用自定义的结束消息循环的方式 (转载)
- C#中使用消息钩子的方法