Session Manager 的 BootExecute、SetupExecute 与 Execute 多字符串解析 Session Manager 的 BootExecute、SetupExecute 与 Execute 多字符串解析本程序的目标是只读记录 Session Manager会话管理器的BootExecute、SetupExecute与Execute配置并准确说明每一项的类型、原始条目和读取状态。会话管理器是 Windows 在用户登录前启动的系统组件负责建立早期会话环境。三项值位于HKLM\System\CurrentControlSet\Control\Session Manager都应按REG_MULTI_SZ处理。REG_MULTI_SZ是注册表保存多段 UTF-16 文本的值类型每段文本和整个列表都使用 NUL 终止字符标记边界。要完成这个目标分为四步定位 Session Manager 键并确认三项值各自的身份。按 API 返回的字节长度读取每项原始数据。验证REG_MULTI_SZ的 UTF-16 边界再按原顺序解析条目。区分空列表、缺失值、终止异常和读取错误并保留观察结果。这四步连接了注册表配置、Windows APIWindows Application Programming InterfaceWindows 应用程序编程接口和最终记录。注册表键提供配置来源RegOpenKeyExW提供可查询的键句柄RegQueryValueExW返回类型和字节范围解析器在该范围内恢复条目最后输出每项值的状态。Session Manager 键 - 三项值名 - 原始类型和字节 - 多字符串条目 - 值级状态记录一、对象关系和完整流程会话管理器子系统Session Manager Subsystem在用户登录前参与系统初始化通常由smss.exe承载。BootExecute、SetupExecute与Execute是 Session Manager 键下三份独立的REG_MULTI_SZ配置。每份配置都应保留自己的条目顺序、类型、终止状态和读取结果。固件 - Windows Boot Manager - 内核与驱动初始化 - Session Manager 初始化会话与子系统 - 按启动阶段处理相关配置 - Winlogon 进入用户登录流程枚举程序只读取三项配置。它按返回字节长度验证REG_MULTI_SZ然后保留原始条目、空列表、缺失值和双 NUL 终止状态。定位键和值名 - 读取类型和字节长度 - 验证 UTF-16 边界并解析条目 - 记录值级状态1. Session Manager 参与用户会话出现之前的系统初始化这三个值位于用户登录前的启动阶段。Windows 从固件、Windows Boot Manager、内核加载到用户登录之间经过多个阶段。内核完成基本初始化后会启动会话管理器子系统Session Manager Subsystem常由smss.exe承载来建立会话、准备子系统环境并处理早期启动相关配置。HKLM\System\CurrentControlSet\Control\Session Manager保存的是会话管理器相关配置。CurrentControlSet是系统当前使用的控制集别名系统会将它映射到某个实际的ControlSet00x。读取时使用CurrentControlSet能对应本次启动选择的控制集。仅比较另一个ControlSet00x的同名值不能代替当前配置的结果。这类值出现在早期系统阶段读取时更应保留原始格式。条目文本可以包含由会话管理器或其调用组件解释的参数不能直接按普通桌面程序命令行的规则拆成“第一个空格前是路径”。2. 三个值是独立的命令列表不共享状态BootExecute、SetupExecute与Execute要分别记录。BootExecute常与启动检查阶段相关常见条目为autocheck autochk *。SetupExecute面向安装或设置场景。Execute是同一配置键下的另一份列表。它们共享父键但系统不一定在相同条件下处理每一项。每个值至少有四种读取状态键不存在或无法打开、值不存在、值存在且为空列表、值存在且包含条目。值存在但类型不是REG_MULTI_SZ还应作为类型异常单独报告。把所有“没有条目”的情况合并为一个空字符串会丢失配置和读取失败之间的重要区别。3. REG_MULTI_SZ 是有顺序的 UTF-16 字符串数组多字符串不是普通字符串。REG_MULTI_SZ的数据由多段 UTF-16 文本串接而成。每段以单个 NUL 结束列表以额外 NUL 结束。条目顺序就是 API 返回缓冲区中的顺序应按原顺序输出。一个条目的列表 autocheck autochk * \0 \0 两个条目的列表 first \0 second \0 \0 合法空列表 \0 \0每个\0代表一个 UTF-16 NUL 宽字符而非字面反斜杠和零。RegQueryValueExW的长度参数仍以字节为单位因此程序先用字节数分配缓冲区再通过字节数 / sizeof(wchar_t)得到可检查的字符数量。读取长度为奇数、缺少双 NUL 或在第二次查询中扩容的情况都应保留为格式或读取状态。4. 配置值与实际执行是两类观察结果配置值与实际执行需要分开。注册表中出现BootExecute条目只证明读取时配置包含该文本。条目是否在某次启动中被处理、具体工具是否运行成功、是否因为磁盘状态或安装阶段被跳过需要启动日志、事件记录或独立运行时证据。读取程序可以安全地报告完整键路径、值名、类型、条目顺序、原始长度、是否观察到双 NUL 和 API 返回码。它不应仅凭某个词出现在条目内就断言系统已经执行过相应程序。5. 读取注册表前需要认识句柄、权限和长度单位下方 API 使用几个基础对象。HKEY是已打开注册表键的句柄不是路径字符串也不是键内容。RegOpenKeyExW成功后产生它调用方负责用RegCloseKey归还系统资源。KEY_QUERY_VALUE是只读查询值的访问权限请求它不允许修改配置。DWORD是 32 位无符号整数类型。注册表 API 中保存长度的DWORD可能表示两种单位名称长度通常是 UTF-16 宽字符个数原始值数据长度通常是字节数。BYTE是一个字节。wchar_t在 Windows 上通常承载一个 UTF-16 代码单元。将字节数直接当作字符数会造成缓冲区过大或过小。NUL是数值为零的终止字符。C 和 Win32 的许多宽字符串 API 依靠 NUL 找到文本结尾。REG_MULTI_SZ则把 NUL 同时用作条目分隔符和列表终止符。因此解析该类型时必须以 API 返回长度为边界不能只调用寻找第一个 NUL 的字符串函数。二、第一步定位父键并区分三项配置三个值共享同一个父键。BootExecute用于启动检查阶段SetupExecute用于安装与设置阶段Execute是另一份会话管理器命令列表。同一父键不代表三项配置具有相同状态。打开键时只申请查询权限。成功返回的HKEY是系统资源读取结束后必须关闭。// 意义在已有根键或父键下打开一个注册表子键。// 返回ERROR_SUCCESS 表示成功。ERROR_FILE_NOT_FOUND 表示键不存在。// ERROR_ACCESS_DENIED 表示当前令牌没有 samDesired 所请求的权限。// 成功时 *phkResult 是新 HKEY调用方拥有它必须恰好调用一次 RegCloseKey。LSTATUSRegOpenKeyExW(HKEY hKey,// 输入HKEY_LOCAL_MACHINE 等预定义根键或已打开的父键。预定义根键不关闭。LPCWSTR lpSubKey,// 输入相对 hKey 的 NUL 结尾 UTF-16 路径。可为 nullptr表示 hKey 本身。DWORD ulOptions,// 输入保留参数必须为 0。REGSAM samDesired,// 输入访问掩码。只读本节值时使用 KEY_QUERY_VALUE不请求写权限。PHKEY phkResult// 输出接收新句柄的非空指针。失败时不得使用其中的值。);// 意义释放 RegOpenKeyExW 成功返回的注册表键句柄。// 返回ERROR_SUCCESS 表示关闭成功。预定义根键无需也不应传入。LSTATUSRegCloseKey(HKEY hKey// 输入调用方拥有的已打开 HKEY。关闭后该值不能重复查询或关闭。);正确读取时为每个值保留独立结果。父键存在而Execute未设置时BootExecute和SetupExecute仍应照常输出。constexprwchar_tkKeyPath[]LSystem\\CurrentControlSet\\Control\\Session Manager;HKEY keynullptr;if(RegOpenKeyExW(HKEY_LOCAL_MACHINE,kKeyPath,0,KEY_QUERY_VALUE,key)ERROR_SUCCESS){// 分别读取 BootExecute、SetupExecute、Execute。RegCloseKey(key);}// 错误示例读取到一个值后直接结束。// 后两个值的缺失、空列表或实际条目都不会被观察到。RegQueryValueExW(key,LBootExecute,nullptr,nullptr,data,size);return;完成第一步定位父键并区分三项配置后已经取得只读查询所需的HKEY也明确了每次查询对应的值名。键句柄只能说明查询入口有效尚未提供值的内容。下一步需要让注册表 API 返回每项数据的类型和准确字节范围。三、第二步按返回字节数读取原始数据注册表查询函数的长度参数单位是字节。先传入空数据指针取得长度再按该长度读取原始数据。第二次读取遇到ERROR_MORE_DATA时需要扩大缓冲区并有限重试。// 意义读取一个注册表值的类型和原始字节。// 返回ERROR_SUCCESS 表示成功。ERROR_FILE_NOT_FOUND 表示值名不存在。// ERROR_MORE_DATA 表示 *lpcbData 指定的字节容量不足。此函数不把数据转换为字符串。LSTATUSRegQueryValueExW(HKEY hKey,// 输入已打开且至少有 KEY_QUERY_VALUE 权限的键句柄。LPCWSTR lpValueName,// 输入NUL 结尾 UTF-16 值名。nullptr 或 L 表示默认值。LPDWORD lpReserved,// 输入保留参数必须为 nullptr。LPDWORD lpType,// 输出接收 REG_MULTI_SZ 等类型。可为 nullptr本文必须提供以验证类型。LPBYTE lpData,// 输出接收原始字节。只查询所需长度时传 nullptr。LPDWORD lpcbData// 输入/输出传入 lpData 容量字节返回实际或所需字节数。不可为 nullptr。);正确的两次读取保留 API 返回的类型和实际字节数。REG_MULTI_SZ只有在字节数能被sizeof(wchar_t)整除时才按 UTF-16 解析。#includeWindows.h#includeutility#includevectorstructRegistryValueBytes{DWORD typeREG_NONE;// API 返回的注册表类型。std::vectorBYTEdata;// API 原样返回的数据。长度单位始终是字节。};// 正确示范读取一个值并处理两次查询之间长度变化造成的 ERROR_MORE_DATA。staticLSTATUSReadRegistryValueBytes(HKEY key,// 输入已成功打开、具有 KEY_QUERY_VALUE 的键。constwchar_t*valueName,// 输入NUL 结尾的值名本例为 BootExecute 等固定名称。RegistryValueBytesresult)// 输出仅在 ERROR_SUCCESS 时保存类型和完整字节。{for(intattempt0;attempt!3;attempt){// 有界重试避免持续修改的键导致无限循环。DWORD typeREG_NONE;DWORD requiredBytes0;LSTATUS statusRegQueryValueExW(key,valueName,nullptr,type,nullptr,requiredBytes);if(status!ERROR_SUCCESS){returnstatus;// ERROR_FILE_NOT_FOUND 与访问错误由调用者按值身份记录。}std::vectorBYTEbytes(requiredBytes);// requiredBytes 是字节不乘 sizeof(wchar_t)。DWORD actualBytesrequiredBytes;statusRegQueryValueExW(key,valueName,nullptr,type,bytes.empty()?nullptr:bytes.data(),actualBytes);if(statusERROR_MORE_DATA){continue;// 值在两次调用之间变长。重新取得新的长度。}if(status!ERROR_SUCCESS){returnstatus;}bytes.resize(actualBytes);// 只保留 API 实际写入的字节。result.typetype;result.datastd::move(bytes);returnERROR_SUCCESS;}returnERROR_MORE_DATA;// 连续变化三次保留此状态而不猜测内容。}RegistryValueBytes value;constLSTATUS statusReadRegistryValueBytes(key,LBootExecute,value);if(statusERROR_SUCCESSvalue.typeREG_MULTI_SZ){// 下面只在确认类型正确后才将 value.data 按 UTF-16 多字符串解释。}完成第二步按返回字节数读取原始数据后已经获得类型与限定长度的字节缓冲区。此时缓冲区仍只是原始数据直接当作普通字符串会丢失后续条目或在终止字符缺失时越出返回范围。下一步需要确认它确实符合 UTF-16 多字符串的边界规则再恢复原有顺序。四、第三步验证格式并按顺序解析条目REG_MULTI_SZ使用单个 NUL 分隔条目使用双 NUL 终止整个列表。autocheck autochk *\0\0表示一个完整条目。\0\0表示合法空列表。值不存在是第三种状态。// 正确示范先验证类型和字节对齐再以 API 返回范围为唯一边界解析。std::vectorstd::wstringentries;if(statusERROR_SUCCESSvalue.typeREG_MULTI_SZvalue.data.size()%sizeof(wchar_t)0){constauto*textreinterpret_castconstwchar_t*(value.data.data());conststd::size_t countvalue.data.size()/sizeof(wchar_t);std::size_t begin0;while(begincount){std::size_t endbegin;while(endcounttext[end]!L\0)end;if(endbegin)break;// 已到达双 NUL 中的第一个字符。entries.emplace_back(textbegin,end-begin);if(endcount)break;// 缺少 NUL 时严格停在 API 返回范围内。beginend1;}}BootExecute条目需要保留为完整原始文本。autocheck、autochk、*的组合具有该值自身的语义按普通 Run 键的“首个空格前是程序路径”规则拆分会改变原始记录。// 错误示例把多字符串缓冲区直接当作一个普通 C 字符串。constwchar_t*textreinterpret_castconstwchar_t*(value.data.data());std::wcouttext;// 只得到第一段且缺少 NUL 时可能越界读取。完成第三步验证格式并按顺序解析条目后已经可以在 API 返回范围内得到可显示的原始条目。条目为空或解析结束并不代表所有配置情况相同。下一步需要把空列表、未设置的值、格式异常和 API 错误分别记录。五、第四步记录空列表、缺失值与终止异常空列表和缺失值需要分开显示。空SetupExecute通常表现为REG_MULTI_SZ与零条目Execute未设置时则由RegQueryValueExW返回ERROR_FILE_NOT_FOUND。双 NUL 缺失需要作为格式状态保存。解析器仍可在 API 返回的最后字符处停止并输出已读取条目不能继续扫描注册表缓冲区之外的内存。if(statusERROR_SUCCESSentries.empty()value.typeREG_MULTI_SZ){// 合法空列表类型存在条目数为 0。}if(statusERROR_FILE_NOT_FOUND){// 值未设置与空列表不同。}完整可运行程序在附件https://wangweicm.lanzouu.com/iZ1aq3xtkffg