关键词
Windows2000, VC++, C++Bulider, Visual Basic, HOOK, DLL
引言
在一些应用场合,比如基于Windows2000(以下简称Win2K)下开发工控软件需要,为了增强系统安全性,需要对键盘事件进行监控、屏蔽。满足控制系统安全性要求。作为一个Win2K后台监控软件的编写,需要注意如下要点:HOOK(键盘挂钩函数),DLL,MsgINA.dll,Shell_NotifyIcon(托盘函数)。为了提高软件编写效率,可以采用混合编程方式,即采用VC++/ C++Bulider 6.0编写DLL文件,采用Visual Basic编写客户端程序。
1 HOOK与DLL简介
1) HOOK
HOOK是一种反调函数。是Windows系统为应用程序提供用于监控系统各种事件消息的类中断程序。在系统消息机制里挂上用户自定义消息处理钩子(HOOK),达到对消息的过滤。Windows系统本身提供数个HOOK函数,为实现在Win2K/NT平台下的键盘屏蔽,要采用低级键盘HOOK,即WH_KEYBOARD_LL。此HOOK函数可以屏蔽Ctrl+Esc、Alt+Tab、和Alt+Esc等系统功能键,在WINNT SP3后的操作系统中都是支持的。设置HOOK需要用到SetWindowsHookEx()函数,在程序退出后,必须用UnhookWindowsHookEx()函数卸载掉HOOK。
2) DLL与Msgina.dll
DLL(动态链结库)是Microsoft Windows最重要组成之一。大多数与Windows相关程序,不是程式模块组模式,就是动态链结库模式。为实现对所有键盘事件的监控,必须将HOOK函数放在DLL文件中。
Windows本身就是由许多的DLL组成的,它所有的库模块也都设计成DLL。在Win2K在,为了屏蔽Ctrl+Alt+Del组合键,必须了解Msgina.dll。在Win2K系统中,微软采用Winlogon和GINA-Graphical Identification and Authentication提供交互式登录支持。登录成功后,按下Ctrl+Alt+Del组合键,系统将通过Winlogon调用Msgina.dll内部函数WlxLoggedOnSAS。所以要屏蔽Ctrl+Alt+Del组合键,则可以写一个新的GINA.dll,其中提供接口调用Msgina.dll,从而实现屏蔽。
3) Shell_NotifyIcon
客户端程序应该运行在后台,所以可以将其最小化在系统托盘中。采用Shell_NotifyIcon API函数用来添加、删除、更改系统托盘区(taskbar status area)的图标。
2 程序实现
在本文中,采用VC++6.开发系统GINA DLL, C++Bulider 6.0开发低层HOOK DLL,VB6.0开发客户端程序,实现混合编程。
1) 自定义GINA编写
因为自定义GINA编写资料较多,本文只简要介绍。自定义GINA可以采用VC++6.0开发。下面给出Windows2000 的Msgina内部函数表。表中函数将在自定义GINA中导入。
函数名 |
说明 |
WlxActivateUserShell |
激活用户外壳程序 |
WlxDisPlayLockedNotice |
允许GINA dll显示锁定信息 |
WlxDisPlaySASNotice |
当没有用户登录时,winlogon调用此函数 |
WlxDisPlayStatusMessage |
Winlogon用一个状态信息调用此函数进行显示 |
WlxGetConsoleSwitchCredentials |
Winlogon调用此函数读取当前登录用户的信任信息,并透明的将它们传到目标会话 |
WlxGetStatusMessage |
Winlogon调用此函数获取当前状态信息 |
WlxIntialize |
针对指定的窗口位置进行GINA dll初始化 |
WlxIsLockOk |
验证工作站正常锁定 |
WlxIslogoffOk |
验证注销正常 |
WlxLoggedOnSAS |
用户已登录并且工作站没有被加锁,若此时接收到SAS事件,则Winlogon调用此函数 |
WlxLoggedOutSAS |
没有用户登录,若此时接收到SAS事件,则Winlogon调用此函数 |
WlxLogoff |
请求注销操作时通知GINA dll |
WlxNegotiate |
表示当前的winlogon版本是否能使用GINA dll |
WlxNetworkProviderLoad |
在加载网络服务提供程序收集了身份和认证信息后,Winlogon调用此函数 |
WlxRemoveStatusMessage |
Winlogon调用此函数告诉GINA dll停止显示状态信息 |
WlxScreensaverNotify |
允许GINA与屏幕保护操作交互 |
WlxShutdown |
在关闭之前Winlogon调用此函数,允许GINA实现任何关闭任务,例如从读卡器中退出智能卡 |
WlxStartApplication |
当系统需要在用户的上下文中启动应用程序时调用此函数 |
WlxWkstalockedSAS |
当工作站被锁定,如果接收到一个SAS,则Winlogon调用此函数 |
我们需要注意的是WlxLoggedOnSAS函数。屏蔽Ctrl+Alt+Del组合键代码将在调用该函数时添加。我们采用读取注册表键值来判断是否屏蔽,而该键值将在客户端程序中被操作。
// 当系统处于登陆成功,没有锁定的状态下 // Winlogon接收到SAS事件,于是调用该函数 int WINAPI WlxLoggedOnSAS(PVOID pWlxContext, DWORD dwSasType, PVOID pReserved) { HKEY hKey; DWORD dwType=REG_DWORD; //定义读取数据类型:双字节 char content[4]; //所查询注册表键值的内容 DWORD dwLength=4; //打开注册表键 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE//FCSKBLock//KBConfig", 0,KEY_READ,&hKey) ==ERROR_SUCCESS) { //读取CtrlAltDel键值 if(RegQueryValueEx(hKey,"CtrlAltDel",NULL,&dwType,(unsigned char *)content,&dwLength) ==ERROR_SUCCESS) { if(* content==1) return WLX_SAS_ACTION_NONE;//直接返回桌面程序,实现屏蔽 } } return theApp.MyWlxLoggedOnSAS(pWlxContext,dwSasType,pReserved ) ; } |
开发完成的自定义GINA.dll要放到Wint/system32文件夹中。并修改注册表:
键项名 |
/HKEY_LOCAL_MACHINE/Software/Microsoft/WindowsNT/CurrentVersion/Winlogon |
子键名 |
myGina(任意名称均可) |
子键类型 |
[REG_SZ] |
子键值 |
myGina(自定义Gina的名称) |
若GINADLL不存在,新建即可。
再重启计算机后myGina即为系统使用。
2) 全局HOOK、DLL编写
采用BORLAND C++Bulider 6.0(以下简称BCB)编写安装全局HOOK的DLL文件。BCB是一款优秀的C/C++语言开发工具,可以快速开发高质量的Windows程序。下面介绍简要步骤:
I. 利用BCB新建向导,建立一个DLL工程。在此DLL中我们将有条件的安放两个HOOK。一个用于捕获系统功能热键并屏蔽,另一个用作客户端程序的激活热键;
II. 在cpp里添加如下代码:
此段代码用于申明全局变量和导出函数。因为此DLL文件将被VB编写的客户端程序所调用,所以声明导出函数时需要将语句extern ”C” 放置在声明处。另外在BCB中默认的调用约定为__cdecl方式,而在VB中调用约定为__stdcall。
pragma argsused //下面变量用于HOOK.cpp static HHOOK hOldHook=0;/*记录上一个注册的键盘钩子*/ static HHOOK hOldHook2=0;/*记录上一个注册的键盘钩子*/ static HWND hProcWnd=0; /*记录客户程序的窗体*/ static HANDLE hInstance=0;/*DLL的句柄*/ //导出setHotKey extern "C" __declspec(dllexport) char _stdcall ActivateKey(HWND hWnd,bool nCode,bool bWhich);
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { //用全局变量保存这个DLL的句柄 hInstance=hinst; return 1; } |
因为客户端程序是作为后台运行的,所以我们需要给其安放个激活热键,以便用户在任何情况下通过热键呼出。所以必须通过DLL文件安放一个全局HOOK,用作激活热键。当用户按下激活热键后,DLL会截获消息并向指定的客户端程序发送激活消息。
//客户端程序热键--------------------------------------------------------------------------- LRESULT CALLBACK HotKeyProc(int nCode,WPARAM wParam,LPARAM lParam) { bool fEatKeystroke = FALSE; //PKBDLLHOOKSTRUCT p = NULL; if (nCode == HC_ACTION) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; fEatKeystroke = ((p->flags & LLKHF_ALTDOWN) != 0)&& (p->vkCode==VK_F12);//自定义激活//热键: Alt+F12 break; } if(fEatKeystroke) SendMessage(hProcWnd,WM_USER+200,2000,0); //用于激活客户程序的自定义消息 } return(fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam)); }
|
此处为捕获、屏蔽系统功能热键的回调函数,用户可根据需要添加修改需要屏蔽的按键。
//屏蔽Ctrl+Esc/Alt+Tab/Win/F1/Alt+Esc等功能按键------------------------------------------------------------------ LRESULT CALLBACK ShieldKeyProc(int nCode,WPARAM wParam,LPARAM lParam) { bool fEatKeystroke = FALSE; //PKBDLLHOOKSTRUCT p = NULL; if (nCode == HC_ACTION) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; fEatKeystroke = (p->vkCode==VK_F1)||//F1 ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0))|| //Alt+Tab ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0)) || //Alt+Esc ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0)) || //Ctrl+Esc (((GetKeyState(VK_CONTROL) & 0x8000) != 0) && (p->vkCode == VK_SPACE))|| //Ctrl+Space (((GetKeyState(VK_CONTROL) & 0x8000) != 0) && ((GetKeyState(VK_SHIFT) & 0x8000) != 0)); break; } } return(fEatKeystroke ? 1 : CallNextHookEx(NULL, nCode, wParam, lParam)); } |
这个函数负责根据客户端调用参数,向系统注册、注销HOOK。HOOK必须在不要的时候卸载!
//HWND hWnd:客户端程序调用窗体的句柄,bool nCode:挂还是不挂HOOK的标志,bool bWhich:挂哪个HOOK的标志 char _stdcall ActivateKey(HWND hWnd,bool nCode,bool bWhich) { if (bWhich) { if(nCode) // 安放底层HOOK { hProcWnd=hWnd;//记录下这一个DLL是由哪个窗体调用的 hOldHook=SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)ShieldKeyProc,hInstance,0); //记录下上一个DLL是由哪个窗体调用的 return(hOldHook != NULL? 1: 0 ); } else // 卸下HOOK UnhookWindowsHookEx(hOldHook); } else { if(nCode) // 安放HotHooK { hProcWnd=hWnd;//记录下这一个DLL是由哪个窗体调用的 hOldHook2=SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)HotKeyProc,hInstance,0); //记录下上一个DLL是由哪个窗体调用的 return(hOldHook2 !=NULL ? 1: 0); } else // 卸下HOOK UnhookWindowsHookEx(hOldHook2); } return true; |
III. 以Release方式编译保存。
IV. 关于DLL调试可以参见有关文档。
3) 客户端程序
微软的 Visual Basic 因为其编写Windows界面程序的方便、灵活而成为我们开发客户端程序的首选。我们采用Visual Basic 6.0 中文企业版(以下简称VB)进行开发。VB本身并不直接支持DLL文件的开发,但提供了对DLL的调用功能。作为客户端程序,就是实现用户操作与程序调用DLL,API函数的转换。下面介绍简要步骤:
I. 工程建立
新建三个窗体.分别命名为:Form1,frmLogin,Dialog.
Form1作为主窗体界面布置如图<1>:
第一项采用API函数屏蔽任务栏;
第二项通过操作注册表,实现屏蔽Ctrl+Alt+Del组合键;
第三项通过调用开发的底层键盘HOOK DLL实现功能键的屏蔽。
密码设置项用于客户端程序激活需要密码情况。
frmLogin作为用户设置密码后,重新激活的登录窗体,如图<2>:
Dialog则作为”密码设置”窗体,如图<3>:
II. 代码流程:
本文给出主流程图。
说明:
i. 因为软件是基于Windows2000平台,所以启动后首先要判断系统平台;
ii. 考虑系统安全性,程序要检查是否已有远行实例;
iii. 因为要接受DLL文件发送的激活消息,所以可以在窗体加载事件中通过SetWindowLong函数在VB消息序列中添加自定义消息过滤函数。
SetWindowLong语法:
SetWindowLong (hwnd, GWL_WNDPROC, AddressOf SysMenuProc)
hwnd:当前窗体的句柄
GWL_WNDPROC:设置一个新的窗口消息处理过程的地址
AddressOf SysMenuProc :取新的窗口消息处理过程名称
返回值代表前个窗体消息处理过程。
SysMenuProc 函数是个回调函数。必须声明定义在标准模块中。
iv. 程序最小化在系统托盘区编程利用Shell_NotifyIcon函数。
Shell_NotifyIcon语法可以参见微软的MSDN。添加系统托盘图标子程序放在窗体的Resize事件中。程序在退出时必须删除图标。