返回列表 发布新帖

【Rust】开源Win系统自动解锁的代码,以及Winlogon认证机制的讨论。

2415 0
邓小K 发表于 3 天前 | 查看全部 阅读模式 <

马上注册,结交更多好友,享用更多功能,让你轻松玩转小K网。

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

×
起因
几个星期前的某一天,公司来了一个新的同桌。
我一看他的电脑居然能面容解锁,勾起了我强烈的好奇心。
我的电脑虽然有摄像头,但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. 代码实现流程

【Rust】开源Win系统自动解锁的代码,以及Winlogon认证机制的讨论。

【Rust】开源Win系统自动解锁的代码,以及Winlogon认证机制的讨论。

创建 ICredentialProvider 接口实例
  1. fn CreateInstance(
  2.     &self,
  3.     punkouter: Ref<'_, windows::core::IUnknown>,
  4.     riid: *const GUID,
  5.     ppv_object: *mut *mut std::ffi::c_void,
  6. ) -> windows::core::Result<()> {
  7.     info!("SampleClassFactory::CreateInstance 被调用 - 开始创建凭据提供程序实例");
  8.      
  9.     // 不支持聚合,若提供了外部对象则返回错误
  10.     if punkouter.is_some() {
  11.         error!("不支持聚合,返回CLASS_E_NOAGGREGATION");
  12.         return Err(CLASS_E_NOAGGREGATION.into());
  13.     }
  14.     unsafe {
  15.         // 检查输出指针是否有效
  16.         if ppv_object.is_null() {
  17.             error!("输出指针为空,返回E_INVALIDARG");
  18.             return Err(E_INVALIDARG.into());
  19.         }
  20.          
  21.         // 实例化凭据提供程序并转换为ICredentialProvider接口
  22.         let provider: ICredentialProvider = SampleProvider::new().into();
  23.         // 查询请求的接口并返回
  24.         let result = provider.query(riid, ppv_object);
  25.         if result.is_err() {
  26.             error!("接口查询失败: {:?}", result.message());
  27.             Err(E_INVALIDARG.into())
  28.         } else {
  29.             info!("凭据提供程序实例创建成功");
  30.             Ok(())
  31.         }
  32.     }
  33. }
复制代码
通过管道监听用户名和密码
  1. fn Advise(&self, pcpe: windows_core::Ref<ICredentialProviderEvents>, upadvisecontext: usize) -> windows_core::Result<()> {
  2.     info!("SampleProvider::Advise - 注册事件通知,上下文ID: {}", upadvisecontext);
  3.     let mut inner = self.inner.lock().unwrap();
  4.     inner.events = pcpe.clone(); // 保存事件接口
  5.     inner.advise_context = upadvisecontext; // 保存上下文ID
  6.     // 启动管道监听,传入系统事件接口
  7.     if let Some(events) = &inner.events {
  8.         inner.listener = Some(CPipeListener::start(events.clone(), upadvisecontext, inner.shared_creds.clone()));
  9.     }
  10.     Ok(())
  11. }
复制代码
  1. let h_pipe = CreateNamedPipeW(
  2.     pipe_name,
  3.     PIPE_ACCESS_INBOUND,
  4.     PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
  5.     PIPE_UNLIMITED_INSTANCES,
  6.     512, 512, 0,
  7.     None // 先使用默认权限,如果创建失败,则使用 create_everyone_full_access_sa
  8. );
  9. if h_pipe.is_invalid() {
  10.     error!("创建管道失败");
  11.     break;
  12. }
  13. // 使命名管道服务器进程能够等待客户端进程连接到命名管道的实例
  14. let f_connected = ConnectNamedPipe(
  15.     h_pipe,
  16.     None // 创建管道时未指定FILE_FLAG_OVERLAPPED,使用同步模式,函数会阻塞线程,直到客户端连接成功或发生错误才返回
  17. );
  18. if f_connected.is_err() {
  19.     let _ = CloseHandle(h_pipe);
  20.     error!("管道连接失败:{:?}", f_connected.err());
  21.     break;
  22. }
  23. if !running_clone.load(Ordering::SeqCst) {
  24.     // 防止在退出时误读数据
  25.     let _ = CloseHandle(h_pipe);
  26.     break;
  27. }
  28. let mut buf = [0u16; 256];
  29. let mut read = 0;
  30. // 获取 buf 的字节切片视图 (&mut [u8])
  31. // buf 的字节长度是 256 * 2 (每个 u16 占两个字节)
  32. let byte_slice = std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * 2);
  33. // 读取用户名
  34. if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() {
  35.     let user = String::from_utf16_lossy(&buf[.. (read as usize / 2)]);
  36.     let mut creds = shared_creds_clone.lock().unwrap();
  37.     creds.username = user.trim_matches('\0').to_string();
  38. }
  39. // 读取密码前重置一下缓冲区
  40. read = 0;
  41. // 读取密码
  42. if ReadFile(h_pipe, Some(byte_slice), Some(&mut read), None).is_ok() {
  43.     let pass = String::from_utf16_lossy(&buf[.. (read as usize / 2)]);
  44.     let mut creds = shared_creds_clone.lock().unwrap();
  45.     creds.password = pass.trim_matches('\0').to_string();
  46. }
复制代码
重要: 序列化登录凭证并传输到LSA进行校验
  1. // 使用系统 API 打包 Kerberos 凭据
  2. // 第一次调用获取长度
  3. let _ = CredPackAuthenticationBufferW(
  4.     CRED_PACK_FLAGS(0), // 默认传 0
  5.     pwz_username,
  6.     pwz_password,
  7.     None, // 第一次传 None
  8.     &mut auth_buffer_size
  9. );
  10. // 分配 COM 内存,系统会自动释放这块内存
  11. let out_buf = CoTaskMemAlloc(auth_buffer_size as usize) as *mut u8;
  12. // 第二次调用真正打包
  13. CredPackAuthenticationBufferW(
  14.     CRED_PACK_FLAGS(0),
  15.     pwz_username,
  16.     pwz_password,
  17.     Some(out_buf), // 传入分配好的指针
  18.     &mut auth_buffer_size
  19. )?;
  20. // 填充返回给 Windows 的结构体
  21. *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
  22. (*pcpcs).clsidCredentialProvider = CLSID_SampleProvider;
  23. (*pcpcs).cbSerialization = auth_buffer_size;
  24. (*pcpcs).rgbSerialization = out_buf;
  25. // 重点:AuthenticationPackage 需要通过 LsaLookupAuthenticationPackage 获取
  26. // 通常在 Provider 初始化时获取一次
  27. (*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}\InprocServer32

2. 代码实现流程

                               
登录/注册后可看大图

创建 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]?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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(())
        }
    }
}





通过管道监听用户名和密码
[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]?

01

02

03

04

05

06

07

08

09

10

11

12

13

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(())
}





[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]?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

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进行校验
[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]?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

// 使用系统 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;





源码地址
Github
Gitee
使用方法
  • 将 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


请大家文明发帖,遵守法律法规!站长QQ460551

回复

您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

您需要 登录 后才可以回复,轻松玩转社区,没有帐号?立即注册
快速回复
关灯 在本版发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表