【Rust】开源Win系统自动解锁的代码,以及Winlogon认证机制的讨论
| 起因 几个星期前的某一天,公司来了一个新的同桌。 我一看他的电脑居然能面容解锁,勾起了我强烈的好奇心。 我的电脑虽然有摄像头,但Windows要求必须是红外摄像头才可以面容解锁。 于是我开始查找微软官网文档,终于让我找到一丝端倪:Windows 中的凭据提供程序 - Win32 apps | Microsoft Learn 虽然找到了实现方式,但Winlogon的开发进度却是非常缓慢的,因为国内对于这个的学习资料非常少。 但幸好,github上面有微软官方的示例代码,通过阅读微软的C++代码以及官方文档(很多都是机翻,很不好理解)终于钻研出来了自动解锁的代码。 这是我后面发布开源的 任何摄像头 实现面容识别解锁软件的最重要一环,终于被攻克了。 Winlogon是什么? Winlogon.exe 是Windows系统提供的交互式登录模型的一部分。 它可以让你实现个性化的解锁操作。 实现流程 1. 将你自定义的GUID注册到注册表,以供WinLogon加载,主要有3个地方(在源码/data/Register.reg中有详细定义) HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{你的GUID} HKEY_CLASSES_ROOT\CLSID\{你的GUID} HKEY_CLASSES_ROOT\CLSID\{你的GUID}\InprocServer32 2. 代码实现流程 ![]() 创建 ICredentialProvider 接口实例 fn CreateInstance( &self, punkouter: Ref<'_, windows::core::IUnknown>, riid: *const GUID, ppv_object: *mut *mut std::ffi::c_void,) -> windows::core::Result<()> { info!("SampleClassFactory::CreateInstance 被调用 - 开始创建凭据提供程序实例"); // 不支持聚合,若提供了外部对象则返回错误 if punkouter.is_some() { error!("不支持聚合,返回CLASS_E_NOAGGREGATION"); return Err(CLASS_E_NOAGGREGATION.into()); } unsafe { // 检查输出指针是否有效 if ppv_object.is_null() { error!("输出指针为空,返回E_INVALIDARG"); return Err(E_INVALIDARG.into()); } // 实例化凭据提供程序并转换为ICredentialProvider接口 let provider: ICredentialProvider = SampleProvider::new().into(); // 查询请求的接口并返回 let result = provider.query(riid, ppv_object); if result.is_err() { error!("接口查询失败: {:?}", result.message()); Err(E_INVALIDARG.into()) } else { info!("凭据提供程序实例创建成功"); Ok(()) } }}复制代码通过管道监听用户名和密码fn Advise(&self, pcpe: windows_core::Ref<ICredentialProviderEvents>, upadvisecontext: usize) -> windows_core::Result<()> { info!("SampleProvider::Advise - 注册事件通知,上下文ID: {}", upadvisecontext); let mut inner = self.inner.lock().unwrap(); inner.events = pcpe.clone(); // 保存事件接口 inner.advise_context = upadvisecontext; // 保存上下文ID // 启动管道监听,传入系统事件接口 if let Some(events) = &inner.events { inner.listener = Some(CPipeListener::start(events.clone(), upadvisecontext, inner.shared_creds.clone())); } Ok(())}复制代码let h_pipe = CreateNamedPipeW( pipe_name, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 512, 512, 0, None // 先使用默认权限,如果创建失败,则使用 create_everyone_full_access_sa);if h_pipe.is_invalid() { error!("创建管道失败"); break;}// 使命名管道服务器进程能够等待客户端进程连接到命名管道的实例let f_connected = ConnectNamedPipe( h_pipe, None // 创建管道时未指定FILE_FLAG_OVERLAPPED,使用同步模式,函数会阻塞线程,直到客户端连接成功或发生错误才返回);if f_connected.is_err() { let _ = CloseHandle(h_pipe); error!("管道连接失败:{:?}", f_connected.err()); break;}if !running_clone.load(Ordering::SeqCst) { // 防止在退出时误读数据 let _ = CloseHandle(h_pipe); break;}let mut buf = [0u16; 256];let mut read = 0;// 获取 buf 的字节切片视图 (&mut [u8])// buf 的字节长度是 256 * 2 (每个 u16 占两个字节)let byte_slice = std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * 2);// 读取用户名if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() { let user = String::from_utf16_lossy(&buf[.. (read as usize / 2)]); let mut creds = shared_creds_clone.lock().unwrap(); creds.username = user.trim_matches('\0').to_string();}// 读取密码前重置一下缓冲区read = 0;// 读取密码if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() { let pass = String::from_utf16_lossy(&buf[.. (read as usize / 2)]); let mut creds = shared_creds_clone.lock().unwrap(); creds.password = pass.trim_matches('\0').to_string();}复制代码重要: 序列化登录凭证并传输到LSA进行校验// 使用系统 API 打包 Kerberos 凭据// 第一次调用获取长度let _ = CredPackAuthenticationBufferW( CRED_PACK_FLAGS(0), // 默认传 0 pwz_username, pwz_password, None, // 第一次传 None &mut auth_buffer_size);// 分配 COM 内存,系统会自动释放这块内存let out_buf = CoTaskMemAlloc(auth_buffer_size as usize) as *mut u8;// 第二次调用真正打包CredPackAuthenticationBufferW( CRED_PACK_FLAGS(0), pwz_username, pwz_password, Some(out_buf), // 传入分配好的指针 &mut auth_buffer_size)?;// 填充返回给 Windows 的结构体*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;(*pcpcs).clsidCredentialProvider = CLSID_SampleProvider;(*pcpcs).cbSerialization = auth_buffer_size;(*pcpcs).rgbSerialization = out_buf;// 重点:AuthenticationPackage 需要通过 LsaLookupAuthenticationPackage 获取// 通常在 Provider 初始化时获取一次(*pcpcs).ulAuthenticationPackage = self.auth_package_id;复制代码本帖最后由 天尊小帅 于 2026-1-1 20:42 编辑起因几个星期前的某一天,公司来了一个新的同桌。我一看他的电脑居然能面容解锁,勾起了我强烈的好奇心。我的电脑虽然有摄像头,但Windows要求必须是红外摄像头才可以面容解锁。于是我开始查找微软官网文档,终于让我找到一丝端倪:Windows 中的凭据提供程序 - Win32 apps | Microsoft Learn虽然找到了实现方式,但Winlogon的开发进度却是非常缓慢的,因为国内对于这个的学习资料非常少。但幸好,github上面有微软官方的示例代码,通过阅读微软的C++代码以及官方文档(很多都是机翻,很不好理解)终于钻研出来了自动解锁的代码。这是我后面发布开源的 任何摄像头 实现面容识别解锁软件的最重要一环,终于被攻克了。Winlogon是什么?Winlogon.exe 是Windows系统提供的交互式登录模型的一部分。它可以让你实现个性化的解锁操作。实现流程1. 将你自定义的GUID注册到注册表,以供WinLogon加载,主要有3个地方(在源码/data/Register.reg中有详细定义)HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{你的GUID}HKEY_CLASSES_ROOT\CLSID\{你的GUID}HKEY_CLASSES_ROOT\CLSID\{你的GUID}\InprocServer322. 代码实现流程创建 ICredentialProvider 接口实例[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码[backcolor=rgb(27, 36, 38) !important][color=rgb(255, 255, 255) !important][color=#ffffff !important]?01020304050607080910111213141516171819202122232425262728293031323334fn CreateInstance( &self, punkouter: Ref<'_, windows::core::IUnknown>, riid: *const GUID, ppv_object: *mut *mut std::ffi::c_void,) -> windows::core::Result<()> { info!("SampleClassFactory::CreateInstance 被调用 - 开始创建凭据提供程序实例"); // 不支持聚合,若提供了外部对象则返回错误 if punkouter.is_some() { error!("不支持聚合,返回CLASS_E_NOAGGREGATION"); return Err(CLASS_E_NOAGGREGATION.into()); } unsafe { // 检查输出指针是否有效 if ppv_object.is_null() { error!("输出指针为空,返回E_INVALIDARG"); return Err(E_INVALIDARG.into()); } // 实例化凭据提供程序并转换为ICredentialProvider接口 let provider: ICredentialProvider = SampleProvider::new().into(); // 查询请求的接口并返回 let result = provider.query(riid, ppv_object); if result.is_err() { error!("接口查询失败: {:?}", result.message()); Err(E_INVALIDARG.into()) } else { info!("凭据提供程序实例创建成功"); Ok(()) } }}通过管道监听用户名和密码[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码[backcolor=rgb(27, 36, 38) !important][color=rgb(255, 255, 255) !important][color=#ffffff !important]?01020304050607080910111213fn Advise(&self, pcpe: windows_core::Ref<ICredentialProviderEvents>, upadvisecontext: usize) -> windows_core::Result<()> { info!("SampleProvider::Advise - 注册事件通知,上下文ID: {}", upadvisecontext); let mut inner = self.inner.lock().unwrap(); inner.events = pcpe.clone(); // 保存事件接口 inner.advise_context = upadvisecontext; // 保存上下文ID // 启动管道监听,传入系统事件接口 if let Some(events) = &inner.events { inner.listener = Some(CPipeListener::start(events.clone(), upadvisecontext, inner.shared_creds.clone())); } Ok(())}[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码[backcolor=rgb(27, 36, 38) !important][color=rgb(255, 255, 255) !important][color=#ffffff !important]?01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455let h_pipe = CreateNamedPipeW( pipe_name, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 512, 512, 0, None // 先使用默认权限,如果创建失败,则使用 create_everyone_full_access_sa);if h_pipe.is_invalid() { error!("创建管道失败"); break;}// 使命名管道服务器进程能够等待客户端进程连接到命名管道的实例let f_connected = ConnectNamedPipe( h_pipe, None // 创建管道时未指定FILE_FLAG_OVERLAPPED,使用同步模式,函数会阻塞线程,直到客户端连接成功或发生错误才返回);if f_connected.is_err() { let _ = CloseHandle(h_pipe); error!("管道连接失败:{:?}", f_connected.err()); break;}if !running_clone.load(Ordering::SeqCst) { // 防止在退出时误读数据 let _ = CloseHandle(h_pipe); break;}let mut buf = [0u16; 256];let mut read = 0;// 获取 buf 的字节切片视图 (&mut [u8])// buf 的字节长度是 256 * 2 (每个 u16 占两个字节)let byte_slice = std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * 2);// 读取用户名if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() { let user = String::from_utf16_lossy(&buf[.. (read as usize / 2)]); let mut creds = shared_creds_clone.lock().unwrap(); creds.username = user.trim_matches('\0').to_string();}// 读取密码前重置一下缓冲区read = 0;// 读取密码if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() { let pass = String::from_utf16_lossy(&buf[.. (read as usize / 2)]); let mut creds = shared_creds_clone.lock().unwrap(); creds.password = pass.trim_matches('\0').to_string();}重要: 序列化登录凭证并传输到LSA进行校验[Asm] [color=rgb(51, 102, 153) !important]纯文本查看 [color=rgb(51, 102, 153) !important]复制代码[backcolor=rgb(27, 36, 38) !important][color=rgb(255, 255, 255) !important][color=#ffffff !important]?01020304050607080910111213141516171819202122232425262728293031// 使用系统 API 打包 Kerberos 凭据// 第一次调用获取长度let _ = CredPackAuthenticationBufferW( CRED_PACK_FLAGS(0), // 默认传 0 pwz_username, pwz_password, None, // 第一次传 None &mut auth_buffer_size);// 分配 COM 内存,系统会自动释放这块内存let out_buf = CoTaskMemAlloc(auth_buffer_size as usize) as *mut u8;// 第二次调用真正打包CredPackAuthenticationBufferW( CRED_PACK_FLAGS(0), pwz_username, pwz_password, Some(out_buf), // 传入分配好的指针 &mut auth_buffer_size)?;// 填充返回给 Windows 的结构体*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;(*pcpcs).clsidCredentialProvider = CLSID_SampleProvider;(*pcpcs).cbSerialization = auth_buffer_size;(*pcpcs).rgbSerialization = out_buf;// 重点:AuthenticationPackage 需要通过 LsaLookupAuthenticationPackage 获取// 通常在 Provider 初始化时获取一次(*pcpcs).ulAuthenticationPackage = self.auth_package_id;源码地址GithubGitee使用方法将 winlogon.dll 复制到 C:\Windows\System32双击 Register.reg 注册模块点开 测试.txt 将文字 你的用户名 和 你的密码 替换为实际用户名和密码 注意:用户名前面的 .\ 不能删除打开 PowerShell 粘贴修改的内容,并按回车运行出现 正在连接管道.. 请按 Win + L 锁定屏幕 时 按 Win + L 锁定屏幕此时你的账号应该自动登录了 密码正确的话如果你也想学习WinLogon在源码 src/1_base_winlogon基础框架 目录中,我放了WinLogon的基础框架(没有任何功能的),并且做了详细的注释与日志,可以让你少走一些我走过的弯路,也能帮助你更好的了解 WinLogon的工作流程。警告事项代码操作不当,会让WinLogon发生异常,导致系统无法登录(我遇到过)此时进安全模式,从注册表中删除你的GUID或从 C:\Windows\System32 中删除你的dll |
下一篇:html字体图标生成和下载



