~/posts/binary/k7-named-pipe-registry-client-trust.md

K7 特权管道:配置写入与客户端信任

从 K7 配置请求的 28 字节头部出发,用长度差分、注册表写入与进程状态分离各层证据,分析客户端允许规则和驱动保护规则的错位,并补充正式修复版本。

date[31:24]
read[23:16]
7 分钟
cat[15:8]
二进制
目录
  1. 0x00从配置按钮找到服务边界
  2. 0x01长度差分揭示协议约束
  3. 0x02用后端状态验证重放效果
  4. 0x03注册表写入与进程启动相接
  5. 0x04补丁暴露了信任集合差异
  6. 0x05接受身份与保护身份必须对齐
  7. 0x06后续公告补充与修复重点
  8. 0x07参考资料

K7 Ultimate Security 17.0.2045 的配置界面会限制普通用户,但对应的 SYSTEM 服务曾接受低权限客户端提交的注册表内容。更深的问题出现在补丁之后:服务认定可信的客户端,与驱动实际保护的进程,并不是同一个集合。

这个案例适合分三层看:管道是否可达、请求是否有权修改对象、客户端身份是否代表运行时完整性。以下将历史版本的行为记录与静态分析分开,并用离线模型核对协议字段;不将它们视为当前版本的复现结果。

从配置按钮找到服务边界

对象 历史记录中的角色
K7 Ultimate Security 17.0.2045 初始测试产品版本
K7TSMain.exe 普通用户上下文中的界面进程
K7TSMngr.exe SYSTEM 服务进程
K7TSMngrService1 与配置操作对应的命名管道
K7MailProxyV1 枚举发现的空 DACL 线索,并非已确认的配置通路

普通用户打开配置页会看到限制提示;管理员可以开放非管理员修改设置的选项。关键动作是把按钮点击与管道读写关联,而非看到一个宽松 ACL 就假定它承载所有功能。Windows 的管道访问检查控制连接权限,业务操作仍需单独授权。

界面示意图:普通用户限制提示与 K7 访问控制选项。

界面示意图。用于说明两个入口的权限语义,不表示某次实验的截图或状态。

捕获中的配置请求长 213 字节,其中 28 字节为头部,185 字节为正文;对应回复长 36 字节。正文包含 HKLM\Software\K7 Computing\K7TotalSecurity\CommonInfo 和三个值名:

  • AdminNonAdminIsValid:开放非管理员配置的状态。
  • AdminChangesNeedPassword:修改配置是否需要密码。
  • AdminChangesPasswordHash:密码相关状态字段。

这更像通用注册表文本的传输,而不是只接收布尔值的专用配置接口。但“含有路径”尚不足以推出任意注册表写入,需要观察服务端最终修改了什么。

长度差分揭示协议约束

下面是正文缩短一字节后的请求头。字节顺序是小端序,七个字段各占一个 DWORD。

配置请求头:28 字节
000000005354374B101000001C00000000000000
0000001044000100B800000000000000
  1. magic0x00–0x03
  2. version0x04–0x07
  3. header_size0x08–0x0B
  4. reserved0x0C–0x0F
  5. command0x10–0x13
  6. body_length0x14–0x17
  7. auxiliary_length0x18–0x1B
偏移 数值 解释
0x00 0x4b375453 原始字节为 ASCII ST7K
0x04 0x1010 协议标识
0x08 0x1c 头部长度 28
0x0c 0 样本中的保留字段
0x10 0x10044 记录中的注册表请求命令
0x14 0xb8 正文长度 184
0x18 0 样本中的辅助长度字段

差分测试用于区分两种解释:值名白名单,以及正文长度检查。这里的“成功”指观察到新值,不只是客户端收到回复。

修改 值名字节数 头部正文长度 历史结果
AdminNonAdminIsValid 20 185 原始配置请求
改为 aaaaa,保留旧长度 5 185 未成功
等长改为 AdminNonAdminIsValie 20 185 新值被写入
缩短为 AdminNonAdminIsVali,同步长度 19 184 新值被写入

等长的新值名成功,说明服务并未只认原有值名;缩短请求在更新长度后成功,说明先前失败与帧结构有关。它支持“值名可控”的结论,路径范围仍需另行验证。

通用编码应写满偏移 0x14 处的四字节字段。仅改一个字节,只对小于 256 字节的特定长度成立;长度必须按实际编码后的字节计算,包含真实传输的换行和终止字节。

header_check.pypython
import struct

header = bytes.fromhex(
    "53 54 37 4b 10 10 00 00 1c 00 00 00 00 00 00 00 "
    "44 00 01 00 b8 00 00 00 00 00 00 00"
)
fields = struct.unpack("<7I", header)
assert fields == (0x4b375453, 0x1010, 28, 0, 0x10044, 184, 0)

body = b"x" * 300  # Illustrative bytes, not registry syntax.
updated = bytearray(header)
struct.pack_into("<I", updated, 20, len(body))
assert updated[20:24] == b"\x2c\x01\x00\x00"

这个离线检查已运行:原头解析为 28 字节头部与 184 字节正文;示例长度 300 编码为 2c 01 00 00。示例正文只是占位字节,不连接管道,也不修改注册表。

用后端状态验证重放效果

配置重放的动画先显示普通用户访问受限,随后出现 36 字节回复,再打开设置页时,非管理员配置选项已启用。回复本身只证明有数据返回;真正支持权限变化的是配置页状态与后端写入记录。

Process Monitor 中的写入主体是 SYSTEM,实际路径包含 HKLM\SOFTWARE\WOW6432Node\K7 Computing\K7TotalSecurity\CommonInfo。它与请求文本没有完全相同的路径拼写:WOW64 注册表重定向让 32 位与 64 位视图有所区别。核对证据时应记录进程位数和注册表视图,而非把两个路径当成互相矛盾的结果。

证据 支持的结论 不直接支持的结论
低权限发起进程 请求不依赖管理员界面操作 每个产品版本均可接受
回复 36 字节 服务处理了连接并返回数据 注册表写入必然成功
SYSTEM 的注册表写入 高权限服务应用了请求 任意路径均可写
新值名与配置变化 对象内容可受客户端影响 任意后续操作都会提权

注册表写入与进程启动相接

早期版本的后续记录把可控路径延伸到 Image File Execution Options(IFEO),目标是辅助程序 K7TSHlpr.exe 的 Debugger 配置。IFEO 的调试器设置影响目标程序的新实例;当 SYSTEM 服务启动这个辅助程序时,注册表数据就可能改变高权限执行入口。

flowchart TD
  A["低权限客户端"] --> B["服务接受注册表内容"]
  B --> C["SYSTEM 写入 IFEO 配置"]
  C --> D["服务启动辅助程序"]
  D --> E["调试器配置改变启动入口"]
  E --> F["高权限子进程与账户状态变化"]

动画中的对照顺序很重要:测试账户起初不存在,直接创建账户得到访问拒绝;管道操作后,进程树显示 K7TSMngr.exe 下出现 SYSTEM 的命令解释器,最后账户查询显示新增账户属于本地管理员组。证据链因此不只依赖脚本打印的成功文字。

这条路径依赖三个独立条件:相关注册表位置受写入原语影响、目标映像实际被启动、启动上下文具有高权限。配置开关变化与 SYSTEM 执行是两个不同层次的结论。

补丁暴露了信任集合差异

历史阶段 观察 边界
初始 17.0.2045 低权限脚本可重放配置请求 缺少完整的服务端授权
加入客户端识别后 普通 PowerShell 请求失效;产品进程中的请求仍有成功记录 文件身份不等于运行时完整性
K7Sentry.sys 22.0.0.70 先前载体路径受阻,K7QuervarCleaningTool.exe 路径仍使配置生效 客户端允许范围与进程保护范围不一致

中间阶段还记录了常规模块加载与手动映射结果不同。这说明客户端所在进程值得检查,但不意味着每个签名文件或每种映射方法都有效。表中的 22.0.0.70 是测试中的中间驱动版本,不是厂商最终公告的修复版本。

服务端静态分析涉及 ProcessPipeConnection:读取 28 字节头,检查魔数、协议标识和头长,并把两个长度限制在 1 MiB 内。客户端验证前的语义条件为:

dispatch_condition.cc
command == 0x1004C || old_windows_condition || ValidatePipeClient(this)

这里是 0x1004C,而配置请求是 0x10044。二者不同;没有命令分发与运行轨迹,就不应把配置操作直接归入这个验证例外。该表达式是静态分析的语义摘录,不是完整可编译函数。

接受身份与保护身份必须对齐

ValidatePipeClient 通过 GetNamedPipeClientProcessId 获取客户端 PID,再用 OpenProcess 和 QueryFullProcessImageNameA 查询映像路径。记录中的接受条件包括安装路径匹配、文件摘要缓存命中,以及厂商签名验证。

驱动的 ShouldProtectProcess 采用另一套规则:命中 VDefProtectedProcs 中的名称,或满足 K7 文件名前缀与规范化安装路径条件。路径子串匹配不是严格目录归属;磁盘文件签名也不是进程内存的完整性证明。

令 A 表示服务接受该客户端,P 表示驱动给予预期保护,可以把不一致直接写成真值表:

A P 含义
false false 不接受,也未按该规则保护
false true 被保护,但不被此服务接受
true false 服务信任了未被同等保护的载体
true true 两条规则在该进程上对齐

离线模型枚举四种组合,只有 A and not P 命中缺口。这是设计一致性的检查,不是新版本漏洞证明:候选进程还要满足可控制请求逻辑、服务接受操作等运行时条件。

后续公告补充与修复重点

2025-12-22 的厂商公告将问题列为 CVE-2025-67826,确认 17.0.2045 的本地命名管道问题,并要求至少更新到 K7 Ultimate Security 17.0.2057,且 K7Sentry.sys 为 22.0.0.74 或更高。这是原发表日之后的公告补充,不应倒推为早期实验已验证的修复状态。

服务端应把“可连接”“来自产品文件”“有权修改某个配置”拆成独立检查。配置接口要限制键、值、类型和允许的状态迁移;若依赖进程保护维持客户端可信,就要共同维护接受集合与保护集合,并在身份查询或完整性判断失败时停止处理敏感操作。

这个案例最重要的结论不是某个特定管道名,而是验证顺序:先证明帧结构成立,再证明对象被修改,最后证明高权限行为发生。任何一层的成功,都不替代下一层证据。

参考资料

NORMAL~/posts/binary/k7-named-pipe-registry-client-trust.md§--
0%zh-CN