首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
1
0
[原创][自学][Windows编程]Windows 兼容与特殊登录启动键的枚举
发表于: 2026-7-16 10:33
880
[原创][自学][Windows编程]Windows 兼容与特殊登录启动键的枚举
WangWei_CM.
2026-7-16 10:33
880
# Windows 兼容与特殊登录启动键的枚举 兼容启动键、策略型启动键和命令解释器配置键分散在多个注册表位置。枚举时要先确定来源,再读取原始数据,最后按各自的用途解释。这个顺序可以避免漏项,也能防止把配置记录误当成程序已经运行的证据。 ## 一、对象关系和完整流程 ### 1. 先分清系统使用流程和枚举流程 系统使用流程从一个触发动作开始。触发动作可能是用户登录、启动 `cmd.exe`,也可能是进入终端服务的安装模式。负责该动作的 Windows 组件会寻找对应的注册表位置。它读取值类型和数据后,才决定是否处理其中的命令。 登录 Shell 是用户登录后提供桌面和开始菜单的程序,Windows 默认使用 `explorer.exe`。命令解释器是读取并执行命令文本的程序,这里指 `cmd.exe`。终端服务安装模式用于处理多用户环境中的应用安装兼容问题。三个场景由不同组件处理,因此不能把相关注册表值都叫作“开机自启”。 ```text 安装程序、管理员或策略写入配置 -> 形成“根键 + 注册表视图 + 子键路径 + 值名”记录 -> 登录、cmd.exe 启动或终端服务安装模式触发 -> 对应的 Windows 组件读取配置 -> 解释值类型和文本 -> 决定是否执行 ``` 枚举流程只读取配置。它不会触发这些命令,也不能单独证明某个命令曾经执行。 ```text 列出所有来源 -> 确定根键和注册表视图 -> 用只读权限打开键 -> 按 API 返回的长度读取原始字节 -> 记录值名、类型、长度和读取状态 -> 对已验证的文本生成解释结果 ``` ### 2. 一条记录由四部分共同定位 注册表是 Windows 管理的分层配置数据库。子键类似目录,值类似目录中的具名配置项。根键 `HKEY_LOCAL_MACHINE`(HKLM)保存本机范围配置,通常影响多个用户。根键 `HKEY_CURRENT_USER`(HKCU)保存当前用户配置,通常只影响当前登录身份。 一条记录的位置由根键、注册表视图、子键路径和值名共同组成。例如: ```text 根键:HKCU 子键:Software\Microsoft\Command Processor 值名:Autorun ``` 这四部分中的任一项不同,都应视为另一条记录。子键不存在、值不存在、值为空字符串和读取失败也是四种不同状态。枚举结果要分别显示这些状态,避免把权限不足写成“没有配置”。 ```text HKLM Software\Microsoft\Windows\CurrentVersion\Run SecurityHealth = %windir%\system32\SecurityHealthSystray.exe ``` ### 3. 注册表视图决定读取哪一份数据 64 位 Windows 会为部分软件配置维护 64 位和 32 位注册表视图。视图可以理解为同一路径下的两套独立数据。64 位程序默认看到 64 位视图,32 位程序默认看到 32 位视图。 `KEY_WOW64_64KEY` 明确选择 64 位视图。`KEY_WOW64_32KEY` 明确选择 32 位视图。枚举程序应主动选择视图,使结果不受程序自身位数影响。 ```text HKLM\Software 64 位视图:供 64 位组件使用 32 位视图:供 WOW64 下的 32 位组件使用 ``` ### 4. 值类型决定原始字节的解释方法 注册表 API 返回值类型和一段原始字节。`REG_SZ` 保存普通 UTF-16 文本。UTF-16 是 Windows 宽字符 API 常用的文本编码,每个编码单元占两个字节。`REG_EXPAND_SZ` 也保存 UTF-16 文本,其中可以包含 `%SystemRoot%` 这样的环境变量标记。 `REG_DWORD` 保存四字节无符号整数。`REG_MULTI_SZ` 保存多段文本。每段文本以一个 NUL 结束,整个列表以额外的 NUL 结束。NUL 是数值为零的终止字符。 文本转换前必须先检查值类型和返回的字节数。类型不匹配、字节数为奇数或文本没有终止符时,都要按异常数据处理。 ```text REG_SZ: explorer.exe REG_EXPAND_SZ: %SystemRoot%\System32\example.exe REG_DWORD: 0x00000001 REG_MULTI_SZ: first\0second\0\0 ``` ### 5. 读取时先保留来源 打开键之前,先记录 HKLM 或 HKCU、完整子键路径和 32 位或 64 位视图。枚举值时,再记录值名、REG 类型、原始字节长度和 API 返回码。来源信息与原始数据保存完成后,才能解释 `REG_SZ` 或 `REG_EXPAND_SZ` 文本。 环境变量展开、路径检查、签名验证和命令行拆分都属于后续分析。它们不能覆盖原始文本,也不能代替来源信息。 读取状态也要保留。`ERROR_FILE_NOT_FOUND` 表示对象不存在。`ERROR_ACCESS_DENIED` 表示当前访问权限不足。`ERROR_MORE_DATA` 表示缓冲区太小,需要扩容后重试。`ERROR_NO_MORE_ITEMS` 表示枚举正常结束。每个来源都要独立读取,一个来源失败后仍应继续处理其它来源。 ### 6. 配置存在不等于程序已经运行 配置层只回答键和值是否存在,以及读取到了什么原始数据。处理层回答当前 Windows 版本和相关组件是否会读取该位置。运行层回答命令是否执行、目标进程是否创建,以及程序是否正常完成。 枚举程序只能直接观察配置层。它应记录完整路径、值名、REG 类型、原始字节长度、注册表视图、调用账户和读取时间。实际执行情况需要结合进程、事件或日志证据判断。 ### 7. API 类型说明资源和长度单位 `HKEY` 是打开注册表键后得到的引用。调用方完成读取后,要用 `RegCloseKey` 释放它。`REGSAM` 是访问权限的位集合。`KEY_QUERY_VALUE` 允许读取值,`KEY_WOW64_32KEY` 和 `KEY_WOW64_64KEY` 用于选择注册表视图。`DWORD` 是 32 位无符号整数,可以保存访问位、值类型或长度。 名称长度通常以 UTF-16 宽字符个数计算。值数据长度通常以字节计算。注册表返回的文本不一定带有结尾 NUL,因此只能按 API 返回的精确长度构造字符串。使用 `wcslen` 扫描未确认终止的缓冲区可能越过有效数据边界。 ## 二、先把不同位置分开记录 ### 1. 每个键的作用范围不同 枚举应先列出所有独立来源。常见的兼容或特殊登录位置包括: ```text HKCU\Software\Microsoft\Windows\CurrentVersion\RunServices HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows (Run、Load) HKCU\Software\Microsoft\Command Processor (Autorun) HKLM\Software\Microsoft\Command Processor (Autorun) HKLM\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\... ``` 根键必须作为来源的一部分保留。HKCU 保存当前用户配置,HKLM 保存本机配置。同名值出现在两处时,代表两个独立设置。 不同键也有不同用途。`RunServices` 属于兼容位置。策略型 `Explorer\Run` 位于策略路径。Windows 键中的 `Run` 和 `Load` 是固定值名。Command Processor 的 `Autorun` 是命令解释器初始化设置。 HKLM 的 32 位和 64 位软件视图需要分别读取。`KEY_WOW64_64KEY` 和 `KEY_WOW64_32KEY` 可以固定读取范围。结果中必须写明视图。路径文本相同的值也不能合并。 ### 2. 打开键时只请求读取权限 打开现有键的宽字符 API 返回一个新的 `HKEY`。返回 `ERROR_SUCCESS` 表示成功,其它 `LSTATUS` 返回值表示键不存在、权限不足等 Win32 错误。 ```cpp // 意义:在 hKey 下打开 lpSubKey,并请求 samDesired 指定的权限和注册表视图。 // 返回:ERROR_SUCCESS 表示成功。其它 LSTATUS 是可记录的 Win32 错误码。 LSTATUS RegOpenKeyExW( HKEY hKey, // 输入:预定义根键或已经打开的父键。 LPCWSTR lpSubKey, // 输入:相对 hKey 的、以 NUL 结束的键路径。 DWORD ulOptions, // 输入:保留,必须为 0。 REGSAM samDesired, // 输入:KEY_QUERY_VALUE 与可选 KEY_WOW64_* 标志。 PHKEY phkResult // 输出:成功时得到的新 HKEY,调用方负责关闭。 ); // 意义:释放 RegOpenKeyExW 成功得到的键句柄。 // 返回:ERROR_SUCCESS 表示成功。同一个打开句柄只能关闭一次。 LSTATUS RegCloseKey( HKEY hKey // 输入:已打开且尚未关闭的子键句柄。 ); ``` 正确做法:先记录键路径和视图,再用最小权限打开键。一个键不能读取时,记录该来源的失败状态,然后继续处理其它来源。 ```cpp HKEY key = nullptr; const LSTATUS status = RegOpenKeyExW( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Command Processor", 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &key); if (status == ERROR_SUCCESS) { // 读取 Autorun 等直接值。 RegCloseKey(key); } ``` 错误示例:把默认视图当成完整范围。下面的调用在 32 位程序中默认读取 32 位视图,无法固定得到 64 位 Command Processor 设置。 ```cpp // 错误:缺少 KEY_WOW64_64KEY,结果依赖调用方位数。 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Command Processor", 0, KEY_QUERY_VALUE, &key); ``` ## 三、用长度上限枚举每个键的直接值 ### 3. 查询容量时区分名称字符数与数据字节数 查询键信息的宽字符 API 返回直接值数量、最长值名和最长值数据长度。最长值名以宽字符个数计算。最长数据以字节计算。最大值名长度不包含结尾 NUL。 ```cpp // 意义:读取已打开键的值数量、最长值名和最长值数据长度。 // 返回:ERROR_SUCCESS 表示成功。其它 LSTATUS 表示键句柄或访问状态异常。 LSTATUS RegQueryInfoKeyW( HKEY hKey, LPDWORD lpClass, LPDWORD lpcchClass, LPDWORD lpReserved, LPDWORD lpcSubKeys, LPDWORD lpcMaxSubKeyLen, LPDWORD lpcMaxClassLen, LPDWORD lpcValues, // 输出:当前直接值数量。 LPDWORD lpcMaxValueNameLen, // 输出:最长值名字符数,不含 NUL。 LPDWORD lpcMaxValueLen, // 输出:最长值数据字节数。 LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime ); ``` 正确做法:为值名额外预留一个 `wchar_t`,值数据则按字节分配。`Run`、`Load` 和 `Autorun` 都可能是空文本、未终止文本或环境变量文本。数据长度只能使用 API 返回值。 ```cpp DWORD valueCount = 0, maxNameChars = 0, maxDataBytes = 0; const LSTATUS status = RegQueryInfoKeyW( key, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &valueCount, &maxNameChars, &maxDataBytes, nullptr, nullptr); if (status != ERROR_SUCCESS) { return; } std::vector<wchar_t> valueName(maxNameChars + 1, L'\0'); std::vector<BYTE> valueData(maxDataBytes == 0 ? 1 : maxDataBytes); ``` 错误示例:把数据字节数当成宽字符数。这个写法会掩盖单位错误,也没有为最大长度值名预留 NUL 位置。 ```cpp // 错误:maxDataBytes 的单位是字节,不能用来构造 wchar_t 缓冲区。 std::vector<wchar_t> valueData(maxDataBytes); ``` ### 4. 枚举值时重新填入两个容量参数 按索引读取值的宽字符 API 只读取当前键的直接值。返回成功后,名称长度变为实际字符数,数据长度变为实际字节数。下一次调用前,需要重新填入缓冲区容量。 ```cpp // 意义:读取 hKey 中 dwIndex 指定的一个直接值。 // 返回:ERROR_SUCCESS 表示成功。ERROR_NO_MORE_ITEMS 表示正常结束。 // ERROR_MORE_DATA 表示容量不足。 LSTATUS RegEnumValueW( HKEY hKey, DWORD dwIndex, LPWSTR lpValueName, LPDWORD lpcchValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ); ``` 正确做法:每次调用前恢复容量。遇到 `ERROR_MORE_DATA` 时,重新查询长度上限,再有限重试同一索引。只有 `ERROR_NO_MORE_ITEMS` 表示当前键枚举正常结束。值数量只是查询时刻的统计结果。 ```cpp DWORD nameChars = static_cast<DWORD>(valueName.size()); DWORD dataBytes = static_cast<DWORD>(valueData.size()); DWORD type = REG_NONE; const LSTATUS status = RegEnumValueW( key, index, valueName.data(), &nameChars, nullptr, &type, valueData.data(), &dataBytes); if (status == ERROR_SUCCESS) { valueName.resize(nameChars); // 实际字符数,不包含 NUL。 valueData.resize(dataBytes); // 实际字节数。 } ``` 错误示例:把所有值都解释为启动命令。Windows 键中的 `Run` 和 `Load` 需要按固定值名识别。Command Processor 的 `Autorun` 也需要单独识别。策略键和 RunServices 中的普通值仍要保留完整位置与类型。未知值不能只靠名称猜测用途。 ## 四、保留文本、环境变量和命令解释边界 ### 5. 原始 REG_EXPAND_SZ 文本与展开文本不同 原始字节必须先保留。一个 `REG_EXPAND_SZ` 值可以是 `%SystemRoot%\System32\cmd.exe /d /c task.cmd`。其中,`%SystemRoot%` 来自当前进程的环境变量。不同用户和服务账户可能得到不同的 `%APPDATA%` 与 `%USERPROFILE%` 展开结果。 命令文本必须完整保留。`"C:\\Program Files\\Tool\\tool.exe" /background` 的第一个空格位于引号内。`cmd.exe /c` 后面的内容也要按命令解释器规则处理。按第一个空格截断会得到错误的路径。 只有 `REG_SZ` 和 `REG_EXPAND_SZ` 可以按单个 UTF-16 文本处理。`REG_BINARY`、`REG_DWORD` 和 `REG_MULTI_SZ` 需要保留原始类型与字节长度。数据看起来可以显示,也不能直接按命令文本解释。 ## 五、让结论保持在只读范围内 ### 6. 同名值不应跨位置去重 条目位置必须包含根键、注册表视图、键路径和值名。策略型 Run 与普通 Run 属于不同位置。HKCU 与 HKLM 也属于不同位置。32 位与 64 位视图即使出现相同命令文本,也不能合并为同一条设置。 注册表状态和实际活动需要分别判断。读取到兼容键或 `Autorun` 值,只能说明该值在本次查询时存在。它是否被具体系统组件读取,是否创建目标进程,目标是否完成,都需要独立的进程或事件证据。 每个成功打开的 `HKEY` 都要关闭。程序只请求 `KEY_QUERY_VALUE`,不会创建、修改或删除键值。这样的读取不会改变观察对象。 完整可运行程序在附件。 
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
上传的附件:
002-Windows兼容与特殊登录启动键的枚举.zip
(160.71kb,3次下载)
收藏
・
1
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
WangWei_CM.
30
发帖
1
回帖
40
RANK
关注
私信
他的文章
[原创] 进程模块枚举、映像身份与线程启动地址关联
214
[原创]Windows 进程 CPU、内存、I/O 与调度指标采样
980
[原创]跨位数进程的映像、命令行与环境块读取
534
[原创]进程实例身份、父子关系与会话归属
293
[原创]Windows 进程枚举NtQuerySystemInformation(SystemProcessInformation
634
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
Fzzz
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部