2024年2月24日发(作者:)

课程报告

windows钩子程序实现

班学姓级号名:

教师评语:

教师签名:

2010年7月

1

课程设计题目

目的和背景

windows钩子程序实现

目的:

1)更深入的学习C++,并学会在Visual C++ 6.0上编写应用程序

2)了解windows钩子程序的基本原理,类型和实现过程

3)掌握用C++来设计一个钩子程序

背景:钩子的本质是一段用以处理系统消息的程序,通过系统调用,把他挂入系统。钩子的种类很多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩子程序先行截获该消息、得到对此消息的控制权。此时钩子函数可以对截获的消息进行加工处理,甚至可以强制结束消息的传递。鼠标钩子是能截获鼠标的操作,包括单击,双击,鼠标所在的位置等;而键盘钩子是截获从键盘输入的信息。

1)熟悉钩子程序在Windows操作系统的作用

2)通过找资料,学习钩子程序的基本原理,包括Windows的消息传递机制,钩子的概念,钩子的类型,钩子的实现过程

3)学习和掌握钩子函数,Win32全局钩子的运行机制,VC6中MFC

DLL的分类及特点和在VC6中全局共享数据的实现

4)用C++编写一个windows钩子程序;实现适时获取当前鼠标所在窗口的标题和监视各种键盘消息,如,把把鼠标所在窗口标题显示在一个EDITBOX中,从键盘输入的信息记录在一个文档里

Windows XP 操作系统,Visual C++ 6.0

1)18周周(三)—18周周(四):查找相关的资料,对钩子程序的相关知识进行全面的了解

2)18周周(五)—19周周(一):对程序进行分析,并加强有关方面的知识,如,C++编程的能力

3)19周周(二)—19周周(三):学习了解Win32全局钩子的运行机制,VC6中MFC DLL的分类及特点和在VC6中全局共享数据的实现

4)19周周(四)—20周周(一):编码实现windows钩子程序,并实现相应的功能

5)20周周(二)—20周周(三):进行程序测试

[1] 王育坚.Visual C++面向对象编程教程(第2版)[M].北京:清华大学出版社,2007.10.

[2] 王西武,阎梅 ,赵怀勋. 在VC6下应用Windows系统钩子技术[J]. 现代电子技术 . 2004:27(17) .

[3] 徐士良.常用算法程序集:C++语言描述(第4版)[M].北京:清华大学出版社,2009.7.

[4] 钱能.C++程序设计教程:设计思想与实现(修订版)[M].北京:清华大学出版社.2009.7.

[5] 游洪跃, 伍良富, 王景熙.C++面向对象程序设计实验和课程设计教程[M].北京:清华大学出版社,2009.2.

[6]

倪步喜.Windows的钩子技术及实现[J].计算机与现代化.2007,28(1):28-30.

2

主要内容

采用的工具方法

进度安排

参考资料

1目的和背景

钩子的本质是一段用以处理系统消息的程序,通过系统调用,把他挂入系统。钩子的种类很多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩子程序先行截获该消息、得到对此消息的控制权。此时钩子函数可以对截获的消息进行加工处理,甚至可以强制结束消息的传递。鼠标钩子是能截获鼠标的操作,包括单击,双击,鼠标所在的位置等;而键盘钩子是截获从键盘输入的信息。

通过这个课程设计,目的是更深入的学习C++,并学会在Visual C++ 6.0上编写应用程序,了解windows钩子程序的基本原理,类型和实现过程,掌握用C++来设计一个钩子程序。

2设计想思

钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。由此,我们建立一个鼠标和键盘钩子,把它挂入系统,鼠标钩子是能截获鼠标所指的窗口的标题,而键盘钩子是截获从键盘输入的字符。因为要建立的是全局钩子,要Win32的运行机制,并且在Visual C++6.0中用Win32 DLL来构造动态链接库。

3函数与数据结构

(1)函数SetWindowsHookEx

要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个鼠标和键盘钩子,这个函数的原型是

HHOOKSetwindowsHookEx(int idHook,HOOKPROC Lpfn,INSTANCE hMod,DWORD

dwTreadId)

参数:

idHook:表示钩子类型,它是和钩子函数类型一一对应的。比如,WH_KEYBOAR,表示安装的是键盘钩子,WH_MOUSE表示是鼠标钩子等。

Lpfn:是钩子函数的地址。

HMod:是钩子函数所在的实例的句柄。对于线程钩子,该参数为NULL;对于系统钩子,该参数为钩子函数所在的DLL句柄。

dwThreadId :指定钩子所监视的线程的线程号。对于全局钩子,该参数为NULL。

SetWindowsHookEx:返回所安装的钩子句柄。

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

(2)函数WINAPI DllMain

当一个进程或线程载入和 卸载DLL时,都要调用该函数,它的原型是

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID

lpvReserved);

其中:

第一个参数hinstDLL:表示DLL的实例句柄;

第二个参数fdwReason:它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH (进程卸载),在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以 - 1 -

根据这个参数初始化特定的数据。

第三个参数lpvReserved:是系统保留;

在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。

(3)函数MouseProc

MouseProc 是鼠标钩子处理函数,当SetWindowsHookEx函数第一个参为:WH_MOUSE时,调用本函数,首先要在系统中安装一个鼠标消息钩子。

函数原型:

LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam);

参数:

nCode: 跟所有其他钩子处理函数一样,当 nCode小于0时:调用CallNextHookEx()。nCode 可以是HC_ACTION和 HC_NOREMOVE。当nCode等于HC_ACTION时,wParam和lParam 包含鼠标信息;当nCode等于HC_NOREMOVE时,wParam和lParam 包含鼠标信息,并且鼠标消息没有从消息队列里移除。

wParam: 指定鼠标消息ID。

lParam: 一个MOUSEHOOKSTRUCT 结构的指针。

返回值: 如果参数ncode小于0,则必须 返回CallNextHookEx(),也就是CallNextHookEx()的返回值如果参数ncode大于等于0,并且钩子处理函数没有处理消息,CallNextHookEx()的返回值,否则当您安装WH_MOUSE钩子的应用程序将不会得到通知,并且得到一个错误的结果,如果钩子处理的消息,您可以返回一个非0值,防止系统把消息发送到目标窗口程序。

(4)函数KeyboardProc

KeyboardProc是键盘钩子处理函数

函数原型:

LRESULT CALLBACK KeyboardProc( int code,WPARAM wParam, LPARAM lParam );

参数:

Code: 根据这个数值决定怎样处理消息如果code 小于0,则必须KeyboardProc()函数返回CallNextHookEx() code可以是下列值:

HC_ACTION:wParam和lParam包含按键消息;

HC_NOREMOVE:wParam和lParam包含按键消息,并且按键消息不能从消息队列中移除。

wParam: 按键的虚拟键值消息 。

lParam: 32位内存,内容描述包括:指定扩展键值,扫描码,上下文,重复次数。

0-15位: 按下键盘次数;

16-23位:指定扫描码,依赖于OEM ;

24位为1时候:表示按键是扩展键;为0时候:表示按键是是数字键盘按键;

25-28位:保留位 ;

29位:上下文键:为1时: ALT按下,其他情况为0 ;

30位:如果是按键按下后发送的消息,30位为1,如果是按键抬起后30位为1;

31位:指定转变状态:31位为0时候,按键正在被按下,为1时候,按键正 - 2 -

在被释放。

返回值:

如果参数code小于0,则必须返回CallNextHookEx(),也就是返回CalNext

HookEx()的返回值

如果参数code大于等于0,并且钩子处理函数没有处理消息,返回CallNextHookEx()的返回值,否则当您安装WH_KEYBOARD钩子时,钩子将不会得到通知,并返回错误结果。如果钩子处理的消息,可以返回一个非0值,防止系统把消息传递给钩子链中的下一个钩子,或者把消息发送到目标窗口。

(5)函数CallNextHookEx

功能是调用下一个钩子,原型为:

CallNextHookEx(hhk HHOOK,nCode Integer,wParam WPARAM,lParam

LPARAM);

参数:

HHOOK:当前钩子的句柄

nCode:钩子代码; 就是给下一个钩子要交待的

WPARAM:要传递的参数; 由钩子类型决定是什么参数

LPARAM:要传递的参数; 由钩子类型决定是什么参数

返回值: 返回下一个钩子执行后的返回值:0表示失败 。其中,参数 nCode的可选值:

HC_ACTION = 0;

HC_GETNEXT = 1;

HC_SKIP = 2;

HC_SKIP = 2;

HC_NOREMOVE = 3;

HC_NOREM = HC_NOREMOVE;

HC_SYSMODALON = 4;

HC_SYSMODALOFF = 5;

(6)函数XYZWindowFromPoint(实现流程图见图1,代码见附录3)

是返回返回光标(point)所在点的子窗口句柄,原型为:

XYZWindowFromPoint(HWND hwndParent,POINT point,UINT uFlags );

其中:

第一个参数hwndParent为处理父窗口;

第二个参数point为光标的坐标;

第三个参数uFlags为窗口的选择。这里默认为CWP_SKIPINVISIBLE(忽略不可见的子窗口);

(7)函数SendMessage

该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到口窗程序处理完消息再返回。原型为

SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);

参数:

hWnd:其窗口程序将接收消息的窗口的句柄。如果此参数HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或 - 3 -

不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。

Msg:指定被发送的消息。

wParam:指定附加的消息指定信息。

IParam:指定附加的消息指定信息。

返回值:返回值指定消息处理的结果,依赖于所发送的消息。

调用XYZWindowFromPointhwndParent(子窗口句柄)!=NULL是返回光标所在点的子窗口句柄得到光标所在点的窗口句柄是得到整个窗口在屏幕上的矩形框位置得到父窗口句柄否是枚举窗口计算窗口的大小返回光标所在点的子窗口句柄

图1 XYZWindowFromPoint的实现过程

4具体实现步骤

其具体实现过程见图2

(1)建立钩子

①选择MFC AppWizard(DLL)创建项目KSHook;

②选择MFC Extension DLL(共享MFC拷贝)类型;

③由于VC5没有现成的钩子类,所以要在项目目录中创建KSHook.h文件,在其中建立钩子

- 4 -

开始系统初始化在OnInitDialog()中调用安装钩子鼠标安装句柄(glhmouse)与键盘安装句柄(glhkey)!=NULL是安装鼠标,键盘钩子调用MouseProc(int nCode,WPARAM

wparam,LPARAM lparam)nCode>=0是调用XYZWindowFromPoint()获取光标所在的窗口句柄(glhTargetWnd)调用KeyboardProc(int nCode,WPARAM

wParam,LPARAM lParam)否有键按下是否是否回车否否glhTargetWnd!=glhPrevTarWnd(上次鼠标所指的窗口句柄)是获取窗口标题否获取虚拟键并变为字符否字符为空格文档给定的窗口句柄是否标识一个已存在的窗口否否发送给对话框显示继续传递消息保存目标窗口是否继续传递消息退出是卸载钩子,清变量结束

图2主程序流程图

- 5 -

类:

class AFX_EXT_CLASS CKSHook : public CObject

{

public:

CKSHook();//钩子类的构造函数

virtual ~CKSHook();//钩子类的析构函数

public:

BOOL StartHook(HWND hWnd); //安装钩子函数

BOOL StopHook();//卸载钩子函数

};

④在文件的顶部加入#include "KSHook.h"语句;

⑤在文件的顶部加入全局共享数据变量:

#pragma data_seg("mydata")

HWND glhPrevTarWnd=NULL; //上次鼠标所指的窗口句柄

HWND glhDisplayWnd=NULL; //显示目标窗口标题编辑框的句柄

HHOOK glhmouse=NULL; //安装的鼠标勾子句柄

HHOOK glhkey=NULL; //安装的键盘勾子句柄

HINSTANCE glhInstance=NULL; //DLL实例句柄

#pragma data_seg()

⑥在DEF文件中定义段属性:

SECTIONS

mydata READ WRITE SHARED

⑦在主文件的DllMain函数中加入保存DLL实例句柄的语句:glhInstance=hInstance;

⑧钩子函数的实现:(详细代码见附录1)

⑨类KSHook的成员函数的具体实现:(详细代码见附录2)

⑩编译项目生成。

(2)创建钩子可执行程序

①用MFC的AppWizard(EXE)创建项目Hook;

②选择“基于对话应用”并按下“完成”键;

③编辑对话框,删除其中原有的两个按钮,加入静态文本框和编辑框,用鼠标右键点击静态文本框,在弹出的菜单中选择“属性”,设置其标题为“鼠标所在的窗口标题”;

④在HookDlg.h中加入对KSHook.h的包含语句#include "KSHook.h";

⑤在CHookDlg.h的CHookDlg类定义中添加私有数据成员:

CKSHook m_hook; //加入钩子类作为数据成员

BOOL bHooked;

⑥修改CHookDlg::OnInitDialog()函数:

CWnd * pwnd=GetDlgItem(IDC_EDIT1); //取得编辑框的类指针

m_ook(pwnd->GetSafeHwnd());//取得编辑框的窗口句柄并安装钩子

⑦链接DLL库,即把..KSHookdebug KSHook 加入到项目设置链接标签中;

⑧编译项目生成可执行文件;

⑨把KSHook 拷贝到..Hook debug目录中;

⑩先运行几个可执行程序,然后运行程序,把鼠标在不同窗口中移动,在Hook程序窗口中的编辑框内将显示出鼠标所在的应用程序主窗口的标题(见附录4)。按下键盘上的一些键,可以发现EXE目录下自动生成了一个文件,该文件记载了你的按键信息(见附录5)。

- 6 -

5课程设计总结及其感想

这个课程设计是设计一个钩子程序,实现两个内容,第一是通过调用鼠标钩子来适时获取鼠标所在的窗口标题;第二是通过调用键盘钩子来获取键盘输入的信息。这个程序是用C++语言在Visual C++ 6.0编程软件上编程实现的。整个程序包括两个模块,一个是模块,它是把钩子函数集成在动态链接库中,利用动态链接库可以实现全局的钩子程序,它是程序的核心部分,里面包含钩子的创建、具体实现、撤消方面的内容,在创建时用到函数SetWindowsHookEx,在具体实现时用到函数MouseProc、函数KeyboardProc、函数CallNextHookEx,在撤消时用到函数UnhookWindowsHookEx来卸载钩子;另一个模块是模块,它是程序的入口,也是实现对话界面的,在运行过程中要调用模块,来实现程序的功能。

在本次课程设计中,自己付出了很多的时间,不仅在课程安排时间里认真的做,而且在课余时间也开了很多的时间。从不知道钩子什么开始,到现在能自己编写了一个简单的鼠标键盘钩子程序,我的收获是很大的。这次用到的是Visual C++编程软件,在此以前,都没有用过这个软件,在学习C++时用的也不是这个软件,因此在开始做设计的前几天也花了一些时间去学习这个软件的应用,现在自己已经初步掌握了这个软件的应用了,但是要想熟练的应用还需进一步的学习。从这次课程设计中,我深刻的体会到重在坚持的意义,很多的东西一开始我们并不会的,但是只要坚持的看进去,一遍一遍的不厌其烦看,会发觉慢慢的就懂了,因此,无论做什么事都要有决心和恒心。

6参考文献

[1] 王育坚.Visual C++面向对象编程教程(第2版)[M].北京:清华大学出版社,2007.10.

[2] 王西武,阎梅 ,赵怀勋. 在VC6下应用Windows系统钩子技术[J]. 现代电子技术 . 2004:27(17):45-46.

[3] 徐士良.常用算法程序集:C++语言描述(第4版)[M].北京:清华大学出版社,2009.7.

[4] 钱能.C++程序设计教程:设计思想与实现(修订版)[M].北京:清华大学出版社,2009.7.

[5] 游洪跃, 伍良富, 王景熙.C++面向对象程序设计实验和课程设计教程[M].北京:清华大学出版社,2009.2.

[6]

倪步喜.Windows的钩子技术及实现[J].计算机与现代化.2007,28(1):28-30.

- 7 -

附录1:(钩子函数的具体实现)

//鼠标钩子函数

LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam)

{

LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *)lparam;

if (nCode>=0)

{

HWND glhTargetWnd=XYZWindowFromPoint(NULL,pMouseHook->pt);

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(glhmouse,nCode,wparam,lparam); //继续传递消息

}

//键盘钩子函数

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

{

char ch=0;

FILE *fl;

if( ((DWORD)lParam&0x40000000) && (HC_ACTION==nCode) ) //有键按下

{

fl=fopen("","a+"); //输出到文件

if (wParam<=0x2f||wParam>=0x100)

ch=' ';

else

{

BYTE ks[256];

GetKeyboardState(ks);

WORD w;

UINT scan=0;

ToAscii(wParam,scan,ks,&w,0);

//ch=MapVirtualKey(wParam,2); //把虚键代码变为字符

ch =char(w);

}

fwrite(&ch, sizeof(char), 1, fl);

fclose(fl);

}

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

}

- 8 -

附录2:(类KSHook的成员函数的具体实现)

CKSHook::CKSHook()

{

}

CKSHook::~CKSHook()

{

if(glhmouse&&glhkey)

{

UnhookWindowsHookEx(glhmouse);

UnhookWindowsHookEx(glhkey);

}

}

//安装钩子并设定接收显示窗口句柄

BOOL CKSHook::StartHook(HWND hWnd)

{

BOOL bResult=FALSE;

glhmouse=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);

glhkey=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);

if(glhmouse!=NULL&&glhkey!=NULL)

bResult=TRUE;

glhDisplayWnd=hWnd; //设置显示目标窗口标题编辑框的句柄

return bResult;

}

//卸载钩子

BOOL CKSHook::StopHook()

{

BOOL aResult=FALSE;

BOOL bResult=FALSE;

if(glhmouse&&glhkey)

{

aResult= UnhookWindowsHookEx(glhmouse);

bResult= UnhookWindowsHookEx(glhkey);

if(bResult&&aResult)

{

glhPrevTarWnd=NULL;

glhDisplayWnd=NULL; //清变量

glhmouse=NULL;

glhkey=NULL;

}

}

return bResult;

}

- 9 -

附录3:(获取窗口句柄)

HWND XYZWindowFromPoint(HWND hwndParent,POINT point,UINT uFlags)

{

if(hwndParent != NULL)

return ::ChildWindowFromPointEx(hwndParent, point, uFlags);

//返回光标(point)所在点的子窗口句柄

RECT rect, rectSearch;

HWND pWnd, hWnd, hSearchWnd;

hWnd = ::WindowFromPoint(point);//得到光标(point)所在点的窗口句柄

if(hWnd != NULL)

{

::GetWindowRect(hWnd, &rect); //得到整个窗口在屏幕上的矩形框位置

pWnd = ::GetParent(hWnd); //得到父窗口句柄

if(pWnd != NULL)

{

hSearchWnd = hWnd;

do

{

hSearchWnd=::GetWindow(hSearchWnd, GW_HWNDNEXT);

//如果再也找不到这样的窗口,该函数就会返回NULL

::GetWindowRect(hSearchWnd, &rectSearch);

if(::PtInRect(&rectSearch, point) && ::GetParent(hSearchWnd) ==

pWnd &&::IsWindowVisible(hSearchWnd))

{

//比较看谁的面积最小

if((() * ( -

)) <(( - ) * ( -

)))

{

hWnd = hSearchWnd;

::GetWindowRect(hWnd, &rect);

}

}

}

while(hSearchWnd != NULL);

}

}

return hWnd;

}

- 10 -

附录4:(鼠标钩子截获的应用程序主窗口的标题)

附录5:(键盘钩子截获的按键信息)

在Hook中从键盘输入以下内容:

EXE目录下自动生成了一个文件,记录按键信息:

- 11 -