目录
前言
系列文章主要讲解关于挂钩 Winlogon 回调过程,实现对任务管理器、电源操作、DWM 自启动控制、常见系统热键等进行编程拦截。最后,我们指出可以通过我们的方法修改系统登陆或者其他安全页面的界面样式,达到自定义的效果。这里主要涉及 R3 用户层下的挂钩,如果是 R0,有些操作可能更为简单。这是之前编写的一篇文章,一直没有发布,最近得空完善了一下。
【提示】系列文章为了有助于不了解 Winlogon 的读者初步理解我们在完成的事情,主要分为(上)、(中)、(下)三部分 ,链接会在下面列出。(需要哪个部分的请自行跳转 ☆*: .。. o(≧▽≦)o .。.:*☆)
系列文章:
屏蔽系统热键(上)传送门 | ID:133801527 |
屏蔽系统热键(中)传送门 | ID:135907307 |
屏蔽系统热键(下)传送门 | ID:135907201 |
一、关于 Winlogon 过程
Winlogon 是 Windows 操作系统的关键部分。此过程始终在 Windows 的后台运行,并负责一些重要的系统功能。如果没有 Winlogon,Windows 将无法使用。
此过程执行与 Windows 登录过程有关的各种关键任务。例如,当登录时,winlogon.exe 进程负责将用户配置文件加载到注册表中。这使程序可以使用注册表 HKEY_CURRENT_USER 下的键,每个 Windows 用户帐户的键都不同。
当 Winlogon 初始化时,它会向系统注册 Ctrl + Alt + Del 安全警示序列 (SAS) [XP 上,安全序列有 SAS 窗口,在之后的系统上为了安全性取消了该机制,但是快捷键依然注册],然后在 WinSta0 窗口工作站中创建三个桌面。
注册 Ctrl + Alt + Del 会使此初始化成为第一个过程,从而确保没有其他应用程序挂钩该键序列。
WinSta0 是表示物理屏幕、键盘和鼠标的窗口站对象的名称。 Winlogon 在 WinSta0 对象中创建以下桌面。
桌面 | 说明 |
---|---|
Winlogon 安全桌面 (WinLogon 桌面) | 这是 Winlogon 用于交互式标识和身份验证以及其他安全对话框的桌面。 Winlogon 在收到事件通知时会自动切换到此桌面。一般地,Winlogon 会拉起一个名为 LogonUI.exe 的 UI 进程用于交互式登陆。 |
应用程序桌面(Default 桌面) | 每次用户成功登录时,都会为该登录会话创建一个应用程序桌面。 应用程序桌面也称为默认桌面或用户桌面。 此桌面是所有用户活动发生的地方。 应用程序桌面受到保护;只有系统和交互式登录会话才有权访问它。 请注意,只有登录用户的特定实例才能访问桌面。 如果交互式用户使用服务控制器激活进程,该服务应用程序将无权访问应用程序桌面。 |
屏幕保护程序桌面 (Screen-saver 桌面) | 这是屏幕保护程序运行时的活动桌面。 如果用户已登录,则系统和交互式登录会话都有权访问桌面。 否则,只有系统有权访问桌面。这个桌面名叫 Screen-saver 桌面,会在系统登陆、锁屏、欢迎界面等情况下变成活动桌面。 |
备注:通常在登录会话处于暂停状态时(用户不是活动用户),这个用户的桌面会切换到 Disconnected 桌面。
Winlogon 具有以下职责:
- 窗口工作站和桌面保护
Winlogon 设置对窗口工作站和相应桌面的保护,以确保每个桌面都可以正确访问。 通常,这意味着本地系统将拥有对这些对象的完全访问权限,并且以交互方式登录的用户将具有对窗口站对象的读取访问权限和对应用程序桌面对象的完全访问权限。
- 标准 SAS 识别
Winlogon 在 User32 服务器中具有特殊的挂钩,使它可以监视 Ctrl + Alt + Del 安全警示序列 (SAS) 事件。 Winlogon 使此 SAS 事件信息用作其 SAS 或 SAS 的一部分,并启动安全序列的 UI 界面。
- 用户配置文件加载
当用户登录时,其用户配置文件将加载到注册表中。 这样,用户的进程可以使用特殊注册表项 HKEY_CURRENT_USER。 Winlogon 会在成功登录后、激活新登录用户的 shell 之前自动执行此操作。
- 将安全性分配给用户 shell
当用户登录时,Winlogon 负责为该用户创建一个或多个初始进程。 Winlogon 调用 Kernel 函数 CreateProcessAsUser,完成启动进程的任务,如启动 explorer.exe。
- 屏幕保护程序控件
Winlogon 监视键盘和鼠标活动,以确定何时激活屏幕保护程序。 激活屏幕保护程序后,Winlogon 将继续监视键盘和鼠标活动,以确定何时终止屏幕保护程序。 如果屏幕保护程序标记为安全,Winlogon 会将工作站视为已锁定。 当存在鼠标或键盘活动时,Winlogon 会恢复锁定的工作站行为。 如果屏幕保护程序不安全,则任何键盘或鼠标活动将终止屏幕保护程序。
Winlogon 的锁屏过程会启动一个名为 LockScreen 的进程。
- 多个网络提供商支持
Windows 系统上安装的多个网络可以包含在身份验证过程和密码更新操作中。 此包含允许其他网络使用 Winlogon 的安全桌面在正常登录期间一次性收集标识和身份验证信息。
二、通过低级键盘钩子挂钩系统快捷键
2.1 Windows 挂钩
挂钩是系统消息处理机制中的一个重要部分,应用程序可以安装子例程来监视系统中的消息流量,应用程序截获消息、鼠标操作和击键等事件,并在某些类型的消息到达目标窗口过程之前对其进行处理。 截获特定类型的事件的函数称为 挂钩过程。 挂钩过程可以对其接收的每个事件执行操作,然后修改或放弃该事件。
系统支持许多不同类型的挂钩;每种类型都提供对其消息处理机制的不同方面的访问。 例如,应用程序可以使用 WH_MOUSE 挂钩来监视鼠标消息的消息流量。
2.2 挂钩过程
系统为每种类型的挂钩维护单独的挂钩链。挂钩链是指向应用程序定义的特殊回调函数(称为挂钩过程)的指针列表。 当发生与特定类型的挂钩关联的消息时,系统会将消息依次传递给挂钩链中引用的每个挂钩过程。挂钩过程可以执行的操作取决于所涉及的挂钩类型。 某些类型挂钩的挂钩过程只能监视消息;其他人可以修改消息或通过链停止其进度,从而阻止它们到达下一个挂钩过程或目标窗口。
为了利用特定类型的挂钩,微软提供了一个挂钩过程,并使用 SetWindowsHookEx 函数将其安装到与挂钩关联的链中。 挂钩过程必须具有以下语法:
LRESULT CALLBACK HookProc(
int nCode,
WPARAM wParam,
LPARAM lParam
) {
// process event ...
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
nCode 参数是挂钩过程用于确定要执行的操作的挂钩代码。 挂钩代码的值取决于挂钩的类型;每种类型都有自己的特征挂钩代码集。 wParam 和 lParam 参数的值取决于挂钩代码,但它们通常包含有关已发送或发布的消息的信息。
SetWindowsHookEx 函数始终在挂钩链的开头安装挂钩过程。 当发生由特定类型的挂钩监视的事件时,系统会在与挂钩关联的挂钩链的开头调用过程。 链中的每个挂钩过程确定是否将事件传递给下一个过程。 挂钩过程通过调用 CallNextHookEx 函数将事件传递给下一过程。
HHOOK SetWindowsHookExA(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);LRESULT CallNextHookEx(
[in, optional] HHOOK hhk,
[in] int nCode,
[in] WPARAM wParam,
[in] LPARAM lParam
);
全局挂钩监视与调用线程位于同一桌面中的所有线程的消息。 特定于线程的挂钩仅监视单个线程的消息。 全局挂钩过程可以在调用线程所在的桌面中的任何应用程序的上下文中调用,因此该过程必须位于单独的 DLL 模块中。 仅在关联线程的上下文中调用特定于线程的挂钩过程。 如果应用程序为其自己的线程之一安装挂钩过程,则挂钩过程可以位于与应用程序代码的其余部分相同的模块中,也可以位于 DLL 中。 如果应用程序为不同应用程序的线程安装挂钩过程,则该过程必须位于 DLL 中。
2.3 键盘钩子
在这里,我们只需要了解各种类型挂钩中的两种:键盘输入钩子和低级别键盘输入钩子。这两个钩子分别对应于将 SetWindowsHookEx 函数的 idHook 参数置为以下值:
WH_KEYBOARD 2 | 安装用于监视击键消息的挂钩过程。 使用 KeyboardProc 挂钩过程。 |
WH_KEYBOARD_LL 13 | 安装用于监视低级别键盘输入事件的挂钩过程。 使用 LowLevelKeyboardProc 挂钩过程。 |
普通的键盘钩子无法挂钩系统热键,只有使用低级钩子 (Low-Level Hook) 才可以挂勾。但是由于“安全警示序列”受到桌面级挂钩保护,任何 User32 程序都无法通过系统提供的钩子函数挂钩这类热键,即 Ctrl + Alt + Delete 和 Win + L 等按键消息是无法挂钩的。但是有些时候,我们只挂钩一般的系统热键就可以了。下面给出了 LowLevelKeyboardProc 挂钩过程的回调:
LRESULT CALLBACK LowLevelKeyboardProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
if(nCode == HC_ACTION)
{
// process event ...
}
// if nCode < HC_ACTION (0), then CallNextHookEx must be used.
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
其中,wParam 是键盘消息的标识符。可以是以下消息之一: WM_KEYDOWN、 WM_KEYUP、 WM_SYSKEYDOWN 或 WM_SYSKEYUP。lParam 是指向 KBDLLHOOKSTRUCT 结构的指针。
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
- vkCode 参数表示虚拟按键码。 该码必须是 1 到 254 范围内的值。
- scanCode 是按键的硬件扫描码,关于硬件扫描码的码表有人整理出来了,可以参考这篇转载文章:键盘硬件扫描码(邓志)。
- flags 是扩展键标志、事件注入标志、上下文代码和转换状态标志。 下表描述了此值的布局:
Bits | 说明 |
---|---|
0 | 指定键是扩展键,例如功能键还是数字键盘上的键。 如果键是扩展键,则值为 1;否则为 0。 |
1 | 指定事件是否是从在较低完整性级别运行的进程中注入的。 如果出现这种情况,则值为 1;否则为 0。 请注意,每当设置位 1 时,也会设置位 4。 |
2-3 | 保留。 |
4 | 指定是否注入事件。 如果出现这种情况,则值为 1;否则为 0。 请注意,设置第 4 位时不一定设置位 1。 |
5 | 上下文代码。 如果按下 Alt 键,则值为 1;否则为 0。 |
6 | 保留。 |
7 | 转换状态。 如果按下了键,则值为 0;如果释放键,则值为 1。 |
应用程序可以使用以下值来测试击键标志。 测试 LLKHF_INJECTED (位 4) 将告知是否已注入事件。 如果是,则测试 LLKHF_LOWER_IL_INJECTED (位 1) 会告诉你事件是否是从以较低完整性级别运行的进程注入的。
- time 参数返回此消息的时间戳,相当于 GetMessageTime 为此消息返回的时间戳。
- dwExtraInfo 返回与消息关联的其他信息。
有了回调函数,我们只需要在消息链中注入回调即可实现挂钩,用于设置钩子的 SetWindowsHookEx 函数可以这样设置参数:
/* WH_KEYBOARD_LL 表示使用低级键盘钩子回调;
* KeyboardProc 是函数指针,指向回调函数;
* 通过 GetModuleHandle(NULL) 获取挂钩进程的模块句柄
* 这里是当前 DLL 的句柄,其中包含挂钩过程函数。
*/
SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(NULL), 0);
三、屏蔽/通知部分系统热键
3.1 理论分析
上文通过 WindowsHook 的回调我们实现了对系统热键的捕获,但是在回调中我们并没有做任何事情。那么,如何通过回调处理具体的按键信息呢?经查阅资料,可以通过会调和函数中 lParam 返回的结构体指针来分析当前按下的低级别按键信息,并对击键消息做出响应:
// 将 lParam 的地址转换为指向适当结构体的指针
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
我们知道系统热键常常是组合按键,因为这样更安全有区分性,可以尽可能避免热键冲突。我们通过 KBDLLHOOKSTRUCT 结构体的成员 vkCode 确定虚拟密钥,flags 用于确定扩展按键,由于他们的组合有限,对于一些特殊的组合可能无法精确判断,故我们也会结合 GetKeyState 函数。
下表展示了 KBDLLHOOKSTRUCT 结构体的成员:
- vkCode
类型:DWORD
虚拟密钥代码。 代码必须是 1 到 254 范围内的值。
- scanCode
类型:DWORD
密钥的硬件扫描代码。
- flags
类型:DWORD
扩展键标志、事件注入标志、上下文代码和转换状态标志。 此成员指定如下。 应用程序可以使用以下值来测试击键标志。 测试LLKHF_INJECTED (位 4) 将告知是否已注入事件。 如果是,则测试LLKHF_LOWER_IL_INJECTED (位 1) 会告诉你事件是否是从以较低完整性级别运行的进程注入的。
我们在消息查询线程中使用 GetKeyState 函数来获取键盘虚拟按键的状态信息,使用该函数并集合结构体返回的信息,即可做到对组合键的识别。
GetKeyState 函数的定义如下:
SHORT GetKeyState(
[in] int nVirtKey
);
根据定义 GetKeyState 函数的返回值为 SHORT 类型,即短整型。SHORT 型是 16 位有符号的数据类型,如果要查询的键被按下,返回值最高位被置为 1,则这个数表示负数,所以可以取最高位和 0 比较来判断按键是否被击中。下面定义的宏可以实现对按键按下和弹出的判断:
#define IsKeyDown(vk_code) ((GetKeyState(vk_code) & 0x8000) ? 1 : 0)
#define IsKeyUp(vk_code) ((GetKeyState(vk_code) & 0x8000) ? 0 : 1)
系统注册的常见热键有:ALT + TAB、ALT + ESC、CTRL + ESC、CTRL + SHIFT + ESC、Win + L 和 CTRL + ALT + DEL 等。通过如下所示的挂钩过程可以对大多数系统热键进行拦截或者通知:
// 低级键盘钩子回调函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
switch (wParam)
{
case WM_KEYDOWN: case WM_SYSKEYDOWN:
// 将 lParam 的地址转换为指向适当结构体的指针
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
if (p->vkCode == VK_F12)
{
// 实现退出/结束挂钩
MessageBoxW(GetForegroundWindow(), L"I'm in position..", L"Hook LowLevelKey", MB_OK);
if (hHook != NULL)
{
if (!UnhookWindowsHookEx(hHook))
{
SetThreadDesktop(hCurDesk);
CloseHandle(hCurDesk);
OutputDebugString(L"Unhook failed..");
break;
}
OutputDebugString(L"键盘钩子成功取消");
}
return 0;
}
// 屏蔽 ALT+TAB
else if ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 ALT+TAB 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 ALT+ESC
else if ((p->vkCode == VK_ESCAPE) &&
((p->flags & LLKHF_ALTDOWN) != 0) &&
((GetKeyState(VK_SHIFT) & 0x8000) == 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 ALT+ESC 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 CTRL+ESC
else if ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 CTRL+ESC 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 CTRL+SHIFT+ESC
else if ((p->vkCode == VK_ESCAPE) &&
((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&
((GetKeyState(VK_SHIFT) & 0x8000) != 0))
{
return 1;
}
// 屏蔽左右 Win + L 键
else if (((GetKeyState(VK_L) & 0x8000) != 0) && ((GetKeyState(VK_LWIN) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 WIN+L 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 此处无法屏蔽 CTRL+ALT+DEL,但可以拦截到消息
else if ((p->vkCode == VK_DELETE) &&
((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&
((GetKeyState(VK_MENU) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 CTRL+ALT+DEL 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
break;
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
但对于一个低级键盘钩子,他不会加载链接库到所有窗口进程中。如果在一个窗口进程中加载钩子,则可以实现消息过滤。但现在加载的对象是 Winlogon,这个进程不是窗口进程,它的线程也不在系统应用程序所处的 Default 桌面下,因此在这个进程中加载钩子,需要注意以下几点:
1. 在需要注入的 DLL 代码中 DLL_PROCESS_ATTACH 处开启一个新线程,并在该线程中实现启用低级键盘钩子的函数。
2. 由于钩子所在的线程为非窗口的消息处理线程,因此,必须在该线程成功设置钩子以后主动接收并分发收到的消息,否则钩子将不会钩到任何消息:
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
3. 由于该线程创建时默认与 Winlogon 同属一个桌面(Winlogon 桌面),而其它包括 explorer.exe 在内的窗口程序都处在 Default 桌面,在 Windows 中规定程序只能获得针对同一桌面上创建的窗口消息。所以,要让该线程能接收到用户在 Default 桌面下操作所产生的消息,必须在该线程中使用如下代码将它的桌面设置为 Default 桌面:
HDESK hDesk = OpenDesktop("Default",0,FALSE,MAXIMUM_ALLOWED);
SetThreadDesktop(hDesk);
CloseHandle(hDesk);
钩子例程在解决了以上问题之后,能正确设置钩子,处理回调函数。从而实现屏蔽/通知系统热键的状态。
3.2 完整代码
一个完整的利用 SetWindowsHookExW 的低级钩子库代码如下:
#include <windows.h>
#include <stdio.h>
// 键盘钩子过程
HDESK hCurDesk = NULL;
// Dll所创建线程的句柄
HANDLE hThread = NULL;
// Dll所创建线程的ID
DWORD dwThreadId = 0;
// Dll所创建线程的线程函数
DWORD WINAPI ThreadFunc();
// 钩子句柄
HHOOK hHook = NULL;
// 低级键盘钩子回调函数
LRESULT CALLBACK KeyboardProc(int, WPARAM, LPARAM);
#define VK_L 0x4C
BOOL APIENTRY DllMain(HANDLE hMoudle, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, NULL, 0, &dwThreadId);
break;
case DLL_PROCESS_DETACH:
// 卸载低级键盘钩子
if (hHook != NULL)
{
if (!UnhookWindowsHookEx(hHook))
{
SetThreadDesktop(hCurDesk);
CloseHandle(hCurDesk);
OutputDebugString(L"Unhook failed..");
break;
}
OutputDebugString(L"键盘钩子成功取消");
}
TerminateThread(hThread, 1);
CloseHandle(hThread);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
// Dll所创建线程的线程函数
DWORD WINAPI ThreadFunc()
{
hCurDesk = GetThreadDesktop(GetCurrentThreadId());
HDESK hUserDesk = NULL;
// 同一桌面上进程之间只能发送窗口消息。无法跨进程与其他桌面发送它们。
// 同样,Windows 消息是限制应用程序定义挂钩。
// 特定桌面中运行的进程挂钩过程将〈〈只获得针对同一桌面上创建窗口消息。〉〉
// 所以,这里必须设置钩子所在线程的桌面为Default桌面
// 才能使得钩子所在线程能接收到 Default 桌面的消息
hUserDesk = OpenDesktopW(L"Default", 0, FALSE, MAXIMUM_ALLOWED);
SetThreadDesktop(hUserDesk);
CloseHandle(hUserDesk);
// 设置低级键盘钩子,屏蔽非SAS window的热键
// 需要 #define _WIN32_WINNT 0x0500
hHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(NULL), 0);
if (hHook == NULL)
{
OutputDebugString(L"Set hook failed..");
return 1;
}
OutputDebugString(L"键盘钩子成功设置");
// 在非 GUI 线程中使用消息钩子必须主动接收并分发收到的消息
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 1;
}
// 低级键盘钩子回调函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
switch (wParam)
{
case WM_KEYDOWN: case WM_SYSKEYDOWN:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
if (p->vkCode == VK_F12)
{
// 实现模拟按键代码
MessageBoxW(GetForegroundWindow(), L"I'm in position..", L"Hook LowLevelKey", MB_OK);
if (hHook != NULL)
{
if (!UnhookWindowsHookEx(hHook))
{
SetThreadDesktop(hCurDesk);
CloseHandle(hCurDesk);
OutputDebugString(L"Unhook failed..");
break;
}
OutputDebugString(L"键盘钩子成功取消");
}
return 0;
}
// 屏蔽 ALT+TAB
else if ((p->vkCode == VK_TAB) && ((p->flags & LLKHF_ALTDOWN) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 ALT+TAB 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 ALT+ESC
else if ((p->vkCode == VK_ESCAPE) &&
((p->flags & LLKHF_ALTDOWN) != 0) &&
((GetKeyState(VK_SHIFT) & 0x8000) == 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 ALT+ESC 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 CTRL+ESC
else if ((p->vkCode == VK_ESCAPE) && ((GetKeyState(VK_CONTROL) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 CTRL+ESC 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 屏蔽 CTRL+SHIFT+ESC
else if ((p->vkCode == VK_ESCAPE) &&
((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&
((GetKeyState(VK_SHIFT) & 0x8000) != 0))
{
return 1;
}
// 屏蔽左右 Win + L 键
else if (((GetKeyState(VK_L) & 0x8000) != 0) && ((GetKeyState(VK_LWIN) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 WIN+L 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
// 此处无法屏蔽 CTRL+ALT+DEL,但可以拦截到消息
else if ((p->vkCode == VK_DELETE) &&
((GetKeyState(VK_CONTROL) & 0x8000) != 0) &&
((GetKeyState(VK_MENU) & 0x8000) != 0))
{
MessageBoxW(GetForegroundWindow(), L"已经拦截到 CTRL+ALT+DEL 键", L"Hook LowLevelKey", MB_OK);
return 1;
}
break;
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
以上的钩子,只能拦截一般的系统热键,而且可能会失败。尤其是不能拦截 Ctrl + Alt + Delete 组合键。在 Windows XP 系统上,可以通过远程注入 DLL 到 winlogon.exe 进程,修改 Winlogon 桌面下的 SAS 窗口的回调函数,从而捕获该窗口的 WM_HOTKEY 消息,可以实现屏蔽 Ctrl + Alt + Delete。这个方法也有缺陷,那就是除了 Ctrl + Alt + Delete 外,大多数的其它系统热键,(包括Alt + Tab,Ctrl + Esc及左右 WIN 键)都无法屏蔽。一般地,我们会混合使用前面两种钩子实现在 WinXP 上的系统热键过滤。但是在 Vista 上就取消了 SAS 界面以及 GINA 组件,所以过滤 Ctrl + Alt + Delete 组合键就成为了难题。这一点我们在后面会解决。
后记
本文被拆解为上中下三部分,这是系列的第一部分。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_59075481/article/details/133801527
文章发布于:2023.10.24,文章更新于:2024.01.29