guest 守护进程 vphoned

运行在虚拟 iPhone 内部的控制执行端——能力概览、make vphoned 快速迭代流程与不重启 VM 的热更新。

guest 守护进程 vphoned

vphoned 是运行在 iOS VM 内部的 Objective-C LaunchDaemon(伪装成 Apple 的 com.apple.mobile.assetd,/usr/libexec/assetd),是宿主控制 guest 的执行端。宿主发的每一条控制请求——触摸注入、文件传输、应用管理、账户切换、keychain、剪贴板、位置模拟、PVFS 文件视图规则管理——最终都由 vphoned 在 guest 内落地。源码在仓库 scripts/vphoned/ 下。除控制通道外还提供 SSH 桥、虚拟相机帧中转、端口转发中继三条独立 vsock 数据通道。

vphoned 与宿主通过 vsock 1337 通信,握手时宣告 caps(能力列表),宿主据此决定哪些功能可用。日志落在 guest 内 /var/root/vphoned-logs/。

提供的能力

能力 说明
hid / touch 键盘 HID 事件与单指触摸注入(归一化坐标)
file guest 文件系统 list/get/put/mkdir/delete/rename/settime/chmod
apps / ipa_install 应用枚举/启动/前台/终止;IPA 安装(含 pending 自举队列)
clipboard UIPasteboard 文本与图片读写
keychain keychain 条目查询与写入
accounts 按 App 的多档案账户切换
location 模拟 GPS 位置注入与清除
settings / url CFPreferences 读写、打开 URL
devmode ARM 开发者模式(arm 后需重启)
keyboard Unicode 文本输入(签名带键盘 App Group 时可用;跨变体键盘安装后默认可用,仅 less 不含)
port_forward guest 内服务到 host 的端口转发中继
pvfs 每进程文件视图(PVFS)规则管理(仅 dev/jb/exp 变体,见下文)
service_list / service_action / process_kill launchd 服务枚举与启动/停止/杀进程(不经 caps 门控,旧 vphoned 回 unknown type,先热更新即可)
guest_metrics 系统级指标(CPU%、内存、进程数、uptime),供 Manager 性能页
list_processes 进程列表(两遍全局采样差分,~50ms 固定开销,按 CPU 降序)
guest_reboot 用户态重启(launchd 重建全部用户态进程,kernel 不动,~40s 回归)
service_disable / service_enable / service_disabled_list 批量持久禁用/恢复 launchd 服务(跨 VM 重启持久)

另外三条独立数据通道不走命令循环:SSH 桥(vsock 2222,inetd 模式 spawn dropbear -i,guest 零 TCP 暴露)、虚拟相机帧中转(vsock 1338 → shm → libvcamcaptured)、端口转发(vsock 1339,支持 TCP 与 Unix socket 目标,并发上限 128)。

make vphoned 快速迭代

改完 scripts/vphoned/ 下的代码,一条命令交叉编译并签名:

make vphoned VM_DIR=/Users/liqiang/.vphone/VMs/dev

流程为:xcrun -sdk iphoneos clang -arch arm64 交叉编译 → 拷贝到 $(VM_DIR)/.vphoned.signed → ldid 用 signcert.p12 + entitlements 签名。构建带增量指纹(.build-hash),源码无变化时秒过。键盘源恒编入,VPHONED_DEV_KEYBOARD 环境变量只切换签名 entitlements(dev ↔ regular)。

推入运行中的 guest(无需重启 VM)

make vphoned 产出新的 .vphoned.signed 后,用以下任一方式推入运行中的 guest:

# 方式一:Connect 菜单 → "Check for vphoned Update"
# 方式二:HostControl socket
echo '{"t":"vphoned_update"}' | nc -U "$(VM_DIR)/vphone.sock"
# 方式三:CLI 子命令
vphone-cli control vphoned-update
# 方式四:vphone-manager 详情页 Guest tab 的「Upgrade vphoned」按钮

更新机制:宿主握手时携带新二进制 SHA-256,guest 比对发现不一致即回 need_update;宿主随后推送整个二进制,guest 落盘缓存后自重启(利用 launchd KeepAlive),新代码立即生效。若缓存版本连续 3 次启动失败,自动回退到安装版二进制。

门禁已移除:一键升级对所有变体放行。bundled 二进制恒以 entitlements.plist 合并键盘 App Group group.com.vphone.keyboard 签名(对 dev/jb/exp 是 entitlements 超集,无副作用);less 变体仍被宿主侧 vphoned_update 检查拒绝。

up-to-date 徽章:guest version 响应新增 bin_sha256(运行中二进制自身 SHA-256),宿主优先与之比对(字节级精确),老 guest 无该键时回退 git-hash 前缀比对,不再误报"已是最新"。

适用边界

这条热更新通道只替换 guest 内的 vphoned 进程二进制:

改动类型 走 make vphoned + 热更新?
scripts/vphoned/ 下任意 guest 进程逻辑(命令 handler、服务实现) ✅ 可以
键盘扩展(keyboard_extension/,跨变体默认安装) ❌ 需重走 CFW 安装
固件补丁(dfu / LLB / txm / kernelcache) ❌ 必须重走 fw_patch* + 启动流程
guest 内其他系统组件(CFW 文件、launchd 配置) ❌ 重走 make boot 对应管线

判据很简单:只要改动不进 /usr/libexec/assetd 这一个进程,热更新就够不着。

首启 IPA 自举队列

vphoned 启动时后台扫描 /usr/libexec/.vphone-pending-ipas/ 内的 .ipa(只认 .ipa,不认 .tipa)自动安装:键盘扩展 IPA 与用户在建机时附加的 IPA(vm create --stage-ipa / cfw install --stage-ipa / Manager 向导 Apps 区 / 远程创建的 extraIPAs)都走这条队列。每个 IPA 最多重试 6 次、间隔 10 秒(等待安装守护进程就绪),成功后写 done marker 防止重装;无有效证书的 IPA 会先做 ad-hoc 重签再安装,任意来源的 IPA 都能离线装进系统。less 变体没有 CFW 阶段,不适用。

PVFS 每进程文件视图规则(v4 协议)

PVFS(per-process file view)是 dev/jb/exp 变体内核提供的「每进程文件视图」能力:同一台 VM 上,不同进程看到的文件系统可以不同。规则由 vphoned 统一管理,一行一条,四类动作:

动作 效果
REDIRECT 对 src_path 的访问重定向到 target_path(src/target 以 / 结尾即递归目录)
DENY 拒绝访问,对进程表现为文件不存在
HIDE 从目录列表中隐藏指定路径
ALLOW 进程级白名单豁免(不参与 match,仅喂 HIDE 白名单)

conf 格式为 binary_path|src_path|target_path|action,binary_path 是目标进程二进制的绝对路径,* 匹配所有进程:

/bin/cat|/private/var/test/pvfs_redirect.txt|/private/var/test/pvfs_tgt.txt|REDIRECT

v4 协议(glob 支持):src 支持 true glob 元字符(* 任意序列含 /、? 单字符、[class] 支持 !/^ 取反+范围、\ 转义),不支持的元字符作为字面处理。每条规则附带 8 字节 prefilter(prefix_len / suffix_len / min_len / flags),由 vphoned 在用户态对 pattern 一次 tokenize 算出,内核在匹配前先过三层廉价门(min_len 长度门 / 字面前缀 memcmp / 字面后缀 memcmp)——未过门直接跳过,大幅降低 namei 热路径开销。

splice redirect:REDIRECT 规则若 pattern 以 * 结尾且 target 以 / 结尾,则为 splice 语义——rewrite 路径 = target + 原路径[star_off..](类似 v3 递归后缀拼接)。不以 * 结尾的 glob REDIRECT 是整路径替换。

部署边界:内核 cave 改动(glob 引擎 / prefilter 门 / 槽尺寸)须 make rebuild_dev;vphoned 侧改动(prefilter 计算 / conf 校验)可热推,无需重启 VM。

规则持久化在 guest 内(/var/root/vphone_rules.conf),VM 重启后由 vphoned 自动重新推送。宿主侧经 control 命令热更新,改动即时生效:

vphone-cli control pvfs-list                     # 查看当前规则(原始 conf + 解析结果)
vphone-cli control pvfs-apply --file rules.conf  # 推送宿主侧规则文件并立即生效
vphone-cli control pvfs-reload                   # 重读 guest 内 conf 并重推(配合 guest 内编辑)
make pvfs_sync                                   # 一键同步宿主侧规则文件(VM 需运行中)

vm cryptid-redirect 基于同一套规则:为已装 App 配置 cryptid=1 副本的 REDIRECT + HIDE,让 App 自读二进制时看到「已加密」形态,规避越狱/脱壳检测(GUI 拖拽/菜单安装 IPA 时自动完成)。该能力仅 dev/jb/exp 变体具备(握手 caps 含 pvfs);regular 变体或旧版 vphoned 上相关命令返回能力错误。

Service Profile 与 guest_reboot

Service Profile 是批量持久禁用/恢复 launchd 服务的能力,单一数据源为 config/services_catalog.json(15 组 / 155 个 label)。service_disable / service_enable 逐 label spawn launchctl disable/enable system/<label>,每批 ≤64 个 label,批后直读 /var/db/com.apple.xpc.launchd/disabled.plist 判定成败(launchctl exit code 不可靠)。禁用跨 VM 重启持久生效。

guest_reboot 是 Service Profile 的满血生效路径:disabled.plist 只在 launchd 装载 job 时读取,在位 stop 后被 KeepAlive 复活的服务到用户态重启才真正消失。vphoned 先回 ok(乐观应答)再延迟 1s spawn launchctl reboot system——launchd 重建全部用户态进程,kernel 不动,vphoned ~40s 回归,控制连接短暂断开属预期。

实测数据(dev VM):apply 13.8s 完成 0 失败 0 丢失;用户态重启后 used 3.78GB→2.48GB(-1.30GB)、进程 401→233、CPU 6%→0%。

排障提示

  • 命令报缺能力(响应带 capability 字段):先 make vphoned 并推送更新,很多能力是运行时探测追加的。
  • 怀疑更新进了崩溃循环:看 guest 日志 /var/root/vphoned-logs/ 与宿主日志中的 running_from / cache_fail_count;连续 3 次失败会自动回退安装版,可删掉缓存后重试。
  • 触摸不生效:先确认握手 caps 含 touch;iOS 26.x base 上触摸走 VZ multitouch 原生路径,属正常现象。
  • 新 VM 上无需手动安装 vphoned:launchd plist 与二进制首次进入 guest 是由 CFW 安装阶段完成的(cfw install / vm create 管线的一部分)。