首页
社区
课程
招聘
[原创]Winlogon 登录相关注册表值与 Notify 子键解析
发表于: 2026-7-20 10:49 3391

[原创]Winlogon 登录相关注册表值与 Notify 子键解析

2026-7-20 10:49
3391

所有内容均在KswordARK中实现,开源地址f3dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2f1%4N6G2M7X3c8p5c8g2k6Q4x3V1k6w2f1%4N6G2M7X3b7`.
图片描述

本节要完成的目标是完整读取 Winlogon 登录相关的配置,并保留每项数据的键路径、名称、类型、实际长度和读取状态。要达到这个目标,分为四步。

交互式登录由多个 Windows 组件协作完成。用户完成身份验证后,系统建立用户会话、加载用户配置、启动 Userinit,再由 Shell 提供桌面环境。Winlogon 位于这条路径中,并读取部分登录初始化相关配置。

读取从 Winlogon 键开始。固定值、逗号分隔的 Userinit 文本和每个 Notify 子键中的直接值需要按不同规则保存。

Winlogon 配置需要放回交互式登录流程理解。用户在锁屏界面或登录界面完成身份验证后,Windows 为该用户建立会话、加载用户配置单元、启动初始化程序,并启动桌面外壳。Winlogon 是参与这一路径的系统组件之一。它读取部分登录初始化配置,并协调用户会话中的后续组件。

下面的简化关系图只表达主要对象之间的职责,不表示每个 Windows 版本都严格按同一内部顺序调用。版本差异、组策略、凭据提供程序和系统安全功能都会影响具体行为。

ShellUserinit 的值之所以重要,是因为它们分别描述桌面外壳文本和用户初始化命令文本。读取它们只能得到注册表当时保存的原文。实际执行是否成功还受到文件存在性、命令行语义、系统策略、会话状态和版本行为影响。

Winlogon 键的直接值与 Notify 子键属于不同层级。HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon 是一个注册表键。ShellUserinitTaskman 是该键的直接值。Notify 是 Winlogon 下的子键。Notify 内部还可能有多个命名子键,每个子键再保存独立值。

层级差异决定 API 调用顺序。直接值由 RegQueryValueExWRegEnumValueW 读取。子键名称由 RegEnumKeyExW 读取。得到子键名称后还要 RegOpenKeyExW 打开子键,才能访问它自己的值。把一个 Notify 子键名传给值查询函数不会自动进入该子键。

历史固定值需要保留版本边界。GinaDLL 关联早期图形化身份验证架构。现代 Windows 的身份验证扩展机制由 Credential Provider 等组件承担。Notify 也有明显的历史与版本差异。读取结果仍应展示已存在的值或子键,并说明它们的语义取决于系统版本和兼容性设置。

AppSetupVmAppletTaskman 等固定值也不应仅因名称存在就推导当前登录过程一定会使用它们。合理的输出同时包含键路径、值名、注册表类型、原始文本或字节、读取时间和适用系统上下文,方便与后续证据交叉比对。

固定值位于 Winlogon 键的第一层。ShellUserinitTaskmanVmAppletGinaDLLAppSetup 都是值名。它们共享一个父键,读取方式相同,缺失的含义却不同。

Shell 保存用户登录后使用的 Shell 文本。常见内容是 explorer.exe,读取结果应保留原始字符串,不能把它直接当作已经验证过的文件路径。

Userinit 保存用户初始化命令文本。常见内容形如 C:\Windows\System32\userinit.exe,,最后逗号属于原始配置的一部分。拆分后会得到一个命令片段和一个空片段。

TaskmanVmAppletGinaDLLAppSetup 具有版本和兼容性差异。某个值未设置是可见的配置状态,读取程序应继续检查后续值,不能把它报告成父键读取失败。

当前用户位置也可能有 HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon 下的 ShellUserinit。机器级与当前用户值需要分别保存根键和完整键路径,两个同名值不能按名称合并。

Notify 是 Winlogon 下的子键集合。每个一级子键本身是一个配置单元,DLLName 是其中常见的模块值,其它已有值也要逐个保留。仅仅读取 DLLName 会遗漏该子键内实际存在的事件配置值。

完成第一步后,已经知道固定值位于哪个键,也知道 Notify 需要按子键再按值读取。接下来需要打开这些位置,并把权限问题与配置缺失分开记录。

打开注册表键的函数接收根键、相对路径和访问权限,成功时返回一个需要关闭的键句柄。

只读查询需要申请最小权限。固定值只需要 KEY_QUERY_VALUE。枚举 Notify 时再额外申请 KEY_ENUMERATE_SUB_KEYS,这样权限不足会准确落在对应操作上。

固定值读取要区分三种状态。父键不存在时记录“键不存在”。父键存在而 Shell 等值不存在时记录“值未设置”。API 返回访问拒绝或其它错误时才记录错误码。

完成第二步后,已经获得带有查询权限的键句柄,也能准确保留键不存在、值未设置和读取失败这三种状态。接下来需要按 API 返回的真实长度读取数据,避免把字符串或二进制内容截断。

查询注册表值的函数使用字节数描述数据缓冲区。lpcbData 在第一次调用时接收所需字节数,在第二次调用时既提供缓冲区容量又返回实际字节数。

正确读取的关键是先查询字节数,再分配准确容量。REG_SZREG_EXPAND_SZ 的字符内容通常是 UTF-16,字节数仍然不能直接当作字符数使用。

字符串解码需要使用返回长度构造文本。结尾 NUL 出现时可以移除一个。缺少结尾 NUL 时仍只使用已经返回的字符范围,不能交给 wcslen 向缓冲区外继续搜索。

固定数组读取会同时丢失长度校验和类型校验。长度超过数组时,RegQueryValueExW 返回 ERROR_MORE_DATA。忽略返回值后再输出缓冲区,会得到截断文本或未初始化数据。

REG_EXPAND_SZ 的原始文本应先保存。文本中可能出现 %SystemRoot%%ProgramFiles% 等环境变量标记,展开结果取决于当前进程环境,属于另一份派生信息。

完成固定值的长度和类型校验后,已经拿到受边界保护的原始文本。Userinit 还要保留逗号的位置,下一部分继续处理这项文本的内部边界。

Userinit 的每一段都要按原始逗号位置切分。空段、连续逗号和最后逗号能反映原始配置,解析过程不能自动去空白或删除空项。


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

上传的附件:
收藏
免费 2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回