Patch the 26.0/26.0.1 IOMobileFramebuffer SwapEnd payload size during CFW install and document the validated 26.0, 26.0.1, and 26.1 host-install matrix.
31 KiB
Firmware Manifest & Component Origins
The erase install firmware is a hybrid of three source sets:
- PCC vresearch101ap — boot chain (LLB/iBSS/iBEC/iBoot) and security monitors (SPTM/TXM)
- PCC vphone600ap — runtime components (DeviceTree, SEP, KernelCache, RecoveryMode)
- iPhone 17,3 — OS image, trust caches, filesystem
The VM hardware identifies as vresearch101ap (BDID 0x90) in DFU mode, so the
BuildManifest identity must use vresearch101ap fields for TSS/SHSH signing. However,
runtime components use the vphone600 variant because its DeviceTree sets MKB dt=1
(allows boot without system keybag), its SEP firmware matches the vphone600 device tree,
and hardware target reports as vphone600ap for proper iPhone emulation.
fw_prepare.sh downloads both IPSWs, merges cloudOS firmware into the iPhone
restore directory, then fw_manifest.py generates the hybrid BuildManifest.
1. Multi-Source IPSW Comparison
Identity Count Overview
| Source | Identities | DeviceClasses |
|---|---|---|
| iPhone 26.0 | 5 | All d47ap |
| iPhone 26.0.1 | 5 | All d47ap |
| iPhone 26.1 | 5 | All d47ap |
| iPhone 26.3 | 5 | All d47ap |
| CloudOS 26.1 | 6 | j236cap, j475dap, vphone600ap (x2), vresearch101ap (x2) |
| KnownWork 26.1 | 5 | All vresearch101ap |
26.0 and 26.0.1 have no matching public PCC vphone600 drop in this repo's tested set, so
it is prepared as iPhone 26.0 or 26.0.1 userspace plus the 26.1 PCC boot/runtime stack.
The 26.0 and 26.0.1 IOMobileFramebuffer dyld-cache patch in CFW install fixes the
only observed GUI ABI mismatch (SwapEnd payload size 0x548 -> 0x560).
CloudOS 26.1 Identity Structure (6 identities)
| Index | DeviceClass | Variant | BuildStyle | Manifest Keys |
|---|---|---|---|---|
| [0] | j236cap | Darwin Cloud Customer Erase Install (IPSW) | RELEASE build | 37 keys (server hardware) |
| [1] | j475dap | Darwin Cloud Customer Erase Install (IPSW) | unknown (no path) | 0 keys (empty placeholder) |
| [2] | vphone600ap | Darwin Cloud Customer Erase Install (IPSW) | RELEASE build | 29 keys (includes UI assets) |
| [3] | vresearch101ap | Darwin Cloud Customer Erase Install (IPSW) | RELEASE build | 20 keys (no UI assets) |
| [4] | vphone600ap | Research Darwin Cloud Customer Erase Install (IPSW) | RESEARCH_RELEASE build | 29 keys (research kernel) |
| [5] | vresearch101ap | Research Darwin Cloud Customer Erase Install (IPSW) | RESEARCH_RELEASE build | 20 keys (research kernel) |
Key distinctions:
- CloudOS[2] vs [4] (vphone600ap): [2] uses RELEASE boot chain + release kernelcache; [4] uses RESEARCH_RELEASE + research kernelcache + txm.iphoneos.research.im4p
- CloudOS[3] vs [5] (vresearch101ap): Same pattern — [3] is RELEASE, [5] is RESEARCH_RELEASE
- vphone600ap has components vresearch101ap lacks: RecoveryMode, AppleLogo, Battery*, RestoreLogo, SEP (vphone600 variant)
- vresearch101ap has only 20 manifest keys (no UI assets, no RecoveryMode)
vphone600ap vs vresearch101ap Key Differences
| Property | vphone600ap | vresearch101ap |
|---|---|---|
| Ap,ProductType | iPhone99,11 | ComputeModule14,2 |
| Ap,Target | VPHONE600AP | VRESEARCH101AP |
| ApBoardID | 0x91 | 0x90 |
| DeviceTree | DeviceTree.vphone600ap.im4p | DeviceTree.vresearch101ap.im4p |
| SEP | sep-firmware.vphone600.RELEASE.im4p | sep-firmware.vresearch101.RELEASE.im4p |
| RecoveryMode | recoverymode@2556~iphone-USBc.im4p | NOT PRESENT |
| MKB dt flag | dt=1 (keybag-less boot OK) | dt=0 (fatal keybag error) |
2. Component Source Tracing
Boot Chain (from PCC vresearch101ap)
| Component | Source Identity | File | Patches Applied |
|---|---|---|---|
| AVPBooter | PCC vresearch1 | AVPBooter*.bin (vm dir) |
DGST validation bypass (mov x0, #0) |
| iBSS | PROD (vresearch101ap release) | Firmware/dfu/iBSS.vresearch101.RELEASE.im4p |
Serial labels + image4 callback bypass |
| iBEC | PROD (vresearch101ap release) | Firmware/dfu/iBEC.vresearch101.RELEASE.im4p |
Serial labels + image4 callback + boot-args |
| LLB | PROD (vresearch101ap release) | Firmware/all_flash/LLB.vresearch101.RELEASE.im4p |
Serial labels + image4 callback + boot-args + rootfs + panic (6 patches) |
| iBoot | RES (vresearch101ap research) | Firmware/all_flash/iBoot.vresearch101.RESEARCH_RELEASE.im4p |
Not patched (only research identity carries iBoot) |
Security Monitors (from PCC, shared across board configs)
| Component | Source Identity | File | Patches Applied |
|---|---|---|---|
| Ap,RestoreSecurePageTableMonitor | PROD | Firmware/sptm.vresearch1.release.im4p |
Not patched |
| Ap,RestoreTrustedExecutionMonitor | PROD | Firmware/txm.iphoneos.release.im4p |
Not patched |
| Ap,SecurePageTableMonitor | PROD | Firmware/sptm.vresearch1.release.im4p |
Not patched |
| Ap,TrustedExecutionMonitor | RES (research) | Firmware/txm.iphoneos.research.im4p |
Trustcache bypass (mov x0, #0 at 0x2C1F8) |
Runtime Components (from PCC vphone600ap)
| Component | Source Identity | File | Patches Applied |
|---|---|---|---|
| DeviceTree | VP (vphone600ap release) | Firmware/all_flash/DeviceTree.vphone600ap.im4p |
Not patched |
| RestoreDeviceTree | VP | Firmware/all_flash/DeviceTree.vphone600ap.im4p |
Not patched |
| SEP | VP | Firmware/all_flash/sep-firmware.vphone600.RELEASE.im4p |
Not patched |
| RestoreSEP | VP | Firmware/all_flash/sep-firmware.vphone600.RELEASE.im4p |
Not patched |
| KernelCache | VPR (vphone600ap research) | kernelcache.research.vphone600 |
25 dynamic patches via KernelPatcher |
| RestoreKernelCache | VP (vphone600ap release) | kernelcache.release.vphone600 |
Not patched (used during restore only) |
| RecoveryMode | VP | Firmware/all_flash/recoverymode@2556~iphone-USBc.im4p |
Not patched |
Important
: KernelCache (installed to disk, patched) uses the research variant. RestoreKernelCache (used during restore process only) uses the release variant. Only vphone600ap identities carry RecoveryMode — vresearch101ap does not.
OS / Filesystem (from iPhone)
| Component | Source | Notes |
|---|---|---|
| OS | iPhone iPhone17,3 erase identity |
iPhone OS image |
| SystemVolume | iPhone erase | Root hash |
| StaticTrustCache | iPhone erase | Static trust cache |
| Ap,SystemVolumeCanonicalMetadata | iPhone erase | Metadata / mtree |
Ramdisk (from PCC)
| Component | Source | Notes |
|---|---|---|
| RestoreRamDisk | PROD (vresearch101ap release) | CloudOS erase ramdisk |
| RestoreTrustCache | PROD | Ramdisk trust cache |
3. Why the Hybrid Approach
Why Not All-vresearch101?
The vresearch101ap device tree sets MKB dt=0, causing a fatal keybag error during boot:
MKB_INIT: dt = 0, bootarg = 0
MKB_INIT: FATAL KEYBAG ERROR: failed to load system bag
REBOOTING INTO RECOVERY MODE.
Also missing the RecoveryMode entry.
Why Not All-vphone600?
The DFU hardware identifies as BDID 0x90 (vresearch101ap). Using vphone600ap identity
(BDID 0x91) fails TSS/SHSH signing and idevicerestore identity matching
(Unable to find a matching build identity).
Solution
vresearch101ap identity fields for DFU/TSS + vphone600 runtime components for a working
boot environment. The vphone600ap device tree sets dt=1, allowing boot without a
pre-existing system keybag:
MKB_INIT: dt = 1, bootarg = 0
MKB_INIT: No system keybag loaded.
The SEP firmware must match the device tree (vphone600 SEP with vphone600 DT).
4. Patched Components Summary
All 6 patched components in fw_patch.py come from PCC (cloudOS):
| # | Component | Source Board | Patch Count | Purpose |
|---|---|---|---|---|
| 1 | AVPBooter | vresearch1 | 1 | Bypass DGST signature validation |
| 2 | iBSS | vresearch101 | 2 | Enable serial output + bypass image4 verification |
| 3 | iBEC | vresearch101 | 3 | Enable serial + bypass image4 + inject boot-args |
| 4 | LLB | vresearch101 | 6 | Serial + image4 + boot-args + rootfs mount + panic handler |
| 5 | TXM | shared (iphoneos) | 1 | Bypass trustcache validation |
| 6 | KernelCache | vphone600 | 25 | APFS seal, MAC policy, debugger, launch constraints, etc. |
All 4 CFW-patched binaries in patchers/cfw.py / cfw_install.sh come from iPhone:
| # | Binary | Source | Purpose |
|---|---|---|---|
| 1 | seputil | iPhone (Cryptex SystemOS) | Gigalocker UUID patch (/%s.gl → /AA.gl) |
| 2 | launchd_cache_loader | iPhone (Cryptex SystemOS) | NOP cache validation check |
| 3 | mobileactivationd | iPhone (Cryptex SystemOS) | Force should_hactivate to return true |
| 4 | launchd.plist | iPhone (Cryptex SystemOS) | Inject bash/dropbear/trollvnc/vphoned/rpcserver_ios daemons |
5. idevicerestore Identity Selection
Source: idevicerestore/src/idevicerestore.c lines 2195-2242
Matching Algorithm
idevicerestore selects a Build Identity by iterating through all BuildIdentities and returning the first match based on two fields:
Info.DeviceClass— case-insensitive match against devicehardware_modelInfo.Variant— substring match against the requested variant string
For DFU erase restore, the search variant is "Erase Install (IPSW)" (defined in idevicerestore.h).
Matching Modes
// Exact match
if (strcmp(str, variant) == 0) return ident;
// Partial match (when exact=0)
if (strstr(str, variant) && !strstr(str, "Research")) return ident;
Critical: Partial matching excludes variants containing "Research". This means:
"Darwin Cloud Customer Erase Install (IPSW)"— matches (contains "Erase Install (IPSW)", no "Research")"Research Darwin Cloud Customer Erase Install (IPSW)"— skipped (contains "Research")
What idevicerestore Does NOT Check
- ApBoardID / ApChipID (used after selection, not for matching)
- Identity index or count (no hardcoded indices)
Conclusion for Single Identity
A BuildManifest with one identity works fine. The loop iterates once, and if DeviceClass and Variant match, it's returned. No minimum identity count required.
6. TSS/SHSH Signing
The TSS request sent to gs.apple.com includes:
ApBoardID = 144(0x90) — must match vresearch101apApChipID = 65025(0xFE01)Ap,ProductType = ComputeModule14,2Ap,Target = VRESEARCH101AP- Digests for all 21 manifest components
Apple's TSS server signs based on these identity fields + component digests. Using vphone600ap identity (BDID 0x91) would fail because the DFU device reports BDID 0x90.
7. Final Design: Single DFU Erase Identity
Since vphone-cli always boots via DFU restore, only one Build Identity is needed.
Identity Metadata (fw_manifest.py)
DeviceClass = vresearch101ap (from C[PROD] deep copy)
Variant = Darwin Cloud Customer Erase Install (IPSW)
Ap,ProductType = ComputeModule14,2
Ap,Target = VRESEARCH101AP
Ap,TargetType = vresearch101
ApBoardID = 0x90
ApChipID = 0xFE01
ApSecurityDomain = 0x01
FDRSupport = False
Source Variable Map
PROD = C[vresearch101ap release] — boot chain, SPTM, RestoreTXM, ramdisk, RestoreTrustCache
RES = C[vresearch101ap research] — iBoot, TXM research
VP = C[vphone600ap release] — DeviceTree, RestoreDeviceTree, SEP, RestoreSEP, RestoreKernelCache, RecoveryMode
VPR = C[vphone600ap research] — KernelCache (patched by fw_patch.py)
I_ERASE = I[iPhone erase] — OS, trust caches, system volume
All 21 Manifest Entries
Boot chain (PROD): LLB, iBSS, iBEC
Research iBoot (RES): iBoot
Security monitors (PROD): Ap,RestoreSPTM, Ap,RestoreTXM, Ap,SPTM
Research TXM (RES): Ap,TXM
Device tree (VP): DeviceTree, RestoreDeviceTree
SEP (VP): SEP, RestoreSEP
Kernel (VPR/VP): KernelCache (research), RestoreKernelCache (release)
Recovery (VP): RecoveryMode
Ramdisk (PROD): RestoreRamDisk, RestoreTrustCache
iPhone OS (I_ERASE): OS, StaticTrustCache, SystemVolume, Ap,SVC Metadata
Full Manifest Component List
LLB ← PROD
iBSS ← PROD
iBEC ← PROD
iBoot ← RES
Ap,RestoreSecurePageTableMonitor ← PROD
Ap,RestoreTrustedExecutionMonitor← PROD
Ap,SecurePageTableMonitor ← PROD
Ap,TrustedExecutionMonitor ← RES
DeviceTree ← VP
RestoreDeviceTree ← VP
SEP ← VP
RestoreSEP ← VP
KernelCache ← VPR (research, patched)
RestoreKernelCache ← VP (release, unpatched)
RecoveryMode ← VP
RestoreRamDisk ← PROD
RestoreTrustCache ← PROD
Ap,SystemVolumeCanonicalMetadata ← I_ERASE
OS ← I_ERASE
StaticTrustCache ← I_ERASE
SystemVolume ← I_ERASE
8. Restore.plist
DeviceMap: [d47ap (iPhone), vphone600ap, vresearch101ap]
ProductTypes: [iPhone17,3, ComputeModule14,1, ComputeModule14,2, Mac14,14, iPhone99,11]
TL;DR
Boot chain = vresearch101 (matches DFU hardware); runtime = vphone600 (keybag-less boot); OS = iPhone.
The firmware is a PCC shell wrapping an iPhone core. The vresearch101 boot chain handles DFU/TSS signing. The vphone600 device tree + SEP + kernel provide the runtime environment. The iPhone userland is patched post-install for activation bypass, jailbreak tools, and persistent SSH/VNC.
9. DeviceTree identity rewrite
The vphone600ap DT carries 13 properties whose values contain the literals
vphone600 / VPHONE600 / iPhone99,11 / vmapple / vresearch. Most are
load-bearing (kernel kext-matching, restore-time identity, IORegistry path
structure) and cannot be rewritten without breaking boot or restore.
DeviceTreePatcher rewrites the 8 that are either userland-facing identity
fields or SoC-generation descriptors with non-binding semantics.
| # | Path | Property | Original | Patched | Slot | Risk |
|---|---|---|---|---|---|---|
| 2 | device-tree |
target-sub-type |
"VPHONE600AP" |
"D47AP" |
12 | HIGHER |
| 3 | device-tree |
compatible[1] |
secondary "iPhone99,11" (multi-string) |
"iPhone17,3" (multi-string, primary "VPHONE600AP" preserved) |
48 | LOW |
| 10 | device-tree/product |
fdr-product-type |
"iPhone99,11" |
"iPhone17,3" |
12 | HIGHER |
| 11 | device-tree/product |
sub-product-type |
"iPhone99,11" |
"iPhone17,3" |
12 | LOW |
| 12 | device-tree/product |
unique-model |
"VPHONE600AP" |
"D47AP" |
12 | LOW |
| 6 | device-tree/arm-io |
device_type |
"vresearch1-io" |
"t8140-io" (matches d47ap) |
14 | MEDIUM |
| 7 | device-tree/arm-io |
soc-generation |
"VResearch1" |
"H17" (matches d47ap) |
11 | MEDIUM-LOW |
| 13 | device-tree/product/vphone600-gestalt-variants |
name (node name) |
"vphone600-gestalt-variants" |
"d47-gestalt-variants" |
27 | LOW-MEDIUM |
Properties intentionally left at vphone600 values (with the empirical reason for each):
device-tree.target-type("VPHONE600") — Tier 2 attempt broke restore; iBoot / restored_external read this and reject the device when the value doesn't match the BuildManifest's signed identity.device-tree.model("iPhone99,11") — Tier 1 attempt broke restore; same reason astarget-type.device-tree.compatible[0]("VPHONE600AP") — IOKit's platform-expert matching at boot binds against the FIRST entry ofcompatible. Rewriting it toD47APforces_PE_init_platform_expertto look for a D47AP-claiming kext, which doesn't exist in our kernelcache. Panic guaranteed.device-tree/arm-io.compatible/device_type/soc-generation(vmapple1 / vresearch1-io / VResearch1) — bind the vmapple1 IO controller kext, GIC, and PCIe drivers. Rename → no kext claims the SoC IO → boot panic.device-tree/arm-io/gic.compatible,device-tree/arm-io/pcie.compatible— same family as arm-io entries.device-tree/product/vphone600-gestalt-variants(node name) — referenced by libMobileGestalt-equivalent code that looks up the subtree by literal node name. Rename breaks the lookup.
Compatible multi-string surgical rewrite (#3). The original blob is:
[0..10] "VPHONE600AP" (11 bytes)
[11] NUL
[12..22] "iPhone99,11" (11 bytes)
[23] NUL
[24..46] "AppleVirtualPlatformARM" (23 bytes)
[47] NUL
Patched (slot length preserved at 48):
[0..10] "VPHONE600AP" ← unchanged: platform-expert bind site
[11] NUL
[12..21] "iPhone17,3"
[22] NUL
[23..45] "AppleVirtualPlatformARM"
[46] NUL
[47] NUL (pad)
AppleVirtualPlatformARM shifts 1 byte earlier but every consumer walks
the blob by NUL-terminators, none depend on a fixed byte offset within
the property. IOKit's platform-expert match against VPHONE600AP (first
entry) is unaffected; the second entry, which userland walks of the
compatible[] list see when enumerating identifiers, now reports
iPhone17,3.
Risk classification.
- LOW (#3, #11, #12) — read only by userland identity APIs (libMobileGestalt, IORegistry queries, App Store device class). No iBoot / restored_external / TSS dependency.
- HIGHER (#2, #10) —
target-sub-typeis in the same restore-signed family astarget-type(already proven to break restore);fdr-product-typeis read by Factory-Data-Restore code paths that may check it against the BuildManifest. If restore fails after a build that enables these, remove them first and try the remaining LOW-risk three in isolation.
Wiring: all 5 patches are declared in
sources/FirmwarePatcher/DeviceTree/DeviceTreePatcher.swift under
propertyPatches. The serializer rebuilds the entire tree on every run,
so each new value is written at the same slot length as the original.
The compatible patch uses a new PropertyValue.bytes(Data) case that
takes a pre-built byte blob (necessary because the multi-string layout
contains embedded NULs).
10. Post-restore DT identity rewrite (EXP-JB-6)
The properties under "known-bad from prior rounds" (root model, root
target-type) cannot be edited at fw_patch time because
restored_external / iBoot's restore mode cross-checks them against the
BuildManifest's signed SupportedProductTypes. But that cross-check
fires only during installation. After restore completes, the boot
path is
AVPBooter (patched)
→ iBSS / iBEC (image4_validate_property_callback bypass patches)
→ LLB (image4 bypass + rootfs bypass patches)
→ iBoot (research) → DT loaded from disk
→ kernel → sysctls populated from DT
The image4 bypass patches we already ship are what allow every fw_patch-time IM4P modification to boot at all — they make the IM4M's stored digest irrelevant on subsequent boots. So an additional IM4P modification applied after restore completes is in-policy with the project's existing trust-chain bypass.
The EXP-JB-6 install step exploits this (in cfw_install_exp.sh
only — JB and DEV variants do NOT run it). While the install pipeline
still has /mnt5 (the preboot volume) mounted in ramdisk mode, it:
- Pulls
/mnt5/<boot-hash>/usr/standalone/firmware/devicetree.img4to the host. - Runs
scripts/patchers/cfw_patch_post_restore_dt.py, which:- Unwraps img4 → IM4P → decompresses LZFSE → DT blob (via pyimg4).
- Applies the three restore-unsafe edits (table below).
- Re-compresses LZFSE → repacks IM4P → repacks IMG4 with the original IM4M.
- Pushes the modified img4 back to the same path.
- The device reboots out of ramdisk; iBoot loads the modified DT;
kernel populates
machine_infofrom the new property values.
| Property | Before (post-Tier-1b state on disk) | After (EXP-JB-6 rewrites to) | Slot |
|---|---|---|---|
device-tree.model |
"iPhone99,11" |
"iPhone17,3" |
12 |
device-tree.target-type |
"VPHONE600" |
"D47" |
10 |
device-tree.compatible |
["VPHONE600AP", "iPhone17,3", "AppleVirtualPlatformARM"] (post-Tier-1b) |
["D47AP", "VPHONE600AP", "AppleVirtualPlatformARM"] (reordered) |
48 |
The compatible rewrite is a reorder, not a replacement: VPHONE600AP
stays in the list (now as the second entry), so IOKit's platform-expert
match against the AppleVMApple1IO kext still binds. compatible[0]
becomes D47AP, which is what userland's hw.model resolves to.
Userland-visible effects after the next boot:
sysctl hw.machine→"iPhone17,3"(was"iPhone99,11")sysctl hw.product→"iPhone17,3"(was"iPhone99,11")sysctl hw.model→"D47AP"(was"VPHONE600AP")- Settings → General → About → Model Identifier reflects the new ProductType.
- Every
MGCopyAnswerkey backed byIOPlatformExpertDeviceproperties picks up the new values.
Idempotence: the patcher reads the existing DT, applies edits, and writes back only if there's a difference. A second run on an already-patched devicetree.img4 detects target-state-already-met and exits without rewriting.
Wiring:
scripts/patchers/cfw_patch_post_restore_dt.py— host-side patcher. Uses pyimg4 for img4↔IM4P+LZFSE round-trip; mirrors the DT format parser/serializer fromDeviceTreePatcher.swift.scripts/cfw_install_exp.sh [EXP-JB-6]— install step that runs after JB-5 (LaunchDaemon deploy) and before the CLEANUP/umount block, in the EXP install script only. Discovers the boot-manifest-hash via the sameget_boot_manifest_hashhelper used by earlier install steps. Tolerates a missing devicetree.img4 with warn-and-skip rather than aborting the install. JB and DEV install scripts do NOT carry this step.
Why this is restore-safe (and the fw_patch-time version was not):
- fw_patch-time path modifies the IM4P bytes that are sent during
restore. iBoot's restore mode reads each IM4P and (via the image4
validation callback the iBSS/iBEC/LLB patches already neuter) accepts
its digest mismatch — but some other path inside
restored_externalenforces the top-levelSupportedProductTypescheck independently and rejects DT.model values that don't appear in that list. - EXP-JB-6 post-restore path never re-runs the BuildManifest check. Subsequent boots validate IM4P contents against the IM4M only — which the image4 bypass patches already wave through. So the same byte change that's fatal at restore is harmless after restore.
11. Build-version rewrite (EXP-JB-7, opt-in)
Opt-in step. EXP-JB-7 runs only when the SPOOF_BUILD environment
variable is set — e.g. make setup_machine EXP=1 SPOOF_BUILD=23F77, or
make cfw_install_exp SPOOF_BUILD=23F77 if you're re-running just the
CFW phase. When omitted/empty the step is skipped entirely and the
build identifier stays at whatever the IPSW shipped. Only the EXP
install script runs this step; JB and DEV variants are not affected.
The iPhone IPSW we install from ships with build identifier 23B85 (iOS
26.1). iOS displays this string in Settings → General → About → "Build"
and exposes it through MGCopyAnswer("BuildVersion"), CoreFoundation's
_CFCopyServerVersionDictionary, App Store telemetry, and every other
framework path that reads /System/Library/CoreServices/SystemVersion.plist.
The build identifier lives in exactly two on-device plist files. Both are plain XML/binary plists (no Apple-side per-file signature), and both live on volumes that are writable at install time:
| Path | Volume | Role |
|---|---|---|
/System/Library/CoreServices/SystemVersion.plist |
rootfs | The canonical source. Read by every userland identity API. |
/private/preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist |
preboot | Cryptex-side copy. Read by Cryptex-aware OS-version queries. |
The EXP-JB-7 install step rewrites the ProductBuildVersion key in both
plists to a target value (currently 23F77):
ProductBuildVersion: "23B85" → "23F77"
ProductVersion (26.1), ProductName (iPhone OS), BuildID,
SystemImageID, and ProductCopyright are left untouched.
Wiring:
scripts/patchers/cfw_patch_build_version.py— host-side plistlib-based rewriter. Auto-detects XML vs binary plist format and preserves it on write. Idempotent — a re-run on an already-patched plist exits without rewriting.scripts/cfw_install_exp.sh [EXP-JB-7]— install step that runs after EXP-JB-6 (post-restore DT) and before CLEANUP, in the EXP install script only. For each of the two plist paths it scp_from's the file to host, runs the patcher with target23F77, scp_to's the file back. Tolerates missing-on-device with warn+continue. JB and DEV install scripts do NOT carry this step.
What this does not flip:
sysctl kern.osversion— populated at boot from a kernel global initialized from boot args, not fromSystemVersion.plist. The kernel image we ship was built against23B78(the PCC vphone600 / vresearch101 build identifier; visible in 621 kext-Info.plist embeddedDTPlatformBuild/DTSDKBuildblobs inside the kernelcache but never read bykern.osversionat runtime). To flipkern.osversionwe'd need to either rebuild the kernelcache with a differentOS_BUILD_VERSIONor patch the kernel boot args path — out of scope for this round.SystemVersionCompat.plist— carries23B34a/19.1, a legacy iOS-19 marker forMacCatalyst-style version queries. Not user- visible, deliberately untouched.- DSC dylib internals — the apparent
23B85byte sequence in DSC chunk.21is a Swift mangled-name UUID-uniquing substring (_TtC11MediaCoreUIP33_98519F523B8515A67EEFBCB0824D82807Counter), not a build identifier. Patching it would corrupt a Swift symbol name.
Order of operations: this step runs after EXP-JB-6 (the DT identity
rewrite) so that any post-restore identity work targeting /mnt5 has
completed before we modify the Cryptex's SystemVersion.plist (which
lives on the same volume).