文章作者:tabby
原始地址:http://blog.csdn.net/tabby/archive/2007/12/25/1966980.aspx
在编写程序的过程中,我们有时需要实现屏蔽操作系统一些热键的功能,如(Ctrl+Alt+Delete,Ctrl+Shift+Esc等)。网络上有很多关于这方面的资料,总结了一下,一般有如下两个方法:
1. 通过加载低级键盘钩子(WH_KEYBOARD_LL)截获大部分的系统热键,并屏蔽它。这个方法比较简单,但有个缺陷,那就是对Ctrl+Alt+Delete没有办法。
2. 通过远程注入DLL到winlogon进程,修改winlogon桌面下SAS窗口的回调函数,从而捕获该窗口的WM_HOTKEY消息,并屏蔽它,可以实现屏蔽ctrl+delete+alt。这个方法相对复杂,可以解决第一点中存在的问题,但是也有缺陷,那就是除了Ctrl+Alt+Delete外,大多数的其它系统热键,(包括Alt+Tab,Ctrl+Esc及左右两个Windows键)都无法屏蔽。
所以如果我们的程序需要屏蔽大量的系统热键,就应当将以上两个方法结合起来使用。dll注入有很多好处,包括可以实现对我们所运行进程的隐藏,这非常有用。当我们的进程运行后,屏蔽掉了系统热键,当然不想用户随便在进程管理器里面就kill掉,因此将以上两个方法结合的办法就是把代码通通写到dll里面,然后再一起注入到winlogon进程中。
对于上面所讲的第二点,注入到winlogon进程,没问题,可以很好的完成功能(网上有很多相关的文章及代码)。但对于第一点,如果在一个GUI程序中加载钩子,也没有问题。但现在加载的对象是winlogin进程,这个进程很特殊,它不是GUI进程,也不在系统应用程序所处的Default桌面下,因此在这个进程中加载钩子,需要注意以下几点:
1. 在需要注入的DLL代码中的DLL_PROCESS_ATTACH后面开启一个新线程,并在该线程中实现修改SAS窗口回调函数的代码以及设置低级键盘钩子。
2. 由于钩子所在的线程为非GUI线程,因此,必须在该线程成功设置钩子以后主动接收并分发收到的消息,否则钩子将不会钩到任何消息:
MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
3. 由于该线程创建时默认与winlogon同属一个桌面(winlogon桌面),而其它包括explorer.exe在内的GUI程序都处在Default桌面,Windows中规定程序只能获得针对同一桌面上创建的窗口消息。所以,要让该线程能接收到用户在Default桌面下操作所产生的消息,必须在该线程中使用如下代码将它的桌面设置为Default桌面:
HDESK hDesk = OpenDesktop("Default",0,FALSE,MAXIMUM_ALLOWED); SetThreadDesktop(hDesk); CloseHandle(hDesk);
我的程序在解决了以上问题之后,能正确将所编写的DLL注入到winlogon进程,并在DLL加载的时候开启线程,设置钩子,替换SAS窗口回调函数。从而实现屏蔽了我所能想到的所有系统热键。
附:DLL的完整源码(注入该DLL到winlogon进程的源码大家可以在网上找到)
#define _WIN32_WINNT 0x0500 //Use WH_KEYBOARD_LL #include <windows.h> #include <stdio.h> //SAS window句柄 HWND hSASWnd = NULL; //原有SAS window回调函数地址 FARPROC FOldProc = NULL; //起屏蔽作用的新SAS window回调函数 LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam); //枚举所有窗体句柄的回调函数 BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam); //Dll所创建线程的句柄 HANDLE hThread = NULL; //Dll所创建线程的ID DWORD dwThreadId = 0; //Dll所创建线程的线程函数 DWORD WINAPI ThreadFunc(); //_H钩子句柄 HHOOK hHook = NULL; //_H低级键盘钩子回调函数 LRESULT CALLBACK KeyboardProc(int,WPARAM,LPARAM); //对外输出字符串 char szOutput[36]; BOOL APIENTRY DllMain(HANDLE hMoudle, DWORD dwReason, LPVOID lpReserved) { switch(dwReason) { case DLL_PROCESS_ATTACH: sprintf(szOutput,"Dll成功加载于 %d 号进程。",GetCurrentProcessId()); OutputDebugString(szOutput); //创建更替SAS window回调函数的线程 if(FOldProc == NULL) hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&dwThreadId); break; case DLL_PROCESS_DETACH: sprintf(szOutput,"Dll成功卸载。",GetCurrentProcessId()); //MessageBox(NULL, szOutput, "ZZ", MB_ICONINFORMATION | MB_OK); OutputDebugString(szOutput); //恢复原有SAS window的回调函数 if(FOldProc != NULL) SetWindowLong(hSASWnd,GWL_WNDPROC,long(FOldProc)); //_H卸载低级键盘钩子 if(hHook != NULL) { if(!UnhookWindowsHookEx(hHook)) { OutputDebugString("Unhook failed.."); //__leave; break; } OutputDebugString("键盘钩子成功取消"); } TerminateThread(hThread,1); CloseHandle(hThread); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; } //Dll所创建线程的线程函数 DWORD WINAPI ThreadFunc() { //打开Winlogon桌面 HDESK hDesk = OpenDesktop("Winlogon",0,FALSE,MAXIMUM_ALLOWED); //枚举桌面所有窗体 EnumDesktopWindows(hDesk,(WNDENUMPROC)EnumWindowsProc,0); //修改SAS window的回调函数 if(hSASWnd != NULL) { FOldProc = (FARPROC)SetWindowLong(hSASWnd,GWL_WNDPROC,long(SASWindowProc)); } CloseHandle(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 hHook = SetWindowsHookEx(WH_KEYBOARD_LL,KeyboardProc,GetModuleHandle(NULL),0); if (hHook == NULL) { OutputDebugString("Set hook failed.."); //__leave; return 1; } OutputDebugString("键盘钩子成功设置"); //_H在非GUI线程中使用消息钩子必须主动接收并分发收到的消息 MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 1; } //枚举所有窗体句柄的回调函数 BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam) { char ClassBuf[128]; //获得当前窗体的显示文本 GetWindowText(hwnd,ClassBuf,sizeof(ClassBuf)); //在"Winlogon"桌面中查询窗口"SAS window"。 if(strstr(ClassBuf,"SAS window")!=NULL) { //返回SAS window句柄 hSASWnd = hwnd; return FALSE; } return TRUE; } //起屏蔽作用的新SAS window回调函数 LRESULT CALLBACK SASWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { if(uMsg == WM_HOTKEY) { //屏蔽所有WM_HOTKEY消息 OutputDebugString("All SAS window's hotkeys are disabled"); return 1; WORD wKey = HIWORD(lParam); WORD wModifier = LOWORD(lParam); bool IsCtrlDown = ((wModifier & VK_CONTROL) != 0); bool IsAltDown = ((wModifier & VK_MENU) != 0); bool IsShiftDown = ((wModifier & VK_SHIFT) != 0); //Ctrl + Alt + Del组合键 if(IsCtrlDown && IsAltDown && wKey == VK_DELETE) { return 1; //屏蔽 } //Ctrl + Shift + Esc组合键,这个组合键将显示任务管理器,可根据需要是否屏蔽。 else if(IsCtrlDown && IsShiftDown && wKey == VK_ESCAPE) { // Do nothing } } return CallWindowProc((WNDPROC)FOldProc,hwnd,uMsg,wParam,lParam); } //_H低级键盘钩子回调函数 LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam) { if (nCode == HC_ACTION) { switch (wParam) { case WM_KEYDOWN: case WM_SYSKEYDOWN: //case WM_KEYUP: case WM_SYSKEYUP: PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam; if (p->vkCode == VK_F12) { //实现模拟按键代码 MessageBox(GetForegroundWindow(),"I'm in position..","ZZ",MB_OK); } //屏蔽ALT+TAB else if ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0)) { OutputDebugString("ALT+TAB is disabled"); return 1; } //屏蔽ALT+ESC else if ((p->vkCode == VK_ESCAPE) && ((p->flags & LLKHF_ALTDOWN) != 0)) { OutputDebugString("ALT+ESC is disabled"); return 1; } //屏蔽CTRL+ESC else if ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0)) { OutputDebugString("CTRL+ESC is disabled"); return 1; } //屏蔽CTRL+SHIFT+ESC,(SAS window中也已屏蔽) else if ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0) && ((GetKeyState(VK_SHIFT) & 0x8000) != 0)) { OutputDebugString("CTRL+SHIFT+ESC is disabled"); return 1; } //屏蔽左右windows键 else if (p->vkCode == VK_LWIN || p->vkCode == VK_RWIN) { OutputDebugString("windows key is disabled"); return 1; } //此处无法屏蔽CTRL+ALT+DEL,已在SAS window中屏蔽 else if ((p->vkCode == VK_DELETE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0) && ((GetKeyState(VK_MENU) & 0x8000) != 0 )) return 1; break; } } return CallNextHookEx(hHook,nCode,wParam,lParam); }
从网易博客(http://cxwstar.blog.163.com/blog/static/484687632011128113057867/)迁移,原发表于2011年2月28日.