65.9K
CodeProject 正在变化。 阅读更多。
Home

锁定 Windows 桌面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (155投票s)

2004 年 6 月 14 日

9分钟阅读

viewsIcon

1544246

downloadIcon

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

禁用此按键组合有几种方法:

  1. 禁用任务管理器。这不会捕获按键组合,它只是禁用了按下此按键组合时弹出的应用程序(任务管理器)。请参阅下文如何执行此操作。
  2. 使用键盘设备驱动程序捕获按键。为此,您需要安装 DDK。我在这里不描述这种方法。
  3. 编写一个 GINA 存根。GINA 是 Winlogon 用来执行用户身份验证的 DLL。我不会在这里讨论这种方法,但您可以在这里找到如何操作 [16]
  4. 子类化 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()` 调用。

将内存映射到远程进程地址空间的技术称为注入。

注入可以通过以下方式实现:

  1. 注册表。要将 DLL 注入到进程中,只需将 DLL 名称添加到注册表项中。
    HKLM\Software\Microsoft\Windows NT\
      CurrentVersion\Windows\AppInit_DLLs:STRING

    此方法仅在 Windows NT 或更高版本上支持。

  2. 使用钩子注入 DLL。此方法支持所有 Windows 版本。
  3. 使用 `CreateRemoteThread()`/`LoadLibrary()` 注入 DLL。仅在 Windows NT 或更高版本上可用。
  4. 使用 `WriteProcessMemory()` 将代码直接复制到远程进程,并通过调用 `CreateRemoteThread()` 来启动其执行。仅在 Windows NT 或更高版本上可用。

我更偏爱的注入技术是最后一种。它的优点是不需要外部 DLL。为了正确工作,此方法需要对注入到远程进程中的函数进行非常仔细的编码。为了帮助您避免此技术的常见陷阱,我在源代码开头插入了一些提示。对于那些认为此方法过于危险的人,我还包含了 `CreateRemoteThread()`/`LoadLibrary()` 方法。只需将 `#define DLL_INJECT `,应用程序就会改用此方法。

将代码注入 Winlogon 的子类化后,SAS 窗口也会缩小。

  1. 获取 SAS 窗口句柄
    hSASWnd = FindWindow("SAS Window class", "SAS window");
  2. 子类化 SAS 窗口过程
    SetWindowLong(hSASWnd, GWL_WNDPROC, NewSASWindowProc);
  3. 在 `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` 成员必须设置为桌面的名称。

参考文献

  1. 通过 Paul DiLascia 的 Trapkeys 禁用 Windows XP 中的按键.
  2. 捕获 CtrlAltDel;在 Windows 2000/XP 上隐藏任务列表中的应用程序,作者:Jiang Sheng。
  3. 将代码注入另一个进程的三种方法,作者:Robert Kuster。
  4. 钩子和 DLL,作者:Joseph M. Newcomer。
  5. 键盘钩子,作者:H. Joseph。
  6. 万能键盘钩子,作者:=[ Abin ]=。
  7. 钩子键盘,作者:Anoop Thomas。
  8. HOOK - 设置系统范围钩子的操作指南,作者:Volker Bartheld。
  9. 无需外部 DLL 的系统范围 Windows 钩子,作者:RattleSnake。
  10. 跨进程子类化,作者:Venkat Mani。
  11. 如何从 ANSI 应用程序子类化 Unicode 窗口,作者:Mumtaz Zaheer。
  12. 禁用 Alt-Tab 按键组合,作者:Dan Crea。
  13. 隐藏/显示 Windows 任务栏,作者:Ashutosh R. Bhatikar。
  14. 保护 Windows NT 机器,作者:Vishal Khapre。
  15. 禁用 Windows 开始按钮,作者:Aaron Young。
  16. 使用 GINA.DLL 监视 Windows 用户名和密码以及禁用 SAS(Ctrl+Alt+Del),作者:Fad B。
  17. 将您的 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;
}
© . All rights reserved.