利用 PCC 固件中的 VPHONE600AP 组件构建虚拟 iPhone 完整复现指南
原文: wh1te4ever/super-tart-vphone-writeup
本文档基于原始 writeup 整理为可落地复现的中文操作手册。
目录
- 背景与动机
- 前置要求
- 整体架构概览
- 第一阶段: 修改 super-tart 以启动虚拟 iPhone
- 第二阶段: 构建混合固件
- 第三阶段: 补丁 AVPBooter (BootROM)
- 第四阶段: 修改并编译 libirecovery
- 第五阶段: 补丁固件组件
- 第六阶段: 恢复固件
- 第七阶段: SSH Ramdisk 修复启动
- 第八阶段: 安装 Cryptex 与系统修补
- 第九阶段: 实现 Metal GPU 加速
- 第十阶段: Sequoia 上启用触摸交互
- 兼容性
- 常见问题排查
- 参考项目与工具链
1. 背景与动机
2024 年底,Apple 推出 Private Cloud Compute (PCC)。2025 年底,有人发现 Apple 在 cloudOS 26 的 PCC 固件中新增了 vphone600ap 相关组件——一个 “iPhone Research Environment Virtual Machine”。
2026 年 1 月,@_inside 展示了利用这些组件成功启动虚拟 iPhone 的推文。与 QEMUAppleSilicon (Inferno) 相比,运行更加流畅,甚至支持 Metal 加速。
本文档完整复现 wh1te4ever 的构建流程。
2. 前置要求
2.1 硬件
| 项目 | 要求 |
|---|---|
| 处理器 | Apple Silicon (M1/M2/M3/M4 系列) |
| 内存 | 建议 16GB+(32GB 更佳) |
| 磁盘 | 至少 100GB 可用空间 |
2.2 软件环境
| 项目 | 说明 |
|---|---|
| macOS | Sequoia 15.x 或 Tahoe 26.x |
| SIP | 必须禁用 (System Integrity Protection) |
| AMFI | 必须禁用 |
| Xcode | 最新版(用于编译 Swift 项目) |
| Python 3 | 用于运行固件处理脚本 |
2.3 禁用 SIP 和 AMFI
# 重启进入 Recovery 模式 (开机长按电源键)
# 在终端中执行:
csrutil disable
# 设置 boot-args 禁用 AMFI:
nvram boot-args="amfi_get_out_of_my_way=1"
reboot
2.4 必需工具
| 工具 | 来源 | 用途 |
|---|---|---|
| super-tart | GitHub | 修改后的 tart VM,支持 DFU/自定义 BootROM |
| img4 | GitHub | IMG4/IM4P 处理 |
| img4tool | GitHub | IM4P 创建 |
| pyimg4 | PyPI | Python IMG4 处理库 |
| idevicerestore | GitHub | 固件恢复 |
| libirecovery | wh1te4ever fork | DFU 通信(已修改支持 vresearch101ap) |
| iproxy | GitHub | USB 端口转发 |
| ldid | GitHub | 代码签名 |
| trustcache | GitHub | TrustCache 创建 |
| ipsw | GitHub | IPSW/AEA 处理 |
| IDA Pro | Hex-Rays | 二进制逆向(补丁定位) |
2.5 安装 Python 依赖
pip3 install pyimg4
2.6 固件下载
| 固件 | 版本 | 来源 |
|---|---|---|
| cloudOS 26.1 | 23B85 | PCC 固件(含 vphone 组件) |
| iOS 26.1 | 23B85 (iPhone17,3) | ipsw.me |
3. 整体架构概览
┌─────────────────────────────────────────────────────────────────┐
│ 整体流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 修改 super-tart ──→ 支持 vresearch101 硬件模型 │
│ │ │
│ 2. 混合固件 ──→ cloudOS 26.1 + iOS 26.1 组件 │
│ │ │
│ 3. 补丁 AVPBooter ──→ 绕过 image4 签名验证 │
│ │ │
│ 4. 修改 libirecovery ──→ 支持 vresearch101ap 模型 │
│ │ │
│ 5. 补丁固件组件 ──→ iBSS/iBEC/LLB/TXM/kernelcache │
│ │ │
│ 6. 恢复固件 ──→ DFU 模式 + idevicerestore │
│ │ │
│ 7. SSH Ramdisk ──→ 修复 Cryptex 缺失问题 │
│ │ │
│ 8. 安装 Cryptex + 系统修补 ──→ 注入 dyld_shared_cache 等 │
│ │ │
│ 9. Metal 支持 ──→ AppleParavirtGPUMetalIOGPUFamily.bundle │
│ │ │
│ 10. 触摸支持 ──→ VZVirtualMachineView 鼠标事件重写 │
│ │
└─────────────────────────────────────────────────────────────────┘
启动链 (Boot Chain)
AVPBooter (BootROM)
└──→ iBSS (DFU 模式)
└──→ iBEC
└──→ LLB (正常启动)
└──→ SPTM + TXM + kernelcache
└──→ launchd → iOS 用户空间
4. 第一阶段: 修改 super-tart 以启动虚拟 iPhone
4.1 核心原理
Apple 的 security-pcc 项目对应 /System/Library/SecurityResearch/usr/bin/vrevm 二进制,使用 Virtualization.framework 的私有 API 来初始化硬件模型。关键参数:
- PlatformVersion: 3 (
.appleInternal4) - BoardID: 0x90
- ISA: 2
- BootROM:
AVPBooter.vresearch1.bin - SEPROM:
AVPSEPBooter.vresearch1.bin - 显示分辨率: 1179x2556 (iPhone 15 Pro 级别)
4.2 克隆并修改 super-tart
git clone https://github.com/JJTech0130/super-tart
cd super-tart
4.3 修改 /Sources/tart/VM.swift
在 VM 类中添加 vresearch101 硬件模型初始化函数:
class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
// vresearch101 硬件模型配置
static private func vzHardwareModel_VRESEARCH101() throws -> VZMacHardwareModel {
var hw_model: VZMacHardwareModel
guard let hw_descriptor = _VZMacHardwareModelDescriptor() else {
fatalError("Failed to create hardware descriptor")
}
hw_descriptor.setPlatformVersion(3) // .appleInternal4 = 3
hw_descriptor.setBoardID(0x90)
hw_descriptor.setISA(2)
hw_model = VZMacHardwareModel._hardwareModel(withDescriptor: hw_descriptor)
guard hw_model.isSupported else {
fatalError("VM hardware config not supported (model.isSupported = false)")
}
return hw_model
}
// ...
}
4.4 修改 craftConfiguration 方法
在 craftConfiguration 方法中配置以下关键部分:
4.4.1 Boot Loader + ROM
let bootloader = try vmConfig.platform.bootLoader(nvramURL: nvramURL)
Dynamic(bootloader)._setROMURL(romURL)
configuration.bootLoader = bootloader
4.4.2 SEP 协处理器配置
let homeURL = FileManager.default.homeDirectoryForCurrentUser
var sepstoragePath = homeURL.appendingPathComponent(".tart/vms/vphone/SEPStorage").path
let sepstorageURL = URL(fileURLWithPath: sepstoragePath)
let sep_config = Dynamic._VZSEPCoprocessorConfiguration(storageURL: sepstorageURL)
if let sepromURL {
sep_config.romBinaryURL = sepromURL
}
sep_config.debugStub = Dynamic._VZGDBDebugStubConfiguration(port: 8001)
configuration._setCoprocessors([sep_config.asObject])
重要: SEP 配置不正确会导致 kernel panic。
SEPStorage文件路径必须正确。
4.4.3 平台配置
let pconf = VZMacPlatformConfiguration()
pconf.hardwareModel = try vzHardwareModel_VRESEARCH101()
let serial = Dynamic._VZMacSerialNumber.initWithString("AAAAAA1337")
let identifier = Dynamic.VZMacMachineIdentifier._machineIdentifierWithECID(
0x1111111111111111, serialNumber: serial.asObject
)
pconf.machineIdentifier = identifier.asObject as! VZMacMachineIdentifier
pconf._setProductionModeEnabled(true)
var auxiliaryStoragePath = homeURL.appendingPathComponent(".tart/vms/vphone/nvram.bin").path
let auxiliaryStorageURL = URL(fileURLWithPath: auxiliaryStoragePath)
pconf.auxiliaryStorage = VZMacAuxiliaryStorage(url: auxiliaryStorageURL)
configuration.platform = pconf
4.4.4 键盘与触摸屏
if #available(macOS 14, *) {
let keyboard = VZUSBKeyboardConfiguration()
configuration.keyboards = [keyboard]
}
if #available(macOS 14, *) {
let touch = _VZUSBTouchScreenConfiguration()
configuration._setMultiTouchDevices([touch])
}
4.4.5 显示配置
let graphics_config = VZMacGraphicsDeviceConfiguration()
let displays_config = VZMacGraphicsDisplayConfiguration(
widthInPixels: 1179,
heightInPixels: 2556,
pixelsPerInch: 460
)
graphics_config.displays.append(displays_config)
configuration.graphicsDevices = [graphics_config]
4.5 创建 VM 目录结构
mkdir -p ~/.tart/vms/vphone
# 需要在此目录下创建以下文件:
# - SEPStorage (SEP 存储)
# - nvram.bin (NVRAM / AuxiliaryStorage)
# - disk.img (虚拟磁盘)
4.6 编译 super-tart
cd super-tart
swift build -c release
5. 第二阶段: 构建混合固件
5.1 原理
需要将 cloudOS 26.1 (23B85) 和 iOS 26.1 (iPhone17,3; 23B85) 的组件混合。
来源分配:
| 来源 | 组件 |
|---|---|
| iOS 26.1 (iPhone 16) | SystemVolume, SystemVolumeCanonicalMetadata, OS, StaticTrustCache, RestoreTrustCache, RestoreRamDisk |
| cloudOS 26.1 (PCC) | kernelcache, agx, all_flash, ane, dfu, pmp, sptm, txm 等其余所有 |
5.2 提取与混合脚本 (get_fw.py)
import os
# 1. 下载并解压 iOS 26.1 IPSW (iPhone17,3)
os.system("mkdir -p iPhone17,3_26.1_23B85_Restore")
# 从 ipsw.me 下载并解压到 iPhone17,3_26.1_23B85_Restore/
# 2. 下载并解压 cloudOS 26.1 PCC 固件
# 解压后的 hash 目录名示例:
# 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/
# 3. 从 cloudOS 导入 vphone 相关组件
CLOUDOS_DIR = "399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted"
RESTORE_DIR = "iPhone17,3_26.1_23B85_Restore"
# kernelcache
os.system(f"cp {CLOUDOS_DIR}/kernelcache.* {RESTORE_DIR}")
# 固件子目录
for subdir in ["agx", "all_flash", "ane", "dfu", "pmp"]:
os.system(f"cp {CLOUDOS_DIR}/Firmware/{subdir}/* {RESTORE_DIR}/Firmware/{subdir}")
# sptm, txm 等 im4p 文件
os.system(f"cp {CLOUDOS_DIR}/Firmware/*.im4p {RESTORE_DIR}/Firmware")
# 4. 使用已修改好的 BuildManifest.plist 和 Restore.plist
os.system(f"sudo cp custom_26.1/BuildManifest.plist {RESTORE_DIR}")
os.system(f"sudo cp custom_26.1/Restore.plist {RESTORE_DIR}")
print("Done, grabbed all needed components for restoring")
5.3 修改 BuildManifest.plist
在 Manifest 键下的字典元素中:
- 使用 iOS 26.1 (iPhone 16) 的组件:
SystemVolume,SystemVolumeCanonicalMetadata,OS,StaticTrustCache,RestoreTrustCache,RestoreRamDisk - 使用 PCC 固件 vphone 相关文件: 其余所有项
仓库中提供了最终修改好的文件: BuildManifest.plist
5.4 修改 Restore.plist
- 添加
DeviceMap相关属性或SupportedProductTypes - 修改
SystemRestoreImageFileSystems元素
仓库中提供了最终修改好的文件: Restore.plist
6. 第三阶段: 补丁 AVPBooter (BootROM)
6.1 定位 image4_validate_property_callback
在 IDA Pro 中打开 AVPBooter.vresearch1.bin:
文件路径: /System/Library/Frameworks/Virtualization.framework/Resources/AVPBooter.vresearch1.bin
- 使用 “Text-search (slow!)” 搜索字符串
0x4447 - 找到包含该引用的函数(即
image4_validate_property_callback) - 在该函数的尾声 (epilogue) 处补丁,使其始终返回 0
6.2 补丁方法
参考 steven-michaud 的 gist,在函数返回前插入:
nop ; 0xd503201f
mov x0, #0 ; 0xd2800000
此补丁使得后续可以加载自定义(未签名的)bootloader。
7. 第四阶段: 修改并编译 libirecovery
7.1 为何需要修改
默认的 libirecovery 不支持 vresearch101ap 设备模型,需要添加对应的设备标识。
7.2 操作步骤
# 使用 wh1te4ever 的 fork 版本
git clone https://github.com/wh1te4ever/libirecovery
cd libirecovery
# 编译安装
./autogen.sh
make
sudo make install
7.3 编译 idevicerestore
git clone https://github.com/libimobiledevice/idevicerestore
cd idevicerestore
./autogen.sh
make
sudo make install
8. 第五阶段: 补丁固件组件
8.1 补丁总览
| 组件 | 补丁目的 | 关键偏移 |
|---|---|---|
| iBSS | 绕过签名验证 | 0x9D10, 0x9D14 |
| iBEC | 绕过签名验证 + 启用串口日志 | 0x9D10, 0x9D14, 0x122D4, 0x122D8, 0x24070 |
| LLB | 绕过签名验证 + 串口日志 + SSV 绕过 | 0xA0D8, 0xA0DC, 0x12888, 0x1288C, 0x24990, 0x2BFE8… |
| TXM | 绕过 TrustCache 验证 | 0x2C1F8, 0x2BEF4, 0x2C060 |
| kernelcache | SSV 绕过 (多处) | 0x2476964, 0x23CFDE4, 0xF6D960… |
8.2 通用补丁函数
import struct
def patch(offset, value):
"""在指定偏移处写入补丁值"""
fp.seek(offset)
if isinstance(value, int):
fp.write(struct.pack('<I', value)) # 小端 32 位
elif isinstance(value, str):
fp.write(value.encode('utf-8') + b'\x00')
8.3 补丁 iBSS
# 绕过 image4_validate_property_callback
patch(0x9D10, 0xd503201f) # nop
patch(0x9D14, 0xd2800000) # mov x0, #0
8.4 补丁 iBEC
# 绕过签名验证
patch(0x9D10, 0xd503201f) # nop
patch(0x9D14, 0xd2800000) # mov x0, #0
# 修改 boot-args 为 "serial=3 -v debug=0x2014e %s"
patch(0x122d4, 0xd0000082) # adrp x2, #0x12000
patch(0x122d8, 0x9101c042) # add x2, x2, #0x70
patch(0x24070, "serial=3 -v debug=0x2014e %s")
8.5 补丁 LLB
# 绕过签名验证
patch(0xA0D8, 0xd503201f) # nop
patch(0xA0DC, 0xd2800000) # mov x0, #0
# boot-args
patch(0x12888, 0xD0000082) # adrp x2, #0x12000
patch(0x1288C, 0x91264042) # add x2, x2, #0x990
patch(0x24990, "serial=3 -v debug=0x2014e %s")
# SSV 绕过 — 允许修改后的 rootfs 加载 (snaputil -n 所需)
patch(0x2BFE8, 0x1400000b)
patch(0x2bca0, 0xd503201f)
patch(0x2C03C, 0x17ffff6a)
patch(0x2fcec, 0xd503201f)
patch(0x2FEE8, 0x14000009)
# 绕过 panic
patch(0x1AEE4, 0xd503201f) # nop
8.6 补丁 TXM
# 绕过 TrustCache 验证 — 使未注册的二进制也能执行
# 调用链: FFFFFFF01702B018 → sub_FFFFFFF0170306E4 → ... → sub_FFFFFFF01702EC70
# (基址: 0xFFFFFFF017004000)
patch(0x2c1f8, 0xd2800000) # mov x0, #0 (FFFFFFF0170301F8)
patch(0x2bef4, 0xd2800000) # mov x0, #0 (FFFFFFF01702FEF4)
patch(0x2c060, 0xd2800000) # mov x0, #0 (FFFFFFF017030060)
8.7 补丁 kernelcache
# ========= 绕过 SSV (Signed System Volume) =========
# _apfs_vfsop_mount: 防止 panic "Failed to find the root snapshot..."
patch(0x2476964, 0xd503201f) # nop (FFFFFE000947A964)
# _authapfs_seal_is_broken: 防止 panic "root volume seal is broken..."
patch(0x23cfde4, 0xd503201f) # nop (FFFFFE00093D3DE4)
# _bsd_init: 防止 panic "rootvp not authenticated after mounting..."
patch(0xf6d960, 0xd503201f) # nop (FFFFFE0007F71960)
8.8 IM4P 格式转换 (RAW ↔ IM4P)
补丁流程: IM4P → RAW → 补丁 → IM4P
对于 kernelcache 和 TXM,需要保留 PAYP 结构:
from pathlib import Path
import sys
# === 以 TXM 为例 ===
# 1. 备份原文件
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p "
"iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak")
# 2. IM4P → RAW
os.system("pyimg4 im4p extract -i "
"iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak "
"-o txm.raw")
# 3. 对 txm.raw 执行上述补丁操作 ...
# 4. RAW → IM4P (使用 LZFSE 压缩)
os.system("pyimg4 im4p create -i txm.raw -o txm.im4p -f trxm --lzfse")
# 5. 保留 PAYP 结构
txm_im4p_data = Path(
'iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak'
).read_bytes()
payp_offset = txm_im4p_data.rfind(b'PAYP')
if payp_offset == -1:
print("Couldn't find payp structure !!!")
sys.exit()
with open('txm.im4p', 'ab') as f:
f.write(txm_im4p_data[(payp_offset-10):])
payp_sz = len(txm_im4p_data[(payp_offset-10):])
print(f"payp sz: {payp_sz}")
# 6. 修正 IM4P 头部大小字段
txm_im4p_data = bytearray(open('txm.im4p', 'rb').read())
txm_im4p_data[2:5] = (
int.from_bytes(txm_im4p_data[2:5], 'big') + payp_sz
).to_bytes(3, 'big')
open('txm.im4p', 'wb').write(txm_im4p_data)
# 7. 替换原文件
os.system("mv txm.im4p "
"iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p")
对 iBSS / iBEC 使用 img4tool 转换(无 PAYP):
# iBSS
os.system("tools/img4 -i ...iBSS.vresearch101.RELEASE.im4p.bak -o iBSS.vresearch101.RELEASE")
# ... 补丁 RAW ...
os.system("tools/img4tool -c ...iBSS.vresearch101.RELEASE.im4p -t ibss iBSS.vresearch101.RELEASE")
# iBEC
os.system("tools/img4 -i ...iBEC.vresearch101.RELEASE.im4p.bak -o iBEC.vresearch101.RELEASE")
# ... 补丁 RAW ...
os.system("tools/img4tool -c ...iBEC.vresearch101.RELEASE.im4p -t ibec iBEC.vresearch101.RELEASE")
# LLB
os.system("tools/img4 -i ...LLB.vresearch101.RESEARCH_RELEASE.im4p.bak -o LLB.vresearch101.RESEARCH_RELEASE")
# ... 补丁 RAW ...
os.system("tools/img4tool -c ...LLB.vresearch101.RESEARCH_RELEASE.im4p -t illb LLB.vresearch101.RESEARCH_RELEASE")
9. 第六阶段: 恢复固件
9.1 进入 DFU 模式
在 super-tart 中将虚拟机启动到 DFU 模式。
9.2 执行恢复
idevicerestore -y ./iPhone17,3_26.1_23B85_Restore
9.3 注意事项
- 如果 SEP 配置不正确,会出现 kernel panic
- 恢复完成后虚拟机会自动重启
- 首次重启会因 缺少
/usr/lib/libSystem.B.dylib而在launchd处 panic- 该库位于 Cryptex 分区的
dyld_shared_cache中 - Cryptex 分区无法通过正常恢复流程安装
- 需要通过 SSH Ramdisk 手动注入
- 该库位于 Cryptex 分区的
10. 第七阶段: SSH Ramdisk 修复启动
10.1 获取 SHSH / IM4M
# 获取 SHSH blob
idevicerestore -e -y ./iPhone17,3_26.1_23B85_Restore -t
# 解压
mv shsh/[ECID]-iPhone99,11-26.1.shsh shsh/[ECID]-iPhone99,11-26.1.shsh.gz
gunzip shsh/[ECID]-iPhone99,11-26.1.shsh.gz
# 提取 IM4M
pyimg4 im4m extract -i shsh/[ECID]-iPhone99,11-26.1.shsh -o vphone.im4m
将
[ECID]替换为你的虚拟机 ECID (如0x1111111111111111)。
10.2 生成 IMG4 签名文件
为每个固件组件生成 IMG4:
# iBSS
os.system("tools/img4 -i ...iBSS.vresearch101.RELEASE.im4p.bak -o iBSS.vresearch101.RELEASE")
# ... 补丁 ...
os.system("tools/img4tool -c iBSS.vresearch101.RELEASE.im4p -t ibss iBSS.vresearch101.RELEASE")
os.system("tools/img4 -i iBSS.vresearch101.RELEASE.im4p "
"-o ./Ramdisk/iBSS.vresearch101.RELEASE.img4 -M ./vphone.im4m")
# iBEC
os.system("tools/img4tool -c iBEC.vresearch101.RELEASE.im4p -t ibec iBEC.vresearch101.RELEASE")
os.system("tools/img4 -i iBEC.vresearch101.RELEASE.im4p "
"-o Ramdisk/iBEC.vresearch101.RELEASE.img4 -M vphone.im4m")
# SPTM (无需补丁)
os.system("tools/img4 -i .../sptm.vresearch1.release.im4p "
"-o Ramdisk/sptm.vresearch1.release.img4 -M vphone.im4m -T sptm")
# DeviceTree (无需补丁)
os.system("tools/img4 -i .../DeviceTree.vphone600ap.im4p "
"-o Ramdisk/DeviceTree.vphone600ap.img4 -M vphone.im4m -T rdtr")
# SEP firmware (无需补丁)
os.system("tools/img4 -i .../sep-firmware.vresearch101.RELEASE.im4p "
"-o Ramdisk/sep-firmware.vresearch101.RELEASE.img4 -M vphone.im4m -T rsep")
# TXM (补丁 + PAYP + 签名)
# ... 补丁同第 8.6 节 ...
os.system("pyimg4 img4 create -p txm.im4p -o Ramdisk/txm.img4 -m vphone.im4m")
# kernelcache (补丁 + PAYP + 签名, 注意 Ramdisk 用 rkrn 而非 krnl)
# ... 补丁同第 8.7 节,但 IM4P tag 使用 rkrn ...
os.system("pyimg4 im4p create -i kcache.raw -o krnl.im4p -f rkrn --lzfse")
# ... 保留 PAYP ...
os.system("pyimg4 img4 create -p krnl.im4p -o Ramdisk/krnl.img4 -m vphone.im4m")
10.3 构建自定义 SSH Ramdisk
import glob, subprocess
# 提取原始 ramdisk
os.system("pyimg4 im4p extract -i iPhone17,3_26.1_23B85_Restore/043-53775-129.dmg -o ramdisk.dmg")
# 创建扩容的 ramdisk 副本
os.system("mkdir SSHRD")
os.system("sudo hdiutil attach -mountpoint SSHRD ramdisk.dmg -owners off")
os.system("sudo hdiutil create -size 254m -imagekey diskimage-class=CRawDiskImage "
"-format UDZO -fs APFS -layout NONE -srcfolder SSHRD -copyuid root ramdisk1.dmg")
os.system("sudo hdiutil detach -force SSHRD")
os.system("sudo hdiutil attach -mountpoint SSHRD ramdisk1.dmg -owners off")
# 删除不必要的文件以腾出空间 ...
# 对所有 Mach-O 文件重新签名
target_path = [
"SSHRD/usr/local/bin/*", "SSHRD/usr/local/lib/*",
"SSHRD/usr/bin/*", "SSHRD/bin/*",
"SSHRD/usr/lib/*", "SSHRD/sbin/*", "SSHRD/usr/sbin/*", "SSHRD/usr/libexec/*"
]
for pattern in target_path:
for path in glob.glob(pattern):
if os.path.isfile(path) and not os.path.islink(path):
if "Mach-O" in subprocess.getoutput(f'file "{path}"'):
os.system(f'tools/ldid_macosx_arm64 -S -M -Cadhoc "{path}"')
# 构建 TrustCache
os.system("pyimg4 im4p extract -i "
"iPhone17,3_26.1_23B85_Restore/Firmware/043-53775-129.dmg.trustcache "
"-o trustcache.raw")
os.system("tools/trustcache_macos_arm64 create sshrd.tc SSHRD")
os.system("pyimg4 im4p create -i sshrd.tc -o trustcache.im4p -f rtsc")
os.system("pyimg4 img4 create -p trustcache.im4p -o Ramdisk/trustcache.img4 -m vphone.im4m")
# 封装并签名 ramdisk
os.system("sudo hdiutil detach -force SSHRD")
os.system("sudo hdiutil resize -sectors min ramdisk1.dmg")
os.system("pyimg4 im4p create -i ramdisk1.dmg -o ramdisk1.dmg.im4p -f rdsk")
os.system("pyimg4 img4 create -p ramdisk1.dmg.im4p -o Ramdisk/ramdisk.img4 -m vphone.im4m")
10.4 使用 Ramdisk 启动
创建 boot_rd.sh:
#!/bin/zsh
# 加载 iBSS → iBEC → 进入 Recovery
irecovery -f Ramdisk/iBSS.vresearch101.RELEASE.img4
irecovery -f Ramdisk/iBEC.vresearch101.RELEASE.img4
irecovery -c go
sleep 1
# 加载 SPTM
irecovery -f Ramdisk/sptm.vresearch1.release.img4
irecovery -c firmware
# 加载 TXM
irecovery -f Ramdisk/txm.img4
irecovery -c firmware
# 加载 TrustCache
irecovery -f Ramdisk/trustcache.img4
irecovery -c firmware
# 加载 Ramdisk
irecovery -f Ramdisk/ramdisk.img4
irecovery -c ramdisk
# 加载 DeviceTree
irecovery -f Ramdisk/DeviceTree.vphone600ap.img4
irecovery -c devicetree
# 加载 SEP 固件
irecovery -f Ramdisk/sep-firmware.vresearch101.RELEASE.img4
irecovery -c firmware
# 加载内核并启动
irecovery -f Ramdisk/krnl.img4
irecovery -c bootx
执行:
chmod +x boot_rd.sh
./boot_rd.sh
10.5 验证启动成功
启动后检查:
- super-tart 的第三个窗口应显示 Minecraft Creeper 脸 图案
- 打开 系统信息 (System Information) → USB,应显示 “iPhone Research…”
- 通过 iproxy 连接:
iproxy 2222 22 &
ssh root@127.0.0.1 -p 2222
# 密码: alpine
10.6 重命名 APFS Snapshot
# 在 SSH 连接中执行:
mount_apfs -o rw /dev/disk1s1 /mnt1
# 列出 snapshot
snaputil -l /mnt1
# 输出类似: com.apple.os.update-8AAB8DBA5C8F1F...
# 重命名 snapshot(允许修改根文件系统)
snaputil -n com.apple.os.update-8AAB8DBA5C8F1F... orig-fs /mnt1
umount /mnt1
exit
注意: 这一步是 SSV 绕过的关键——LLB 中的补丁使得
snaputil -n重命名操作成为可能。
11. 第八阶段: 安装 Cryptex 与系统修补
11.1 解密并挂载 Cryptex
import subprocess
# 解密 SystemOS Cryptex (AEA 格式)
key = subprocess.check_output(
"ipsw fw aea --key iPhone17,3_26.1_23B85_Restore/043-54303-126.dmg.aea",
shell=True, text=True
).strip()
os.system(f"aea decrypt -i iPhone17,3_26.1_23B85_Restore/043-54303-126.dmg.aea "
f"-o CryptexSystemOS.dmg -key-value '{key}'")
# 复制 AppOS Cryptex (无需解密)
os.system("cp iPhone17,3_26.1_23B85_Restore/043-54062-129.dmg CryptexAppOS.dmg")
# 挂载
os.system("mkdir CryptexSystemOS CryptexAppOS")
os.system("sudo hdiutil attach -mountpoint CryptexSystemOS CryptexSystemOS.dmg -owners off")
os.system("sudo hdiutil attach -mountpoint CryptexAppOS CryptexAppOS.dmg -owners off")
11.2 传输 Cryptex 到虚拟机
def remote_cmd(cmd):
"""通过 SSH 在虚拟机上执行命令"""
os.system(f"tools/sshpass -p 'alpine' ssh -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -p 2222 root@127.0.0.1 '{cmd}'")
# 挂载根文件系统 (读写)
remote_cmd("/sbin/mount_apfs -o rw /dev/disk1s1 /mnt1")
# 清理旧的 Cryptex 目录
remote_cmd("/bin/rm -rf /mnt1/System/Cryptexes/App")
remote_cmd("/bin/rm -rf /mnt1/System/Cryptexes/OS")
remote_cmd("/bin/mkdir -p /mnt1/System/Cryptexes/App")
remote_cmd("/bin/chmod 0755 /mnt1/System/Cryptexes/App")
remote_cmd("/bin/mkdir -p /mnt1/System/Cryptexes/OS")
remote_cmd("/bin/chmod 0755 /mnt1/System/Cryptexes/OS")
# 通过 SCP 传输 (约需 3 分钟)
SCP_OPTS = ("-q -r -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
"-P 2222")
os.system(f"tools/sshpass -p 'alpine' scp {SCP_OPTS} "
f"CryptexSystemOS/. 'root@127.0.0.1:/mnt1/System/Cryptexes/OS'")
os.system(f"tools/sshpass -p 'alpine' scp {SCP_OPTS} "
f"CryptexAppOS/. 'root@127.0.0.1:/mnt1/System/Cryptexes/App'")
# 创建必要的符号链接
remote_cmd("/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld "
"/mnt1/System/Library/Caches/com.apple.dyld")
remote_cmd("/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld "
"/mnt1/System/DriverKit/System/Library/dyld")
11.3 补丁 seputil
seputil 无法正确找到 gigalocker 文件,需要补丁为始终查找 AA.gl:
# 从虚拟机拉取 seputil
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"root@127.0.0.1:/mnt1/usr/libexec/seputil ./custom_26.1/seputil")
# 补丁: 将 gigalocker 文件名硬编码为 "AA"
fp = open("custom_26.1/seputil", "r+b")
fp.seek(0x1B3F1)
fp.write(b"AA")
fp.close()
# 重新签名
os.system("tools/ldid_macosx_arm64 -S -M -Ksigncert.p12 "
"-Icom.apple.seputil custom_26.1/seputil")
# 推送到虚拟机
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"custom_26.1/seputil 'root@127.0.0.1:/mnt1/usr/libexec/seputil'")
remote_cmd("/bin/chmod 0755 /mnt1/usr/libexec/seputil")
# 重命名 gigalocker 文件
remote_cmd("/sbin/mount_apfs -o rw /dev/disk1s3 /mnt3")
remote_cmd("/bin/mv /mnt3/*.gl /mnt3/AA.gl")
11.4 补丁 launchd_cache_loader
修改 /System/Library/xpc/launchd.plist 后需要补丁 launchd_cache_loader:
# 拉取
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"root@127.0.0.1:/mnt1/usr/libexec/launchd_cache_loader ./custom_26.1/")
# 补丁: 应用 launchd_unsecure_cache=1 效果
fp = open("custom_26.1/launchd_cache_loader", "r+b")
fp.seek(0xB58)
fp.write(struct.pack('<I', 0xd503201f)) # nop
fp.close()
# 重新签名并推送
os.system("tools/ldid_macosx_arm64 -S -M -Ksigncert.p12 "
"-Icom.apple.launchd_cache_loader custom_26.1/launchd_cache_loader")
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"custom_26.1/launchd_cache_loader "
f"'root@127.0.0.1:/mnt1/usr/libexec/launchd_cache_loader'")
remote_cmd("/bin/chmod 0755 /mnt1/usr/libexec/launchd_cache_loader")
11.5 安装 iosbinpack64 (bash/dropbear)
# 传输 iosbinpack64 到虚拟机
os.system(f"tools/sshpass -p 'alpine' scp -q -r -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"jb/iosbinpack64.tar 'root@127.0.0.1:/mnt1'")
# 解压
remote_cmd("/usr/bin/tar --preserve-permissions --no-overwrite-dir "
"-xvf /mnt1/iosbinpack64.tar -C /mnt1")
remote_cmd("/bin/rm /mnt1/iosbinpack64.tar")
启动后初始化 dropbear:
/iosbinpack64/bin/mkdir -p /var/dropbear
/iosbinpack64/bin/cp /iosbinpack64/etc/profile /var/profile
/iosbinpack64/bin/cp /iosbinpack64/etc/motd /var/motd
11.6 配置开机自启服务
创建并注入 3 个 LaunchDaemon plist (bash, dropbear, trollvnc) 到 /System/Library/LaunchDaemons/,并修改 /System/Library/xpc/launchd.plist:
import plistlib
# 传输 plist 文件
for service in ["bash", "dropbear", "trollvnc"]:
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"jb/LaunchDaemons/{service}.plist "
f"'root@127.0.0.1:/mnt1/System/Library/LaunchDaemons'")
remote_cmd(f"/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/{service}.plist")
# 修改 launchd.plist
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"root@127.0.0.1:/mnt1/System/Library/xpc/launchd.plist ./custom_26.1/")
os.system("plutil -convert xml1 custom_26.1/launchd.plist")
# 注入 3 个 LaunchDaemon
for service in ["bash", "dropbear", "trollvnc"]:
target_file = 'custom_26.1/launchd.plist'
source_file = f'jb/LaunchDaemons/{service}.plist'
insert_key = f'/System/Library/LaunchDaemons/{service}.plist'
with open(target_file, 'rb') as ft, open(source_file, 'rb') as fs:
target_data = plistlib.load(ft)
source_data = plistlib.load(fs)
target_data.setdefault('LaunchDaemons', {})[insert_key] = source_data
with open(target_file, 'wb') as f:
plistlib.dump(target_data, f, sort_keys=False)
# 推送修改后的 launchd.plist
os.system(f"tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no "
f"-o UserKnownHostsFile=/dev/null -P 2222 "
f"custom_26.1/launchd.plist "
f"'root@127.0.0.1:/mnt1/System/Library/xpc'")
remote_cmd("/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist")
11.7 关机
remote_cmd("/sbin/halt")
12. 第九阶段: 实现 Metal GPU 加速
12.1 问题
首次正常启动后,Metal 不可用:
-bash-4.4# ./MetalTest
2026-02-08 22:49:02.293 MetalTest[633:9434] device: (null)
2026-02-08 22:49:02.294 MetalTest[633:9434] Metal Not Supported!
虽然 ioreg -l 显示内核已识别 AppleParavirtGPU,但缺少用户空间的 Metal 驱动。
12.2 MetalTest 验证程序
#import <stdio.h>
#import <Metal/Metal.h>
#import <Foundation/Foundation.h>
int main(int argc, char *argv[], char *envp[]) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
NSLog(@"device: %@", device);
if (device) {
NSLog(@"Metal Device Create Success: %@", [device name]);
} else {
NSLog(@"Metal Not Supported!");
}
return 0;
}
12.3 解决方案
- 从 PCC 虚拟机中提取
/System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle - 通过 SSH Ramdisk 放入虚拟 iPhone 的相同路径
- 从 PCC 的
dyld_shared_cache中逆向并重新实现libAppleParavirtCompilerPluginIOGPUFamily.dylib(因为 iPhone 16 的 dsc 中不包含此 dylib)
12.4 验证
-bash-4.4# ./MetalTest
device: <AppleParavirtDevice: 0x102c48fe0>
name = Apple Paravirtual device
Metal Device Create Success: Apple Paravirtual device
13. 第十阶段: Sequoia 上启用触摸交互
13.1 问题
macOS Tahoe 26 上 VZVirtualMachineView 原生支持触摸映射,但 Sequoia 15.x 上不行,需要手动重写鼠标事件。
13.2 解决方案
创建 VirtualMachineView 子类,重写鼠标事件并映射到 _VZMultiTouchEvent:
class VirtualMachineView: VZVirtualMachineView {
var currentTouchSwipeAim: Int64 = 0
override func mouseDown(with event: NSEvent) {
let multiTouchDevices: NSArray = Dynamic(self.virtualMachine)
._multiTouchDevices.asArray!
let locationInWindow = event.locationInWindow
let normalizedPoint = normalizeCoordinate(locationInWindow)
let localPoint = self.convert(locationInWindow, from: nil)
let edgeResult = hitTestEdge(at: localPoint)
self.currentTouchSwipeAim = Int64(edgeResult)
if multiTouchDevices.count as Int > 0 {
guard let touch = VZTouchHelper.createTouch(
withView: self.virtualMachine,
index: 0,
phase: 0, // UITouchPhase.began
location: normalizedPoint.point,
swipeAim: edgeResult,
timestamp: event.timestamp
) else { return }
let touchEvent = Dynamic._VZMultiTouchEvent(
touches: [touch] as NSArray
).asObject
let device = multiTouchDevices.object(at: 0)
Dynamic(device).sendMultiTouchEvents([touchEvent] as NSArray)
}
super.mouseDown(with: event)
}
override func mouseDragged(with event: NSEvent) {
// phase: 1 (UITouchPhase.moved)
// ... 类似 mouseDown 的逻辑 ...
super.mouseDragged(with: event)
}
override func mouseUp(with event: NSEvent) {
// phase: 3 (UITouchPhase.ended)
// ... 类似 mouseDown 的逻辑 ...
super.mouseUp(with: event)
}
// 右键点击 → 双指触摸
override func rightMouseDown(with event: NSEvent) {
// 创建两个 touch (index: 0 和 index: 1)
// ... 实现多点触控 ...
super.rightMouseDown(with: event)
}
override func rightMouseUp(with event: NSEvent) {
// 结束双指触摸
super.rightMouseUp(with: event)
}
}
13.3 坐标标准化
func normalizeCoordinate(_ point: CGPoint) -> NormalizedResult {
let bounds = self.bounds
if bounds.size.width <= 0 || bounds.size.height <= 0 {
return NormalizedResult(point: .zero, isInvalid: true)
}
let localPoint = self.convert(point, from: nil)
var nx = max(0.0, min(1.0, Double(localPoint.x / bounds.size.width)))
var ny = max(0.0, min(1.0, Double(localPoint.y / bounds.size.height)))
if !self.isFlipped { ny = 1.0 - ny }
return NormalizedResult(point: CGPoint(x: nx, y: ny), isInvalid: false)
}
13.4 边缘检测 (用于滑动手势)
func hitTestEdge(at point: CGPoint) -> Int {
let bounds = self.bounds
let distLeft = point.x
let distRight = bounds.size.width - point.x
var minDist: Double
var edgeCode: Int
if distRight < distLeft {
minDist = distRight; edgeCode = 4 // Right
} else {
minDist = distLeft; edgeCode = 8 // Left
}
let topCode = self.isFlipped ? 2 : 1
let bottomCode = self.isFlipped ? 1 : 2
if point.y < minDist {
minDist = point.y; edgeCode = topCode
}
if (bounds.size.height - point.y) < minDist {
minDist = bounds.size.height - point.y; edgeCode = bottomCode
}
return minDist < 32.0 ? edgeCode : 0 // VMViewEdge.none
}
13.5 窗口管理 (ScreenSharingVNC)
class ScreenSharingVNC: VNC {
var virtualMachine: VZVirtualMachine?
private var windowController: NSWindowController?
private func openVMWindow(for vm: VZVirtualMachine) {
let vmView: NSView
if #available(macOS 16.0, *) {
let view = VZVirtualMachineView()
view.virtualMachine = vm
view.capturesSystemKeys = true
vmView = view
} else {
// Sequoia: 使用自定义的 VirtualMachineView
let view = VirtualMachineView()
view.virtualMachine = vm
view.capturesSystemKeys = true
vmView = view
}
let windowSize = NSSize(width: 1179, height: 2556)
let window = NSWindow(
contentRect: NSRect(origin: .zero, size: windowSize),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: false
)
window.contentAspectRatio = windowSize
window.title = "vphone"
window.contentView = vmView
window.center()
let controller = NSWindowController(window: window)
controller.showWindow(nil)
self.windowController = controller
if NSApp == nil { _ = NSApplication.shared }
NSApp.setActivationPolicy(.regular)
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
}
完整源码: ScreenSharingVNC.swift
14. 兼容性
| Mac 型号 | 内存 | macOS 版本 | 状态 |
|---|---|---|---|
| Apple M3 | 16GB | Sequoia 15.7.4 | 已验证 |
| Apple M1 Pro | 32GB | Tahoe 26.3 | 已验证 |
预计任何支持 pccvre 的设备均可运行。
Sequoia vs Tahoe 差异:
- Tahoe 26.x:
VZVirtualMachineView原生支持触摸映射 - Sequoia 15.x: 需要自定义
VirtualMachineView子类重写鼠标事件(见第 13 阶段)
15. 常见问题排查
Q1: SEP 相关 Kernel Panic
原因: SEPStorage 文件路径或内容不正确。
解决: 确认 ~/.tart/vms/vphone/SEPStorage 存在且路径一致。
Q2: 恢复后 launchd panic (缺少 libSystem.B.dylib)
原因: Cryptex 分区未成功恢复,dyld_shared_cache 缺失。
解决: 按第 10-11 阶段通过 SSH Ramdisk 手动注入 Cryptex 文件。
Q3: Metal 不可用 (MTLCreateSystemDefaultDevice 返回 null)
原因: 缺少 AppleParavirtGPUMetalIOGPUFamily.bundle。
解决: 从 PCC VM 中提取该 bundle 并放入 /System/Library/Extensions/。
Q4: 触摸交互无响应 (Sequoia)
原因: Sequoia 的 VZVirtualMachineView 不自动处理触摸事件映射。
解决: 使用自定义 VirtualMachineView 子类(见第 13 阶段)。
Q5: 启动后卡在黑色设置界面
原因: 第一次启动时 Metal 未配置,UI 渲染异常。
解决: 先完成 Metal 支持配置(第 12 阶段),然后重新启动。
Q6: snaputil -n 失败
原因: LLB 中的 SSV 绕过补丁未正确应用。
解决: 重新检查 LLB 补丁偏移 (0x2BFE8, 0x2bca0, 0x2C03C, 0x2fcec, 0x2FEE8)。
16. 参考项目与工具链
| 项目 | 用途 |
|---|---|
| security-pcc | Apple PCC 源码,vrevm 参考实现 |
| super-tart | 修改版 tart VM,支持 DFU/自定义 BootROM |
| vma2pwn | macOS VM 自定义启动链参考 |
| SSHRD_Script | SSH Ramdisk 参考 |
| libirecovery (fork) | 支持 vresearch101ap 的 libirecovery |
| idevicerestore | 固件恢复工具 |
| pyimg4 | IMG4 处理 Python 库 |
| img4tool | IM4P 创建/转换 |
| img4lib | IMG4 处理 C 库 |
| ipsw | IPSW/AEA 固件处理 |
| ldid | 代码签名 |
| trustcache | TrustCache 创建 |
| TrollVNC | VNC 远程控制 |
致谢
- dlevi309 — 触摸交互方案
- khanhduytran0, 34306, asdfugil, verygenericname — Cryptex、设备激活、Ramdisk 启动等
- ma4the, Mard, SwallowS — 多环境测试
免责声明: 本文档仅供安全研究和学习目的。请遵守 Apple 的服务条款和当地法律法规。