《windows核心编程系列》十八谈谈windows钩子

时间:2023-01-04 19:34:50

windows应用程序是基于消息驱动的。各种应用程序对各种消息作出响应从而实现各种功能。

windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功能。所谓的指定窗体并不局限于当前进程的窗体,也能够是其它进程的窗体。当监视的某一消息到达指定的窗体时,在指定的窗体处理消息之前,钩子函数将截获此消息,钩子函数既能够加工处理该消息,也能够不作不论什么处理继续传递该消息。使用钩子是实现dll注入的方法之中的一个。其它经常使用的方法有:注冊表注入,远程线程注入。

钩子函数是一个处理消息的程序段。是在安装钩子的时候向系统注冊的。

关于windows钩子要清楚下面三点:

1:钩子是用来截获系统中的消息流的。利用钩子能够处理不论什么我们感兴趣的消息,当然包含其它进程的消息。

2:截获该消息后,用于处理该消息的程序叫做钩子函数。它是自己定义的函数,在安装钩子时将此函数的地址告诉windows。

3:系统同一时间可能有多个进程安装钩子,多个钩子构成钩子链。所以截获消息并处理后,应该将此消息继续传递下去,以便其它钩子处理这一消息。

注意:使用钩子会使系统变慢,由于它添加了系统对每一个消息的处理量。所以要仅在必要的时候才安装钩子。不须要时要及时卸载。

  安装钩子:

1:

SetWindowsHookEx(

         int idHook,                  //要安装的钩子的类型。

         HOOKPROC lpfn,                  //钩子函数的地址。

         HINSTANCE hMode,               //钩子函数所在DLL在进程内的地址。

         DWORD     dwThread,            //要安装钩子的线程。如为0,则为全部线程安装钩子。

         );

idHook指定要安装钩子的类型,他能够是以下的值:

WH_CALLWNDPROC           //目标线程调用SendMessage发送消息时,钩子函数被调用。

WH_CALLWNDPROCRET   //当SendMessage返回时,钩子函数被调用。

WH_KEYBOARD                 //从消息队列中查询WM_KEYUP或WM_KEYDOWN时。

WH_GETMESSAGE           //目标线程调用GetMessage或PeekMessage时

WH_MOUSE                      //查询消息队列中鼠标事件消息时。

WH_MSGFILTER              //下面请參考MSDN。

WH_SYSMSGFILTER

WH_JORNALRECORD

WHJORNALPLAYBACK

WH_SHELL

WH_CBT

WH_FOREGROUNDIDLE

WH_DEBUG

2 :

lpfn是钩子函数的地址。钩子安装后假设有对应的消息发生,windows将调用此參数指向的函数。一般钩子函数都是位于一个DLL中。当为其它进程内的线程安装钩子时,假设钩子函数在DLL中,系统会把DLL映射到那个进程内,使他能在该进程内被调用。

注意:钩子函数多是被其它进程内的线程调用,而不一定是安装钩子的线程。

钩子函数被调用的过程

当进程A一个线程准备向一个窗体发送一个消息,系统检查该线程是否被安装了钩子,假设该线程被安装了钩子且该消息与钩子要截获的消息类型一致,此消息将被截获。系统检查该钩子的钩子函数所在的DLL是否已经被映射进程A的地址空间中。假设尚未映射,系统会强制将该DLL映射到进程A的地址空间。然后获得钩子函数在进程A的虚拟地址,并调用钩子函数。我们能够在钩子函数内定义我们对该消息处理的过程。

注意:当系统把钩子函数所在的DLL映射到某个进程地址空间时,会映射整个DLL,而不不过钩子函数,这也就说我们能够使用该DLL中的全部导出函数。

3:hmod參数是钩子函数所在dll的实例句柄,也是该dll在进程内的虚拟地址。假设钩子函数在当前进程中,此參数应被指定为NULL.

4:dwThreadid指定要被安装钩子的线程的ID号。假设被设为0,就会为系统内的全部GUI线程安装钩子。

5:钩子函数

钩子被安装后,假设有对应的消息发生,windows将调用钩子函数。下面为钩子函数的原型:

  LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)

   {

     //处理消息的代码。

      return CallNextHookEx(hHook,nCode,wParam,lParam);

   }

HookProc为钩子函数的名称。

nCode指定是否必须处理该消息。假设它为HC_ACTION,那么钩子函数就必须处理该消息。假设小于0,钩子函数就必须将该消息传递给CallNextHookEx,不正确该消息进行处理,并返回CallNextHookEx的返回值。

CallNextHookEx用于把消息传递到钩子链中下一个钩子函数。

wParam和lParam的值依赖于详细的钩子类型。请參考MSDN。

卸载钩子

BOOL UnhookWindowsHookEx(HHOOK hhk);

hhk为要卸载的钩子句柄。

以下将要实现一个样例,实现对键盘按键的监控。一旦有键盘被按下,就在主程序窗体显示一条信息指示哪一个键被按下。

程序外观:

《windows核心编程系列》十八谈谈windows钩子

首先要实现DLL:

在dll内实现钩子函数这是毫无疑问的。而安装钩子和卸载钩子的函数既能够写在主程序内,也能够写在DLL内。写在主程序内时仅仅能够在主程序内安装钩子。而在dll内实现则能够让全部加载该dll的程序安装钩子。如当某进程将该DLL加载的时候,能够在DllMain中创建一个线程,让他调用安装钩子的函数,实现为此进程内的线程安装钩子的目的。为了拓展程序的功能,实现代码重用,最好是将钩子函数写在DLL内。另外这也能够实现模块化。一旦需求发生更改能够仅仅改动DLL内的代码,而不须要改变主程序。

当钩子函数被调用的时候,也就是我们被拦截的消息已被触发,怎样让主程序得到这个通知呢 ?

我们能够在其它进程内的钩子函数内给主程序的窗体发送消息。但怎样发送呢?

PostMessage能够实现这个功能。

看原型:

    BOOL WINAPI PostMessage(HWND hWnd,UINT Msg,WPARAM wparam,LPARAM lParam);

hWnd即为要接受消息的窗体句柄。

Msg为要发送的消息。

wParam和lParam为消息的附加參数。

尽管能够使用PostMessage实现向主程序的窗体发送消息,可是我们怎样获得主程序的窗体句柄呢?我们知道钩子函数是在DLL内实现的,而DLL会被载入到各个进程内。在其它进程要想得到主程序的窗体句柄这是一个问题。

在《windows核心编程系列》谈谈内存映射文件里,我们谈到了在可运行文件内使用共享段,能够实现同一个可运行文件的多个实例共享共享段内的数据的目的。那么在DLL使用共享段呢?哈哈,也许你已经猜出来了,因为DLL被映射到了各个进程,将数据放在DLL的共享段,能够实如今各个进程内共享DLL内共享段数据的目的。

我们的解决方法就是:在DLL内建立共享段,将主程序的窗体句柄放在共享段中。在主程序调用安装钩子的函数时能够将共享段内的窗体句柄赋为主程序的窗体句柄。从而达到在各个进程内共享数据的目的。到此,我们又学习一种在进程间共享数据的方法,还有一种方法是利用内存映射文件。

建立和设置共享段的代码:能够參考《windows核心编程》谈谈内存映射文件。

 #pragma data_seg("shared")
HWND hWnd=NULL;
HHOOK hHook=NULL; #pragma data_seg() #pragma comment(linker,"/SECTION:shared,RWS")

怎么多了个hHook,hHook是创建的钩子的句柄。因为在钩子函数中会调用CallNextHookEx将消息传给钩子链的下一结点。二者都是在其它进程调用的,因此我们也必须把钩子的句柄设为共享。

DLL内创建钩子的代码:

    KEYHOOKDLL_API bool SetHook(
                            bool IsInstall,//true表示安装钩子,false表示卸载钩子。
                            HWND hWnd,     //主程序窗体句柄,用于在主程序内传入设置。
                              int ThreadId)//要安装钩子的线程。
{
::hWnd=hWnd;//将当前窗体句柄赋给DLL共线段内的窗体句柄。
if(IsInstall)
{
hHook=SetWindowsHookEx( WH_KEYBOARD,KeyHookProc,GetModuleHandle
                                           ("keyhookdll"),ThreadId);
return true; }
else
{
UnhookWindowsHookEx(hHook);
return true; } }

创建的钩子类型为WH_KEYBOARD,他能够拦截WM_KEYDOWN
WM_KEYUP
消息。详细请參考MSDN.

创建钩子函数功能非常easy,只安装钩子和设置共享段内的数据。Thread为要安装钩子的线程。主程序在调用时传入0,表示为全部线程安装钩子。

再看钩子函数:

LRESULT CALLBACK KeyHookProc(int nCode ,WPARAM wParam,LPARAM lParam)
{
if(nCode<0||nCode==HC_NOREMOVE)
{
return CallNextHookEx(hHook,nCode,wParam,lParam);
}
if(lParam&0x40000000)//仅仅对WM_DOWN进行响应。
  {
    PostMessage(hWnd,WM_KEYDOWN,wParam,lParam);
  }
        return CallNextHookEx(hHook,nCode,wParam,lParam);

}

在钩子函数中首先推断nCode的值,当他小于零时应该直调用CallNextHookEx,除此之外它也能够有下面取值:

ACTION:说明wParam和lParam包括按键消息的信息,能够处理。

HC_NOREMOVE:说明wParam和lParam包括按键消息的信息,但该消息没有被从消息队列中移除。即程序是调用PeekMessage来查询消息队列内的消息的。

( 与GetMessage的差别与联系:他们都从消息队列内查询消息,有消息时将此消息发送出去,GetMessage在消息队列没有消息时会一直等待,直到有消息到达时才返回。而PeekMessage不管消息队列中是否有消息都马上返回。)

因此当检測到nCode小于0或者为WH_NOREMOVE时不能对消息进行处理而要直接调用CallNextHookEx。lParam的第30位为1时说明此时键被按下,为零时说明键被弹起。此处进行了推断,仅在键被按下时向窗体发送消息。防止消息每次击键发送两次消息。

当某消息到达时我们给主程序窗体发送的消息为用户自己定义消息:WM_KEY

     他被定义为#define WM_KEY  WM_USER+1

在主程序内我们必须自己实现对应此消息的消息处理函数。

原型为:

    afx_msg LRESULT OnKey(WPARAM wParam,LPARAM lParam);

实现:

      char keyname[100];
::GetKeyNameText(lParam,keyname,100);//获得按键的键名。
CString a;
a.Format("用户按键:%s\r\n",keyname);
m_output+=a;
UpdateData(false);
::MessageBeep(MB_OK);
CEdit *edit=(CEdit*)GetDlgItem(IDC_EDIT_OUTPUT);
edit->LineScroll(edit->GetLineCount());
return 0;

到此为止各主要函数都介绍完成,剩下都是怎样创建dll。此处不再介绍。样例程序2011年12月2日下午实现。

总结:以上程序花了近三个小时实现,此程序看似easy但一旦自己动手实现各种问题接踵而至。所以以后要常常动手实现一些看似easy的程序,不要眼高手低。打这些字的时候键盘监控程序仍在工作,显示着我按下的每个键。有明显的电脑感觉速度比寻常慢了不少,看来使用钩子,尤其是系统范围内的钩子会导致非常大的overhead。



    windows核心编程中谈到注入dll的几种方式。当中介绍了使用windows钩子,可是介绍的非常easy。以上内容參考自《windows核心编程》第五版,第四部分和《windows程序设计》第二版,王艳平著。如有错误,请指正。

《windows核心编程系列》十八谈谈windows钩子的更多相关文章

  1. 《Windows核心编程系列》八谈谈用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  2. 《Windows核心编程系列》十一谈谈Windows线程池

    Windows线程池 上一篇博文我们介绍了IO完成端口.得知IO完成端口可以非常智能的分派线程.但是IO完成端口仅对等待它的线程进行分派,创建和销毁线程的工作仍然需要我们自己来做. 我们自己也可以创建 ...

  3. 《windows核心编程系列》一谈谈windows中的错误处理机制

    错误处理 我们写的函数会用返回值表示程序执行的正确与否,使用void,就意味着程序一定不会出错.Bool类型标识true时为真,false时为假.其他类型根据需要可以定义成不同意义. Windows除 ...

  4. 《windows核心编程系列》十七谈谈dll

    DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...

  5. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 &period;

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

  6. 《windows核心编程系列》七谈谈用户模式下的线程同步

    用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...

  7. 《windows核心编程系列 》六谈谈线程调度、优先级和关联性

    线程调度.优先级和关联性 每个线程都有一个CONTEXT结构,保存在线程内核对象中.大约每隔20ms windows就会查看所有当前存在的线程内核对象.并在可调度的线程内核对象中选择一个,将其保存在C ...

  8. 《Windows核心编程系列》十三谈谈在应用程序中使用虚拟内存

    在应用程序中使用虚拟内存 Windows提供了以下三种机制对内存进行操控: 一:虚拟内存.最适合来管理大型对象数据或大型结构数组. 二:内存映射文件.最适合用来管理大型数据流,以及在同一机 器上运行的 ...

  9. 《Windows核心编程系列》九谈谈同步设备IO与异步设备IO之同步设备IO

    同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...

  10. 《windows核心编程系列》四谈谈进程的建立和终止

    第二部分:工作机理 第一章:进程 上一章介绍了内核对象,这一节开始就要不断接触各种内核对象了.首先要给大家介绍的是进程内核对象.进程大家都不陌生,它是资源和分配的基本单位,而进程内核对象就是与进程相关 ...

随机推荐

  1. 去掉IE下input的叉号

    IE10下的Input Text和谷歌下面的 input search 一旦输入内容,会在最右端出现一个叉号,点击后,内容就会自动清空,看似方便,其实有些场景并不需要,需要写代码清除掉. 代码如下: ...

  2. JAVA中读取xls数据方法介绍

    用例编号(UI-0001) 用例名称({验证页面跳转|验证元素文本}-简要明确表述) 验证类型 是否执行 初始URL 初始元素xpath 目标元素xpath 目标元素属性 期望结果 UI-0001 验 ...

  3. java中重载、覆盖和隐藏三者的区别分析

    重载:方法名相同,但参数不同的多个同名函数 注意:1.参数不同的意思是参数类型.参数个数.参数顺序至少有一个不同 2.返回值和异常以及访问修饰符,不能作为重载的条件(因为对于匿名调用,会出现歧义,eg ...

  4. 鸟哥的Linux私房菜学习笔记&lpar;1&rpar;

    2014/10/29 1.档案的权限管理分为三个部分: 拥有者.群组.其他 2.ls -al 命令可以看到档案的详细信息 3.档案的属性中由十个部分构成 第一个部分是档案类型 -代表档案.d代表文件夹 ...

  5. 小米2S TWRP 3&period;0&period;2-0 最新中文版本Recovery

    注意:此版本为合并分区后的twrp  小米2S 合并分区教程:http://forum.xda-developers.com/mi-2/orig-development/flashtools-mifl ...

  6. winform中的数据绑定

    1. 简单的数据绑定 例1 using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings[& ...

  7. JetBrains 系列编译器 破解(idea,webstrom ,pycharm等)

    第一步: 下载安装好Idea 或者Pychar ,安装好后不可运行 第二步: 安装完成之后不要立即运行,以下提供JetbrainsCrack的jar包来破解: jar包资源自行百度 下载之后,把Jet ...

  8. Python数据库连接池DBUtils

    Python数据库连接池DBUtils   DBUtils是Python的一个用于实现数据库连接池的模块. 此连接池有两种连接模式: 模式一:为每个线程创建一个连接,线程即使调用了close方法,也不 ...

  9. 【LOJ】&num;6436&period; 「PKUSC2018」神仙的游戏

    题解 感觉智商为0啊QAQ 显然对于一个长度为\(len\)的border,每个点同余\(n - len\)的部分必然相等 那么我们求一个\(f[a]\)数组,如果存在\(s[x] = 0\)且\(s ...

  10. 近5年常考Java面试题及答案整理(一)

    下列面试题都是在网上收集的,本人抱着学习的态度找了下参考答案,有不足的地方还请指正. 1.面向对象的特征有哪些方面? 抽象:将同类对象的共同特征提取出来构造类. 继承:基于基类创建新类. 封装:将数据 ...