锁定 Windows 桌面






4.84/5 (155投票s)
2004 年 6 月 14 日
9分钟阅读

1544246

63854
限制 Windows 访问,隐藏桌面窗口并禁用特殊按键。
注意
应 VB(及其他语言)程序员的诸多请求,我终于花了一些时间更新了项目,并将所有功能都移到了 DLL 中,以便任何能够调用 Windows 标准库(DLL)的语言编写的程序都可以使用它们。为了演示这些功能,我还包含了 C 和 VB 项目。
引言
我管理着一个系统,需要限制用户访问桌面和其他应用程序。经过一番搜索,我找到了几种不同的技术来实现这一点。虽然最终我没有使用这里描述的任何技术,但我决定将所有代码汇总到一个应用程序中,供所有需要它的人使用。
注意: 我不声称本文中提供的任何代码是我的作品。该应用程序是多个来源的汇编,我将尽力在可能的情况下注明作者。
一、隐藏桌面窗口
隐藏 Windows 桌面、任务栏、开始按钮等,通常是通过将 `FindWindow()` 返回的窗口句柄传递给 `ShowWindow()` 来实现的。
例如,如果您想隐藏任务栏,可以使用以下代码:
ShowWindow(FindWindow("Shell_TrayWnd", NULL), SW_HIDE);
如果您想隐藏开始按钮,您需要先知道按钮控件的 ID,然后使用相同的技术。
ShowWindow(GetDlgItem(FindWindow("Shell_TrayWnd", NULL), 0x130), SW_HIDE);
我如何知道任务栏的类名是“`Shell_TrayWnd`”或开始按钮的 ID 是 0x130?我使用了 Microsoft Visual C++ 6.0 附带的 Spy++ 工具。您可以对任何您想隐藏的窗口或控件使用此技术。
如果您只是想禁用窗口而不是隐藏它,请将 `ShowWindow()` 调用更改为 `EnableWindow()`。
您会发现,如果您隐藏了桌面和任务栏,按下 Win 键或双击桌面区域时,开始菜单仍然会弹出。要了解如何阻止这种不期望的行为,您需要阅读下一节。
二、禁用系统按键
我将系统按键定义为操作系统(OS)用于切换任务或调出任务管理器的所有特殊按键组合。
禁用这些按键组合有几种方法。
Win9x/ME
您可以通过欺骗操作系统,让它认为屏幕保护程序正在运行,来禁用所有这些按键组合(包括 Ctrl+Alt+Del)。这可以通过以下代码实现:
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, &bOldState, 0);
这个技巧在 Windows NT 或更高版本(Win NT+)中无效,因此您需要其他技术。
钩子 (Hooks)
在 Win NT+ 中,捕获按键切换组合的一种方法是编写一个键盘钩子。您可以通过调用 `SetWindowsHookEx()` 来安装键盘钩子。
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hInstance, 0);
`KeyboardProc()` 函数是一个 **回调** 函数,每次按下按键时都会由操作系统调用。在 `KeyboardProc()` 中,您可以决定是捕获按键还是让操作系统(或钩子链中的下一个应用程序)处理它。
LRESULT KeyboardProc(...) { if (Key == VK_SOMEKEY) return 1; // Trap key return CallNextHookEx(...); // Let the OS handle it }
要释放钩子,请使用:
UnhookWindowsHookEx(hKeyboardHook);
有两种类型的钩子:本地钩子和全局(或系统范围)钩子。本地钩子只能捕获您应用程序的事件,而全局钩子可以捕获所有运行应用程序的事件。
要捕获任务切换按键,必须编写一个全局钩子。
Microsoft 文档指出,全局钩子过程应放在一个单独的 DLL 中。然后,该 DLL 会映射到每个进程的上下文中,并可以捕获每个进程的事件——这就是为什么钩子被用于将代码注入到远程进程中。
在我的应用程序中,我想避免使用外部库,所以我将全局钩子设置在我自己的应用程序中(无需外部库)。这是通过在 `SetWindowsHookEx()` 调用的第三个参数中传入应用程序的实例句柄(而不是文档中说明的库句柄)来实现的。这种技术在 Win 9x 中效果很好,但 Win NT+ 不同。通过使用新的键盘和鼠标低级钩子可以实现相同效果。这些新的钩子不需要外部库,因为它们的运作方式与旧钩子不同。文档指出“[...] `WH_KEYBOARD_LL` 钩子不会注入到另一个进程中。相反,上下文会切换回安装钩子的进程,并在其原始上下文中调用它。然后上下文会切换回生成事件的应用程序。”。
我将不再详细介绍钩子,因为有很多优秀的文章讨论了这个主题。
仍然有一个问题:键盘钩子无法捕获 Ctrl+Alt+Del 组合键!为什么?因为操作系统从未将此按键组合发送到键盘钩子链。它在操作系统的不同级别处理,并且永远不会发送到应用程序。那么,我们如何捕获 Ctrl+Alt+Del 按键组合呢?请阅读下一节以了解。
Ctrl+Alt+Del
禁用此按键组合有几种方法:
- 禁用任务管理器。这不会捕获按键组合,它只是禁用了按下此按键组合时弹出的应用程序(任务管理器)。请参阅下文如何执行此操作。
- 使用键盘设备驱动程序捕获按键。为此,您需要安装 DDK。我在这里不描述这种方法。
- 编写一个 GINA 存根。GINA 是 Winlogon 用来执行用户身份验证的 DLL。我不会在这里讨论这种方法,但您可以在这里找到如何操作 [16]。
- 子类化 Winlogon 进程的 SAS 窗口。为此,您必须将代码注入到 Winlogon 进程中,然后子类化其窗口过程。稍后将描述两种实现此目的的技术。
禁用任务管理器
要禁用任务管理器,您只需启用“删除任务管理器”策略,无论是使用组策略编辑器(gpedit.msc)还是设置注册表项。在我的应用程序中,我使用注册表函数为以下项设置值:
HKCU\Software\Microsoft\Windows\CurrentVersion\
Policies\System\DisableTaskMgr:DWORD
将其设置为 1 禁用任务管理器,设置为 0(或删除项)则重新启用它。
子类化 Winlogon 的 SAS 窗口
您通过调用以下函数来子类化窗口过程:
SetWindowLong(hWnd, GWL_WNDPROC, NewWindowProc);
此调用仅对由您的应用程序创建的窗口有效,即您无法子类化属于其他进程的窗口(`NewWindowProc()` 的地址仅对调用 `SetWindowLong()` 函数的进程有效)。
那么,我们如何子类化 Winlogon 的 SAS 窗口呢?
答案是:您必须以某种方式将 `NewWindowProc()` 的地址映射到远程进程的地址空间中,并将其传递给 `SetWindowLong()` 调用。
将内存映射到远程进程地址空间的技术称为注入。
注入可以通过以下方式实现:
- 注册表。要将 DLL 注入到进程中,只需将 DLL 名称添加到注册表项中。
HKLM\Software\Microsoft\Windows NT\ CurrentVersion\Windows\AppInit_DLLs:STRING
此方法仅在 Windows NT 或更高版本上支持。
- 使用钩子注入 DLL。此方法支持所有 Windows 版本。
- 使用 `CreateRemoteThread()`/`LoadLibrary()` 注入 DLL。仅在 Windows NT 或更高版本上可用。
- 使用 `WriteProcessMemory()` 将代码直接复制到远程进程,并通过调用 `CreateRemoteThread()` 来启动其执行。仅在 Windows NT 或更高版本上可用。
我更偏爱的注入技术是最后一种。它的优点是不需要外部 DLL。为了正确工作,此方法需要对注入到远程进程中的函数进行非常仔细的编码。为了帮助您避免此技术的常见陷阱,我在源代码开头插入了一些提示。对于那些认为此方法过于危险的人,我还包含了 `CreateRemoteThread()`/`LoadLibrary()` 方法。只需将 `#define DLL_INJECT `,应用程序就会改用此方法。
将代码注入 Winlogon 的子类化后,SAS 窗口也会缩小。
- 获取 SAS 窗口句柄
hSASWnd = FindWindow("SAS Window class", "SAS window");
- 子类化 SAS 窗口过程
SetWindowLong(hSASWnd, GWL_WNDPROC, NewSASWindowProc);
- 在 `NewSASWindowProc()` 内部,捕获 `WM_HOTKEY` 消息,并为 Ctrl+Alt+Del 按键组合返回 1。
LRESULT CALLBACK NewSASWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_HOTKEY) { // Ctrl+Alt+Del if (lparam == MAKELONG(MOD_CONTROL | MOD_ALT, VK_DELETE)) return 1; } return CallWindowProc(OldSASWindowProc, hWnd, wParam, lParam); }
其他技术
1. `RegisterHotKey()`/Me
我只通过此方法设法禁用了 Alt+Tab 和 Alt+Esc 按键组合。
2. `SystemParametersInfo(SPI_SETSWITCHTASKDISABLE/SPI_SETFASTTASKSWITCH)`
在我尝试过的所有版本中,此方法都无效!
3. 切换到新桌面
使用此技术,您可以创建一个新桌面并切换到它。因为其他进程(通常)在“默认”桌面上运行(Winlogon 在“Winlogon”桌面上运行,屏幕保护程序在“屏幕保护程序”桌面上运行),这会有效地锁定 Windows 桌面,直到在新桌面上运行的进程完成。
以下代码描述了创建新桌面并切换到它,以及在新桌面中运行线程/进程所需的步骤:
// Save original desktop hOriginalThread = GetThreadDesktop(GetCurrentThreadId()); hOriginalInput = OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP); // Create a new Desktop and switch to it hNewDesktop = CreateDesktop("NewDesktopName", NULL, NULL, 0, GENERIC_ALL, NULL); SetThreadDesktop(hNewDesktop); SwitchDesktop(hNewDesktop); // Execute thread/process in the new desktop StartThread(); StartProcess(); // Restore original desktop SwitchDesktop(hOriginalInput); SetThreadDesktop(hOriginalThread); // Close the Desktop CloseDesktop(hNewDesktop);
要将桌面分配给线程,必须从正在运行的线程内部调用 `SetThreadDesktop(hNewDesktop)`。要在新桌面中运行进程,传递给 `CreateProcess()` 的 `STARTUPINFO` 结构的 `lpDesktop` 成员必须设置为桌面的名称。
参考文献
- 通过 Paul DiLascia 的 Trapkeys 禁用 Windows XP 中的按键.
- 捕获 CtrlAltDel;在 Windows 2000/XP 上隐藏任务列表中的应用程序,作者:Jiang Sheng。
- 将代码注入另一个进程的三种方法,作者:Robert Kuster。
- 钩子和 DLL,作者:Joseph M. Newcomer。
- 键盘钩子,作者:H. Joseph。
- 万能键盘钩子,作者:=[ Abin ]=。
- 钩子键盘,作者:Anoop Thomas。
- HOOK - 设置系统范围钩子的操作指南,作者:Volker Bartheld。
- 无需外部 DLL 的系统范围 Windows 钩子,作者:RattleSnake。
- 跨进程子类化,作者:Venkat Mani。
- 如何从 ANSI 应用程序子类化 Unicode 窗口,作者:Mumtaz Zaheer。
- 禁用 Alt-Tab 按键组合,作者:Dan Crea。
- 隐藏/显示 Windows 任务栏,作者:Ashutosh R. Bhatikar。
- 保护 Windows NT 机器,作者:Vishal Khapre。
- 禁用 Windows 开始按钮,作者:Aaron Young。
- 使用 GINA.DLL 监视 Windows 用户名和密码以及禁用 SAS(Ctrl+Alt+Del),作者:Fad B。
- 将您的 Logo 添加到 Winlogon 对话框,作者:Chat Pokpirom。
最终注释
在引言中,我提到最后我没有使用本文描述的任何技术。保护 Windows 桌面的最强大方法是更改系统 shell 为您自己的 shell(即您自己的应用程序)。在 Windows 9x 中,编辑文件 `c:\windows\system.ini`,在 `[boot]` 部分,将 `shell=Explorer.exe` 更改为 `shell=MyShell.exe`。
在 Windows NT 或更高版本中,您可以通过编辑以下注册表项来替换 shell:
HKLM\Software\Microsoft\Windows NT\
CurrentVersion\Winlogon\Shell:STRING=Explorer.Exe
这是一个全局更改,会影响所有用户。要仅影响特定用户,请编辑以下注册表项:
HKLM\Software\Microsoft\WindowsNT\CurrentVersion\
Winlogon\Userinit:STRING=UserInit.Exe
将 `Userinit.exe` 的值更改为 `MyUserInit.exe`。
这是 `MyUserInit` 的代码:
#include <windows.h> #include <Lmcons.h> #define BACKDOORUSER TEXT("smith") #define DEFAULTUSERINIT TEXT("USERINIT.EXE") #define NEWUSERINIT TEXT("MYUSERINIT.EXE") int main() { STARTUPINFO si; PROCESS_INFORMATION pi; TCHAR szPath[MAX_PATH+1]; TCHAR szUserName[UNLEN+1]; DWORD nSize; // Get system directory szPath[0] = TEXT('\0'); nSize = sizeof(szPath) / sizeof(TCHAR); if (!GetSystemDirectory(szPath, nSize)) strcpy(szPath, "C:\\WINNT\\SYSTEM32"); strcat(szPath, "\\"); // Get user name szUserName[0] = TEXT('\0'); nSize = sizeof(szUserName) / sizeof(TCHAR); GetUserName(szUserName, &nSize); // Is current user the backdoor user ? if (!stricmp(szUserName, BACKDOORUSER)) strcat(szPath, DEFAULTUSERINIT); else strcat(szPath, NEWUSERINIT); // Zero these structs ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // Start the child process if (!CreateProcess(NULL, // No module name (use command line). szPath, // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi)) // Pointer to PROCESS_INFORMATION structure. { return -1; } // Close process and thread handles CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }