Skip to content

Latest commit

 

History

History
686 lines (601 loc) · 65.7 KB

File metadata and controls

686 lines (601 loc) · 65.7 KB

Patch Comparison: Regular / Development / Jailbreak / Experimental

EXP is a JB superset. Everything in the baseline tables below that is Y for JB is also Y for EXP. The columns are kept at three variants to avoid noise — the only place EXP and JB diverge is the Experimental additions below, all of which are EXP-only (JB and the other variants are deliberately unaffected). The EXP-only items, taken together:

  • KernelKernelEXPPatcher runs the hv_vmm_present sysctl OID rename plus kernel-internal caller cstring/sandbox-profile-token mangle (formerly wired into JB Group B as JB-26 — moved out).
  • DeviceTree at fw_patch time — 8 identity-rewrite property patches (Tier 1b + 1c) flipping userland-visible identity surfaces toward D47AP / iPhone17,3.
  • DSC user-mode — byte-5 cstring mangle of kern.hv_vmm_present with a sign-in blacklist + per-page slot re-attestation (cfw_patch_hv_vmm_dsc.py), companion to the kernel rename.
  • watchdogd (EXP-JB-3.5) — surgical 2-instruction patch + slot re-attest; forces the cached "am I a VM?" byte to 1 so watchdogd's clean-exit branch runs.
  • Post-restore DT rewrite (EXP-JB-6) — host-side rewrite of devicetree.img4 on the ramdisk's mounted rootfs for the three restore-fatal identity properties (root model, target-type, compatible[0]) that broke restore when applied at fw_patch time.
  • SystemVersion.plist ProductBuildVersion (EXP-JB-7, opt-in) — gated on SPOOF_BUILD=<id>. Rewrites the build identifier in the rootfs and cryptex copies of SystemVersion.plist.

Boot Chain Patches

AVPBooter

# Patch Purpose Regular Dev JB
1 mov x0, #0 DGST signature validation bypass Y Y Y

iBSS

# Patch Purpose Regular Dev JB
1 Serial labels (2x) "Loaded iBSS" in serial log Y Y Y
2 image4_validate_property_callback Signature bypass (b.ne -> NOP, mov x0,x22 -> mov x0,#0) Y Y Y
3 Skip generate_nonce Keep apnonce stable for SHSH (tbz -> unconditional b) - - Y

iBEC

# Patch Purpose Regular Dev JB
1 Serial labels (2x) "Loaded iBEC" in serial log Y Y Y
2 image4_validate_property_callback Signature bypass Y Y Y
3 Boot-args redirect ADRP+ADD -> serial=3 -v debug=0x2014e %s Y Y Y
4 Modern bootx-handoff panic bypass IBootPatcher.patchBootxPrecondition NOPs gate TBZ via structural anchor (no hash/line tied); no-op pre-26.4 Y Y Y
5 Ramdisk boot-args overwrite ramdisk_build.py:patch_ibec_bootargs rewrites string to ... rd=md0 ... wdt=-1 ... (ramdisk-send iBEC only) Y Y Y

LLB

# Patch Purpose Regular Dev JB
1 Serial labels (2x) "Loaded LLB" in serial log Y Y Y
2 image4_validate_property_callback Signature bypass Y Y Y
3 Boot-args redirect ADRP+ADD -> serial=3 -v debug=0x2014e %s Y Y Y
4 Rootfs bypass (5 patches) Allow edited rootfs loading Y Y Y
5 Panic bypass NOP cbnz after mov w8,#0x328 check Y Y Y

TXM

# Patch Purpose Regular Dev JB
1 Trustcache binary-search bypass bl hash_cmp -> mov x0, #0 Y Y Y
2 Selector24 bypass: mov w0, #0xa1 Return PASS (byte 1 = 0) after prologue - Y Y
3 Selector24 bypass: b <epilogue> Skip validation, jump to register restore - Y Y
4 get-task-allow (selector 41|29) bl -> mov x0, #1 - Y Y
5 Selector42|29 shellcode: branch to cave Redirect dispatch stub to shellcode - Y Y
6 Selector42|29 shellcode: NOP pad UDF -> NOP in code cave - Y Y
7 Selector42|29 shellcode: mov x0, #1 Set return value to true - Y Y
8 Selector42|29 shellcode: strb w0, [x20, #0x30] Set manifest flag - Y Y
9 Selector42|29 shellcode: mov x0, x20 Restore context pointer - Y Y
10 Selector42|29 shellcode: branch back Return from shellcode to stub+4 - Y Y
11 Debugger entitlement (selector 42|37) bl -> mov w0, #1 - Y Y
12 Developer mode bypass NOP conditional guard before deny path - Y Y

Kernelcache

Base Patches (All Variants)

# Patch Function Purpose Regular Dev JB
1 NOP tbnz w8,#5 _apfs_vfsop_mount Skip root snapshot sealed-volume check Y Y Y
2 NOP conditional _authapfs_seal_is_broken Skip root volume seal panic Y Y Y
3 NOP conditional _bsd_init Skip rootvp not-authenticated panic Y Y Y
4-5 mov w0,#0; ret _proc_check_launch_constraints Bypass launch constraints Y Y Y
6-7 mov x0,#1 (2x) PE_i_can_has_debugger Enable kernel debugger Y Y Y
8 NOP _postValidation Skip AMFI post-validation Y Y Y
9 cmp w0,w0 _postValidation Force comparison true Y Y Y
10-11 mov w0,#1 (2x) _check_dyld_policy_internal Allow dyld loading Y Y Y
12 mov w0,#0 _apfs_graft Allow APFS graft Y Y Y
13 cmp x0,x0 _apfs_vfsop_mount Skip mount check Y Y Y
14 mov w0,#0 _apfs_mount_upgrade_checks Allow mount upgrade Y Y Y
15 mov w0,#0 _handle_fsioc_graft Allow fsioc graft Y Y Y
16 NOP (3x) handle_get_dev_by_role Bypass APFS role-lookup deny gates for boot mounts Y Y Y
17-26 mov x0,#0; ret (5 hooks) Sandbox MACF ops table Stub 5 sandbox hooks Y Y Y
27 PACIBSP→RET _thread_guard_violation Disable EXC_GUARD delivery (match production behavior) - Y -

JB-Only Kernel Methods (Reference List)

# Group Method Function Purpose JB Enabled
JB-01 A patch_amfi_cdhash_in_trustcache AMFIIsCDHashInTrustCache Always return true + store hash Y
JB-02 A patch_amfi_execve_kill_path AMFI execve kill return site Convert shared kill return from deny to allow (superseded by C21; standalone only) N
JB-03 C patch_cred_label_update_execve _cred_label_update_execve Reworked C21-v3: C21-v1 already boots; v3 keeps split late exits and additionally ORs success-only helper bits 0xC after clearing 0x3F00; still disabled pending boot validation N
JB-04 C patch_hook_cred_label_update_execve sandbox mpo_cred_label_update_execve wrapper (ops[18] -> sub_FFFFFE00093BDB64) Faithful upstream C23 trampoline: copy VSUID/VSGID owner state into pending cred, set P_SUGID, then branch back to wrapper Y
JB-05 C patch_kcall10 sysent[439] (SYS_kas_info replacement) Rebuilt ABI-correct kcall cave: target + 7 args -> uint64 x0; re-enabled after focused dry-run validation Y
JB-06 B patch_post_validation_additional _postValidation (additional) Disable SHA256-only hash-type reject Y
JB-07 C patch_syscallmask_apply_to_proc syscallmask apply wrapper (_proc_apply_syscall_masks path) Faithful upstream C22: mutate installed Unix/Mach/KOBJ masks to all-ones via structural cave, then continue into setter; distinct from NULL-mask alternative Y
JB-08 A patch_task_conversion_eval_internal _task_conversion_eval_internal Allow task conversion Y
JB-09 A patch_sandbox_hooks_extended Sandbox MACF ops (extended) Stub remaining 30+ sandbox hooks (incl. IOKit 201..210) Y
JB-10 A patch_iouc_failed_macf IOUC MACF shared gate A5-v2: patch only the post-mac_iokit_check_open deny gate (CBZ W0, allow -> B allow) and keep the rest of the IOUserClient open path intact Y
JB-11 B patch_proc_security_policy _proc_security_policy Bypass security policy Y
JB-12 B patch_proc_pidinfo _proc_pidinfo Allow pid 0 info Y
JB-13 B patch_convert_port_to_map _convert_port_to_map_with_flavor Skip kernel map panic Y
JB-14 B patch_bsd_init_auth _bsd_init rootauth-failure branch Ignore FSIOC_KERNEL_ROOTAUTH failure in bsd_init; same gate as base patch #3 when layered Y
JB-15 B patch_dounmount _dounmount Allow unmount via upstream coveredvp cleanup-call NOP Y
JB-16 B patch_io_secure_bsd_root AppleARMPE::callPlatformFunction ("SecureRootName" return select), called from IOSecureBSDRoot Force "SecureRootName" policy return to success without altering callback flow; implementation retargeted 2026-03-06 Y
JB-17 B patch_load_dylinker _load_dylinker Skip strict LC_LOAD_DYLINKER == "/usr/lib/dyld" gate Y
JB-18 B patch_mac_mount ___mac_mount Upstream mount-role wrapper bypass (tbnz NOP + role-byte zeroing) Y
JB-19 B patch_nvram_verify_permission _verifyPermission (NVRAM) Allow NVRAM writes Y
JB-20 B patch_shared_region_map _shared_region_map_and_slide_setup Force root-vs-process-root mount compare to succeed before Cryptex fallback Y
JB-21 B patch_spawn_validate_persona _spawn_validate_persona Upstream dual-cbz persona helper bypass Y
JB-22 B patch_task_for_pid _task_for_pid Allow task_for_pid via upstream early pid == 0 gate NOP Y
JB-23 B patch_thid_should_crash _thid_should_crash Prevent GUARD_TYPE_MACH_PORT crash Y
JB-24 B patch_vm_fault_enter_prepare _vm_fault_enter_prepare Force cs_bypass fast path in runtime fault validation Y
JB-25 B patch_vm_map_protect _vm_map_protect Skip upstream write-downgrade gate in vm_map_protect Y

EXP-Only Kernel Methods (Reference List)

Runs in KernelEXPPatcher.findAll() (chained after KernelPatcher + KernelJBPatcher for the .exp variant only — JB and other variants do NOT execute these).

# Group Method Function Purpose EXP Enabled
EXP-01 B patch_hv_vmm_rename sysctl OID name cstring "hv_vmm_present""Xv_vmm_present" (Part A) + every kernel-internal occurrence of kern.hv_vmm_present cstring/sandbox-profile token mangled at byte 5 (Part B) Rename the kern.hv_vmm_present OID's name in place ('h' → 'X' at offset 0 of the 14-byte cstring). After this: sysctlbyname("kern.hv_vmm_present") returns ENOENT; sysctlbyname("kern.Xv_vmm_present") returns the original int value (1). Part B mangles every kernel-internal caller — AMFI, IOCryptoAcceleratorFamily, sandbox-profile token, apfs — so they keep hitting the renamed OID. Companion to the user-mode blacklist-flip mangle in cfw_patch_hv_vmm_dsc.py. Y

CFW Installation Patches

Binary Patches Applied Over SSH Ramdisk

# Patch Binary Purpose Regular Dev JB
1 /%s.gl -> /AA.gl seputil Gigalocker UUID fix Y Y Y
2 NOP cache validation launchd_cache_loader Allow modified launchd.plist Y Y Y
3 mov x0,#1; ret mobileactivationd Activation bypass Y Y Y
4 Plist injection launchd.plist bash/dropbear/trollvnc/vphoned daemons Y Y Y
5 b (skip jetsam guard) launchd Prevent jetsam panic on boot - Y Y
6 LC_LOAD_DYLIB injection launchd Load short alias /b (copy of launchdhook.dylib) at launch - - Y
7 cstring byte 5 mangle 'h' → 'X' ("kern.hv_vmm_present""kern.Xv_vmm_present") + per-page slot-hash re-attestation, BLACKLIST semantics — EXP only DSC dylibs Companion to EXP kernel rename (KernelEXPPatcher.patchHvVmmRename). The mangle is applied to every DSC dylib EXCEPT those in DONT_PATCH_INSTALL_NAMES (sign-in / device-likeness consumers, ~15 entries). Patched dylibs query kern.Xv_vmm_present and get the truthful 1 (graphics / accel passthrough). Blacklisted dylibs keep the original cstring, hit ENOENT on the renamed kernel, cache 0, lie about VM presence. On codeSigningMonitor == 2 hardware the byte-mangle alone causes CODESIGNING/Invalid Page SIGKILL because TXM enforces per-page hashes; the re-attestation pass recomputes the SHA-256 slot in the chunk's CS_CodeDirectory for every modified 16 KiB page. See scripts/patchers/cfw_dsc_codesign.py and cfw_patch_hv_vmm_dsc.py. - - -
8 (removed — was: standalone-binary mangle in 6 rootfs Mach-Os via SSH) n/a Removed in the blacklist-flip redesign. With the EXP kernel rename in place, the 6 rootfs binaries (MobileActivationMigrator, CheckerBoard, StoreKitUISceneService, storekitd, appstored, CorePrescriptionService) get the desired "cache 0 / not in a VM" behavior for free: they keep their original cstring, hit ENOENT on the renamed kernel sysctl, defensive cbnz w0, skip leaves the cached byte at BSS-zero. No SSH-time standalone patch needed. - - -

Installed Components

# Component Description Regular Dev JB
1 Cryptex SystemOS + AppOS Decrypt AEA + mount + copy to device Y Y Y
2 GPU driver AppleParavirtGPUMetalIOGPUFamily bundle Y Y Y
3 iosbinpack64 Jailbreak tools (base set) Y Y Y
4 iosbinpack64 dev overlay Replace rpcserver_ios with dev build - Y -
5 vphoned vsock HID/control daemon (built + signed) Y Y Y
6 LaunchDaemons bash/dropbear/trollvnc/rpcserver_ios/vphoned plists Y Y Y
7 Procursus bootstrap Bootstrap filesystem + optional Sileo deb - - Y
8 BaseBin hooks systemhook.dylib / launchdhook.dylib / libellekit.dylib -> /cores/ plus /b alias for launchdhook.dylib - - Y
9 TweakLoader.dylib Lean user-tweak loader built from source and installed to /var/jb/usr/lib/TweakLoader.dylib - - Y

kern.hv_vmm_present user-mode patcher (EXP only)

Companion to the EXP kernel patcher (KernelEXPPatcher.patchHvVmmRename). Mangles byte 5 of every kern.hv_vmm_present cstring inside DSC dylibs EXCEPT those in DONT_PATCH_INSTALL_NAMES (sign-in / device-likeness consumers, ~15 entries). Patched dylibs query the renamed OID and get the truthful 1 (graphics + accel passthrough); blacklisted dylibs keep the original cstring, hit ENOENT on the renamed kernel, and defensively cache 0 ("not running on a VM") for sign-in / device-attestation surfaces. Source-of-truth research: research/hv_vmm_present_usermode_xrefs.md.

JB and other variants are NOT affected by this patcher.

Patch shape (every site) — cstring mangle:

Before (cstring section bytes, 20 bytes total):
    "kern.hv_vmm_present\0"
    6B 65 72 6E 2E 68 76 5F 76 6D 6D 5F 70 72 65 73 65 6E 74 00

After (1 byte change at offset 0):
    "Xern.hv_vmm_present\0"
    58 65 72 6E 2E 68 76 5F 76 6D 6D 5F 70 72 65 73 65 6E 74 00
    ^^

The kernel's name-to-MIB translation fails with ENOENT when the caller asks for "Xern.hv_vmm_present", so sysctlbyname returns -1. The canonical post-call check (cbnz w0, skip or cmp w0,#0 ; b.ne skip) then takes the skip-cache path; the cached "is_vmm" byte stays at its initial value (BSS-zero = 0).

We don't modify executable code at all — only one byte of read-only string data. The kernel call still happens (with the wrong name), so any sysctl-tracing infrastructure can still see activity.

Idempotent: a re-scan for the literal "kern.hv_vmm_present\0" finds no occurrences in already-mangled dylibs, so the patcher does no work on a re-run.

DSC-side patches — driven by an explicit whitelist (PATCH_INSTALL_NAMES in scripts/patchers/cfw_patch_hv_vmm_dsc.py) applied to chunks under SystemOS/System/Library/Caches/com.apple.dyld/. Comment a line in the whitelist to skip that dylib on the next install — useful for bisecting which consumer is responsible for an observable change.

Dylib Component role (paraphrased)
usr/lib/libMobileGestalt.dylib Backs MGCopyAnswer("hv-vmm-present") — highest fan-in
PrivateFrameworks/AAAFoundation.framework/AAAFoundation Apple ID anti-abuse plumbing
PrivateFrameworks/AuthKit.framework/AuthKit Sign-in-with-Apple-ID / iCloud auth
PrivateFrameworks/IDSFoundation.framework/IDSFoundation Apple Identity Service core (iMessage / FaceTime backbone)
PrivateFrameworks/DeviceIdentity.framework/DeviceIdentity Device-binding / device class identity
PrivateFrameworks/DeviceCheckInternal.framework/... DeviceCheck attestation
PrivateFrameworks/MobileActivation.framework/... Activation flow
PrivateFrameworks/ApplePushService.framework/... APNS client (claims device characteristics on connect)
PrivateFrameworks/AppStoreUtilities.framework/... Store / IAP support
PrivateFrameworks/CorePrescription.framework/... Health prescription store sync gate
PrivateFrameworks/CoreCDP.framework/CoreCDP CDP (cloud key-vault / iCloud Drive plumbing)
PrivateFrameworks/EmailFoundation.framework/... Mail account heuristics
PrivateFrameworks/PhotoFoundation.framework/... Photos asset visibility heuristics
PrivateFrameworks/FindMyBase.framework/FindMyBase Find My anti-spoof
PrivateFrameworks/AirPlaySupport.framework/... AirPlay receiver gate
PrivateFrameworks/TrialServer.framework/TrialServer A/B / trial-rollout exclude-VM gate
PrivateFrameworks/VisionKitCore.framework/VisionKitCore VisionKit
PrivateFrameworks/DVTInstrumentsUtilities.framework/... Xcode Instruments support
PrivateFrameworks/WatchdogServiceManagement.framework/... Watchdog manager
Frameworks/CoreVideo.framework/CoreVideo CoreVideo pipeline

Standalone-binary patches (6 files, applied to the device rootfs over SSH)

Path Role
/System/Library/DataClassMigrators/MobileActivationMigrator.migrator/MobileActivationMigrator Activation migration helper
/Applications/CheckerBoard.app/CheckerBoard Apple internal accessibility test app
/Applications/StoreKitUISceneService.app/StoreKitUISceneService StoreKit UI host
/System/Library/Frameworks/StoreKit.framework/Support/storekitd StoreKit / IAP daemon
/System/Library/PrivateFrameworks/AppStoreDaemon.framework/Support/appstored App Store daemon
/System/Library/PrivateFrameworks/CorePrescription.framework/XPCServices/CorePrescriptionService.xpc/CorePrescriptionService CorePrescription XPC service

Explicitly NOT patched (compute / accel — patching here turns off VM fast-paths that exist so the lib doesn't try to touch real silicon ANE / AGX / hardware codecs):

System/Library/Frameworks/CoreML.framework/CoreML
System/Library/PrivateFrameworks/Espresso.framework/Espresso
System/Library/PrivateFrameworks/AppleNeuralEngine.framework/AppleNeuralEngine
System/Library/PrivateFrameworks/CoreRE.framework/CoreRE
System/Library/PrivateFrameworks/RenderBox.framework/RenderBox
System/Library/PrivateFrameworks/WebGPU.framework/WebGPU
System/Library/PrivateFrameworks/caulk.framework/caulk
System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/IOSurfaceAccelerator
System/Library/ExtensionKit/Extensions/HostInferenceProviderService.appex/HostInferenceProviderService

Wiring

  • scripts/patchers/cfw_patch_hv_vmm.py — standalone cstring patcher (used for the on-device files): finds the "kern.hv_vmm_present\0" cstring in the Mach-O's __cstring section and rewrites its first byte ('k''X').
  • scripts/patchers/cfw_dsc_chunks.py — chunked-DSC byte-level helper (DSCChunks(chunks_dir)): vmaddr↔chunk-fileoff mapping, cstring scan over executable mappings, byte read/write at a vmaddr, and Mach-O header walk-back to resolve a vmaddr to the dylib install name (LC_ID_DYLIB).
  • scripts/patchers/cfw_patch_hv_vmm_dsc.py — DSC-native orchestrator. No external ipsw dependency. For every "kern.hv_vmm_present\0" occurrence in any executable mapping, walks back to the containing dylib's Mach-O header, reads LC_ID_DYLIB, and — if the install name is in the explicit PATCH_INSTALL_NAMES whitelist — rewrites the first byte of the cstring through DSCChunks.write_at_vma. Pure Python. Whitelist-based by design so an operator can comment out individual entries to bisect.
  • scripts/patchers/cfw.py patch-hv-vmm <binary> — standalone-Mach-O subcommand (used for the 6 on-device files).
  • scripts/patchers/cfw.py patch-hv-vmm-dsc <chunks_dir> — DSC subcommand (used while the SystemOS Cryptex DMG is still mounted on the host, before the device copy).
  • scripts/patch_hv_vmm_userland.sh — thin wrapper used by the install scripts.
  • scripts/cfw_install_exp.sh — EXP install script. Pre-step before invoking cfw_install.sh: decrypts the SysOS Cryptex into the cache location cfw_install.sh already uses, mounts it, applies the DSC patch, unmounts. The unmodified cfw_install.sh then sees the cached (already-patched) DMG. Standalone watchdogd is patched later via SSH at step [EXP-JB-3.5].
  • scripts/cfw_install_jb.sh and scripts/cfw_install_dev.sh — unchanged from pre-experimental baseline. Neither runs the DSC patcher.

kern.hv_vmm_present kernel patcher — Part A + Part B (EXP only)

The KernelEXPPatchHvVmmRename Swift patcher (in KernelEXPPatcher.findAll(), chained after KernelPatcher and KernelJBPatcher for the .exp variant only) renames the sysctl OID and rewrites every kernel-internal occurrence of the old name so kexts continue to find it under the new name. Two parts. JB and other variants do NOT run this patcher.

Part A — OID name rename. Finds the OID's oid_name cstring as the NUL-delimited bytes \0hv_vmm_present\0 (exactly one match required in the kernelcache; on iPhone17,3 / iOS 26.1 this lives at file offset 0x964e0 inside com.apple.kernel). Flips byte 0 of the cstring 'h' (0x68) → 'X' (0x58). After the patch, the kernel's sysctl_register_oid keeps the OID's MIB and value (1) intact but the name resolver returns ENOENT for kern.hv_vmm_present and returns 1 for kern.Xv_vmm_present.

Part B — kernel-internal caller mangle. After Part A, any kernel-side sysctlbyname("kern.hv_vmm_present", …) call gets ENOENT and falls into the caller's "not in a VM" branch — which on the bring-up build caused AMFI to panic with AMFI: No PMGR? (ConfigurationSettings.cpp:388) during ramdisk boot. Part B mangles every kernel-internal occurrence of the kern.hv_vmm_present name so callers continue to find the renamed OID. The mangle flips byte 5 of the inner cstring ('h' after kern.) → 'X', producing kern.Xv_vmm_present.

Two byte-aligned forms are searched, both anchored at the kern.hv_vmm_present substring:

Form Needle Where it lives Mangle delta within needle
(i) NUL-delimited cstring \0kern.hv_vmm_present\0 __TEXT,__cstring of any kext that calls sysctlbyname by full name +6 (skip leading NUL + 5)
(ii) Sandbox-profile name token kern.hv_vmm_present\x0f Inside a compiled sandbox-profile blob within com.apple.security.sandbox. The \x0f byte is the sandbox-profile end-of-name marker; the token has no leading NUL. +5

On iPhone17,3 / iOS 26.1 / 23B85 the universe is 5 occurrences (verified by raw substring scan over the kernelcache buffer):

File offset Fileset entry Form
0x541d56 com.apple.driver.AppleMobileFileIntegrity (i) cstring
0x81bdc3 com.apple.iokit.IOCryptoAcceleratorFamily (i) cstring
0xa6618b com.apple.security.sandbox (ii) sandbox-profile name token
0xbb0d55 com.apple.security.sandbox (i) cstring
0xbce1f9 com.apple.filesystems.apfs (i) cstring

Part B emits one patch record per match (5 total, plus Part A's 1) under patch IDs kernelcache_exp.hv_vmm_internal_caller_mangle and kernelcache_exp.hv_vmm_oid_rename. Idempotent: a re-run detects already-mangled bytes (kern.Xv_vmm_present instead of kern.hv_vmm_present) and reports the patch as already applied.

Note on the sandbox-profile occurrence. This was missed by the original Part B because its needle required NUL on both sides. The sandbox-profile blob stores OID names as TLV-framed tokens where the trailing byte is \x0f (sandbox EOT) rather than a NUL. Without the second needle, sandboxed callers that interpret the profile's kern.hv_vmm_present-matching rule would still match against the OLD name, while the OID itself has been renamed — so the rule's ALLOW/DENY/audit action would never fire. With the second needle, the rule's name token is rewritten to kern.Xv_vmm_present and sandboxed callers that hit the renamed OID match the (rewritten) rule as intended. Covered occurrence verified on iPhone17,3 / iOS 26.1 / 23B85 at file offset 0xa6618b.

watchdogd surgical hv_vmm_present cache patch (EXP only)

Why a dedicated patch. After the EXP kernel-side OID rename (KernelEXPPatchHvVmmRename), sysctlbyname("kern.hv_vmm_present", ...) returns ENOENT on this image. /usr/libexec/watchdogd caches that answer at startup. On ENOENT the cached byte stays at its BSS-zero default (0) and the downstream cbz w0, ... at the IOWatchdog-lookup site (+0x58e0) takes a branch into a _os_crash wrapper that does brk #1. launchd's _PanicOnCrash → PanicOnConsecutiveCrash = true flag in com.apple.watchdogd.plist escalates the SIGTRAP to a kernel panic. The cstring-mangle approach used elsewhere doesn't apply here because we want this binary to behave as if the sysctl returned 1, not as if it returned ENOENT.

Patch shape. Two-instruction surgical edit at every site in watchdogd that has the canonical caching shape:

adrp x0, <page>
add  x0, x0, #<off>          ; "kern.hv_vmm_present"
...arg setup...
bl   _sysctlbyname
cbnz w0, <skip>              ; <-- patched: NOP
ldur w8, [x29, #-4]
cmp  w8, #0
cset wN, ne                  ; <-- patched: mov wN, #1
adrp xM, <page>
strb wN, [xM, #<imm>]        ; cached "am I a VM?" byte

Net effect: the cached byte is forced to 1 regardless of the sysctl result, and watchdogd's pre-existing "detected virtual machine environment, exiting..." clean-exit branch runs instead of the trap path. Two functions in watchdogd match this shape on iPhone17,3 / iOS 26.1; both are patched.

Code signing. The byte edit invalidates the SHA-256 slot hashes for the 4 KiB pages containing the modifications in watchdogd's own CS_CodeDirectory. The patcher recomputes those slot hashes in place via cfw_macho_codesign.reattest_modified_offsets (4 KiB page size read from the CD, correct tail-slot length, all present CDs). The resulting CD mutation also changes the cdHash, but the existing JB kernel patch patch_amfi_cdhash_in_trustcache accepts any cdHash, so AMFI's trust-cache check still passes at execve. The patcher does NOT re-sign with ldid — preserving the original Apple-issued code-signing identifier (com.apple.watchdogd) is required for launchd's boot-task identity validation; an earlier attempt to re-sign other rootfs binaries with ldid_sign tripped this check on mobile_obliterator.

Wiring.

  • scripts/patchers/cfw_macho_codesign.py — standalone-Mach-O page-hash re-attestation (parallel to cfw_dsc_codesign.py but parses LC_CODE_SIGNATURE directly, uses page size from the CD header, handles short tail slot, updates every present CD).
  • scripts/patchers/cfw_patch_watchdogd.py — capstone-anchored pattern matcher + Keystone-assembled 2-insn patch + slot reattest. Idempotent.
  • scripts/patchers/cfw.py patch-watchdogd <binary> — CLI subcommand.
  • scripts/patch_hv_vmm_userland.sh watchdogd <binary> — thin shim used by the install script.
  • scripts/cfw_install_exp.sh — invokes the patcher at step [EXP-JB-3.5] on the live /mnt1/usr/libexec/watchdogd (scp-down, patch, scp-up, chmod 0755). JB and DEV install scripts do NOT run this step.

DeviceTree identity properties at fw_patch time (EXP only)

DeviceTreePatcher carries two property-patch lists: basePropertyPatches (4 entries — serial-number, home-button-type, artwork-device-subtype, island-notch-location) applied for every variant, and identityPropertyPatches (8 entries) applied only when includeIdentityPatches is true, which FirmwarePipeline sets exactly when variant == .exp.

The 8 EXP-only identity properties (no restore-fatal ones — those go through EXP-JB-6 post-restore):

# Node path Property Old → New Risk
1 device-tree target-sub-type VPHONE600APD47AP HIGHER
2 device-tree compatible[1] iPhone99,11iPhone17,3 (slot-preserving) LOW
3 device-tree/product fdr-product-type iPhone99,11iPhone17,3 HIGHER
4 device-tree/product sub-product-type iPhone99,11iPhone17,3 LOW
5 device-tree/product unique-model VPHONE600APD47AP LOW
6 device-tree/arm-io device_type vresearch1-iot8140-io MEDIUM
7 device-tree/arm-io soc-generation VResearch1H17 MEDIUM-LOW
8 device-tree/product/vphone600-gestalt-variants name (node rename) vphone600-gestalt-variantsd47-gestalt-variants LOW-MEDIUM

Root model and root target-type are deliberately NOT in this list — both have been empirically shown to break restore. Those edits run post-restore as EXP-JB-6.

Post-restore DT identity rewrite (EXP-JB-6, EXP only)

After the restore daemon's BuildManifest identity check has passed, cfw_install_exp.sh step [EXP-JB-6] scp's devicetree.img4 down from the mounted rootfs (/mnt5/<boot-hash>/usr/standalone/firmware/), runs scripts/patchers/cfw_patch_post_restore_dt.py, and scp's the rewritten img4 back. The Python patcher unwraps the IM4P via pyimg4, parses the DT flat-binary, rewrites three restore-fatal root properties, and repacks preserving the IMG4's original IM4M ticket. The iBSS/iBEC/LLB image4_validate_property_callback bypass (existing JB patch) accepts the modified payload at next boot.

# Property Old → New
1 root model iPhone99,11iPhone17,3
2 root target-type VPHONE600D47
3 root compatible reorder ["VPHONE600AP", "iPhone99,11", "AVP-ARM"]["D47AP", "VPHONE600AP", "AVP-ARM"] (keeps VPHONE600AP in second slot so IOKit's AppleVMApple1IO kext binding still resolves; userland reads only the first entry for hw.model)

Idempotent. Skipped if already-rewritten DT is detected.

SystemVersion.plist ProductBuildVersion rewrite (EXP-JB-7, EXP only, opt-in)

Gated on the SPOOF_BUILD env var. When cfw_install_exp.sh is invoked with e.g. SPOOF_BUILD=23F77, step [EXP-JB-7] runs scripts/patchers/cfw_patch_build_version.py (plistlib-based, format-preserving) on both the rootfs and cryptex copies of SystemVersion.plist to rewrite the ProductBuildVersion key to the specified id. Without SPOOF_BUILD, the step is skipped and the build version stays at the original IPSW value.

File Touched if SPOOF_BUILD=<id>
/mnt1/System/Library/CoreServices/SystemVersion.plist (rootfs) Y
/mnt5/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist (cryptex) Y

kern.osversion is unaffected — that comes from a kernel global initialized from boot args, not from this plist. Userland MG cache picks up the new build identifier on first boot after the gestalt cache rebuild.

CFW Installer Flow Matrix (Script-Level)

Flow Item Regular (cfw_install.sh) Dev (cfw_install_dev.sh) JB (cfw_install_jb.sh) EXP (cfw_install_exp.sh)
Base CFW phases (1/7 -> 7/7) Runs directly Runs directly Runs via CFW_SKIP_HALT=1 zsh cfw_install.sh Runs via CFW_SKIP_HALT=1 zsh cfw_install.sh
Dev overlay (rpcserver_ios replacement) - Y (apply_dev_overlay) - -
SSH readiness wait before install Y (wait_for_device_ssh_ready) - Y (inherited from base run) Y (inherited from base run)
launchd jetsam patch (patch-launchd-jetsam) - Y (base-flow injection) Y (JB-1) Y (JB-1)
launchd dylib injection (inject-dylib /b) - - Y (JB-1) Y (JB-1)
Procursus bootstrap deployment - - Y (JB-2) Y (JB-2)
BaseBin hook deployment (*.dylib -> /mnt1/cores) - - Y (JB-3) Y (JB-3)
First-boot JB finalization (vphone_jb_setup.sh) - - Y (post-boot) Y (post-boot)
DSC pre-patch (kern.hv_vmm_present byte-5 mangle + slot reattest) - - - Y (pre-step, before base CFW)
watchdogd surgical 2-insn patch + slot reattest - - - Y (EXP-JB-3.5)
Post-restore DT identity rewrite (devicetree.img4) - - - Y (EXP-JB-6)
SystemVersion.plist ProductBuildVersion rewrite - - - Y (EXP-JB-7, opt-in via SPOOF_BUILD)
Additional input resources cfw_input cfw_input + resources/cfw_dev/rpcserver_ios cfw_input + cfw_jb_input cfw_input + cfw_jb_input
Extra tool requirement beyond base - - zstd zstd
Halt behavior Halts unless CFW_SKIP_HALT=1 Halts unless CFW_SKIP_HALT=1 Always halts after JB phases Always halts after EXP phases

Summary

Component Regular Dev JB EXP
AVPBooter 1 1 1 1
iBSS 2 2 3 3
iBEC 4 4 4 4
LLB 6 6 6 6
TXM 1 12 12 12
Kernel (base) 28 29 28 28
Kernel (JB methods) - - 59 59
Kernel (EXP methods, hv_vmm) - - - 6
DeviceTree base properties 4 4 4 4
DeviceTree EXP identity properties - - - 8
Boot chain total 46 58 117 131
CFW binary patches (base) 4 5 6 6
CFW EXP-only steps - - - 4 (DSC pre-patch, watchdogd EXP-JB-3.5, post-restore DT EXP-JB-6, build-version EXP-JB-7 opt-in)
CFW installed components 6 7 9 9
CFW total 10 12 15 19
Grand total 56 70 132 150

Ramdisk Variant Matrix

Variant Pre-step Ramdisk/txm.img4 Ramdisk/krnl.ramdisk.img4 Ramdisk/krnl.img4 Effective kernel used by ramdisk_send.sh
RAMDISK make fw_patch release TXM + base TXM patch (1) base kernel (28), legacy *.ramdisk preferred else derive from pristine CloudOS restore kernel from fw_patch (28) krnl.ramdisk.img4 preferred, fallback krnl.img4
DEV+RAMDISK make fw_patch_dev release TXM + base TXM patch (1) base kernel (28), same derivation rule restore kernel from fw_patch_dev (29) krnl.ramdisk.img4 preferred, fallback krnl.img4
JB+RAMDISK make fw_patch_jb release TXM + base TXM patch (1) base kernel (28), same derivation rule restore kernel from fw_patch_jb (28+59) krnl.ramdisk.img4 preferred, fallback krnl.img4
EXP+RAMDISK make fw_patch_exp release TXM + base TXM patch (1) base kernel (28), same derivation rule restore kernel from fw_patch_exp (28+59+6) krnl.ramdisk.img4 preferred, fallback krnl.img4

Cross-Version Dynamic Snapshot

Case TXM_JB_PATCHES KERNEL_JB_PATCHES
PCC 26.1 (23B85) 14 59
PCC 26.3 (23D128) 14 59
iOS 26.1 (23B85) 14 59
iOS 26.3 (23D127) 14 59

Swift Migration Notes (2026-03-10)

  • Swift FirmwarePatcher now matches the Python reference patch output across all checked components:
    • avpbooter 1/1
    • ibss 4/4
    • ibec 7/7
    • llb 13/13
    • txm 1/1
    • txm_dev 12/12
    • kernelcache 28/28
    • ibss_jb 1/1
    • kernelcache_jb 84/84
  • JB parity fixes completed in Swift:
    • C23 vnode_getattr resolution now follows the Python backward BL scan and resolves 0x00CD44F8.
    • C22 syscallmask cave encodings were corrected and centralized in ARM64Constants.swift.
    • Task-conversion matcher masks and kernel-text scan range were corrected, restoring the patch at 0x00B0C400.
    • jbDecodeBranchTarget() now correctly decodes cbz/cbnz, restoring the real _bsd_init rootauth gate at 0x00F7798C.
    • IOUC MACF matching now uses Python-equivalent disassembly semantics for the aggregator shape, restoring the deny-to-allow patch at 0x01260644.
  • C24 kcall10 cave instruction bytes were re-verified against macOS clang/as; no Swift byte changes were needed.
  • The Swift pipeline is now directly invokable from the product binary:
    • vphone-cli patch-firmware --vm-directory <dir> --variant {regular|dev|jb}
    • vphone-cli patch-component --component {txm|kernel-base} --input <file> --output <raw> is available for non-firmware tooling that still needs a single patched payload during ramdisk packaging
    • default loader now preserves IM4P containers via IM4PHandler
    • DeviceTree patching now uses the real Swift DeviceTreePatcher in the pipeline
    • project make fw_patch, make fw_patch_dev, and make fw_patch_jb targets now invoke this Swift pipeline via the unsigned debug vphone-cli build, while the signed release build remains reserved for VM boot/DFU paths
    • on 2026-03-11, the legacy Python firmware patcher entrypoints and patch modules were temporarily restored from pre-removal history for parity/debug work.
    • after byte-for-byte parity was revalidated against Python on 26.1 and 26.3 for regular, dev, and jb, those legacy firmware-patcher Python sources and transient comparison/export helpers were removed again so the repo keeps Swift as the single firmware-patching implementation.
  • Swift pipeline follow-up fixes completed after CLI bring-up:
    • findFile() now supports glob patterns such as AVPBooter*.bin instead of treating them as literal paths.
    • JB variant sequencing now runs base iBSS/kernel patchers first, then the JB extension patchers.
    • Sequential pipeline application now merges each patcher's PatchRecord writes onto the shared output buffer while keeping later patcher searches anchored to the original payload, matching the standalone Swift/Python validation model.
    • apply() now reuses an already-populated patches array instead of re-running findAll(), so patch-firmware / patch-component no longer double-scan or double-print the same component diagnostics on a single invocation.
    • unaligned integer reads across the firmware patcher now go through a shared safe Data.loadLE(...) helper, fixing the JB IM4P crash (Swift/UnsafeRawPointer.swift:449 misaligned raw pointer load).
    • TXMPatcher now preserves pristine Python parity by preferring the legacy trustcache binary-search site when present, and only falls back to the selector24 hash-flags call chain (ldr x1, [x20,#0x38] -> add x2, sp, #4 -> bl -> ldp x0, x1, [x20,#0x30] -> add x2, sp, #8 -> bl) when rerunning on a VM tree that already carries the dev/JB selector24 early-return patch.
    • scripts/fw_prepare.sh now deletes stale sibling *Restore* directories in the working VM directory before patching continues, so a fresh make fw_prepare && make fw_patch cannot accidentally select an older prepared firmware tree (for example 26.1) when a newer one (for example 26.3) was just generated.
  • IM4P/output parity fixes completed after synthetic full-pipeline comparison:
    • IM4PHandler.save() no longer forces a generic LZFSE re-encode.
    • Swift now rebuilds IM4Ps in the same effective shape as the Python patch flow and only preserves trailing PAYP metadata for TXM (trxm) and kernelcache (krnl).
    • IBootPatcher serial labels now match Python casing exactly (Loaded iBSS, Loaded iBEC, Loaded LLB).
    • DeviceTreePatcher now serializes the full patched flat tree, matching Python dtree.py, instead of relying on in-place property writes alone.
  • Synthetic CLI dry-run status on 2026-03-10 using IM4P-backed inputs under ipsws/patch_refactor_input:
    • regular: 58 patch records
    • dev: 69 patch records
    • jb: 154 patch records
  • Full synthetic Python-vs-Swift pipeline comparison status on 2026-03-10 using scripts/compare_swift_python_pipeline.py:
    • regular: all 7 component payloads match
    • dev: all 7 component payloads match
    • jb: all 7 component payloads match
  • Real prepared-firmware Python-vs-Swift pipeline comparison status on 2026-03-10 using vm/ after make fw_prepare:
    • historical note: the now-removed scripts/compare_swift_python_pipeline.py cloned only the prepared *Restore* tree plus AVPBooter*.bin, AVPSEPBooter*.bin, and config.plist, avoiding No space left on device failures from copying Disk.img after make vm_new.
    • regular: all 7 component payloads match
    • dev: all 7 component payloads match
    • jb: all 7 component payloads match
  • Runtime validation blocker observed on 2026-03-10:
    • NONE_INTERACTIVE=1 SKIP_PROJECT_SETUP=1 make setup_machine JB=1 reaches the Swift patch stage and reports [patch-firmware] applied 154 patches for jb, then fails when the flow transitions into make boot_dfu.
    • make boot_dfu originally failed at launch-policy time with exit 137 / signal 9 because the release vphone-cli could not launch on this host.
    • amfidont was then validated on-host:
      • it can attach to /usr/libexec/amfid
      • the initial path allow rule failed because AMFIPathValidator reports URL-encoded paths (/Volumes/My%20Shared%20Files/...)
      • rerunning amfidont with the encoded project path and the release-binary CDHash allows the signed release vphone-cli to launch
      • this workflow is now packaged as make amfidont_allow_vphone / scripts/start_amfidont_for_vphone.sh
    • With launch policy bypassed, make boot_dfu advances into VM setup, emits vm/udid-prediction.txt, and then fails with VZErrorDomain Code=2 "Virtualization is not available on this hardware."
    • VPhoneAppDelegate startup failure handling was tightened so these fatal boot/DFU startup errors now exit non-zero; make boot_dfu now reports make: *** [boot_dfu] Error 1 for the nested-virtualization failure instead of incorrectly returning success.
    • The host itself is a nested Apple VM (Model Name: Apple Virtual Machine 1, kern.hv_vmm_present=1), so the remaining blocker is lack of nested Virtualization.framework availability rather than firmware patching or AMFI bypass.
    • boot_binary_check now uses strict host preflight and fails earlier on this class of host with make: *** [boot_binary_check] Error 3, avoiding a wasted VM-start attempt once the nested-virtualization condition is already known.
    • Added make boot_host_preflight / scripts/boot_host_preflight.sh to capture this state in one command:
      • model: Apple Virtual Machine 1
      • kern.hv_vmm_present: 1
      • SIP: disabled
      • allow-research-guests: disabled
      • current kern.bootargs: empty
      • next-boot nvram boot-args: amfi_get_out_of_my_way=1 -v (staged on 2026-03-10; requires reboot before it affects launch policy)
      • spctl --status: assessments enabled
      • spctl --assess rejects the signed release binary
      • unsigned debug vphone-cli --help: exit 0
      • signed release vphone-cli --help: exit 137
      • freshly signed debug control binary --help: exit 137

Automation Notes (2026-03-06)

  • scripts/setup_machine.sh non-interactive flow fix: renamed local variable status to boot_state in first-boot log wait and boot-analysis wait helpers to avoid zsh status read-only special parameter collision.
  • scripts/setup_machine.sh non-interactive first-boot wait fix: replaced (( waited++ )) with (( ++waited )) in monitor_boot_log_until to avoid set -e abort when arithmetic expression evaluates to 0.
  • scripts/jb_patch_autotest.sh loop fix for sweep stability under set -e: replaced ((idx++)) with (( ++idx )).
  • scripts/jb_patch_autotest.sh zsh compatibility fix: renamed per-case result variable status to case_status to avoid status read-only special parameter collision.
  • scripts/jb_patch_autotest.sh selection logic update:
    • default run now excludes methods listed in KernelJBPatcher._DEV_SINGLE_WORKING_METHODS (pending-only sweep).
    • set JB_AUTOTEST_INCLUDE_WORKING=1 to include already-working methods and run the full list.
  • Sweep run record:
    • setup_logs/jb_patch_tests_20260306_114417 (2026-03-06): aborted at [1/20] with read-only variable: status in jb_patch_autotest.sh.
    • setup_logs/jb_patch_tests_20260306_115027 (2026-03-06): rerun after status fix, pending-only mode (Total methods: 19).
  • Final run result from jb_patch_tests_20260306_115027 at 2026-03-06 13:17:
    • Finished: 19/19 (PASS=15, FAIL=4, all fails rc=2).
    • Failing methods at that time: patch_bsd_init_auth, patch_io_secure_bsd_root, patch_vm_fault_enter_prepare, patch_cred_label_update_execve.
    • 2026-03-06 follow-up: patch_io_secure_bsd_root failure is now attributed to a wrong-site patch in AppleARMPE::callPlatformFunction ("SecureRoot" gate at 0xFFFFFE000836E1F0), not the intended "SecureRootName" deny-return path. The code was retargeted the same day to 0xFFFFFE000836E464 and re-enabled for the next restore/boot check.
    • 2026-03-06 follow-up: patch_bsd_init_auth was retargeted after confirming the old matcher was hitting unrelated code; keep disabled in default schedule until a fresh clean-baseline boot test passes.
    • Final case: [19/19] patch_syscallmask_apply_to_proc (PASS).
    • 2026-03-06 re-analysis: that historical PASS is now treated as a false positive for functionality, because the recorded bytes landed at 0xfffffe00093ae6e4/0xfffffe00093ae6e8 inside _profile_syscallmask_destroy underflow handling, not in _proc_apply_syscall_masks.
    • 2026-03-06 code update: scripts/patchers/kernel_jb_patch_syscallmask.py was rebuilt to target the real syscallmask apply wrapper structurally and now dry-runs on PCC-CloudOS-26.1-23B85 kernelcache.research.vphone600 with 3 writes: 0x02395530, 0x023955E8, and cave 0x00AB1720. User-side boot validation succeeded the same day.
  • 2026-03-06 follow-up: patch_kcall10 was rebuilt from the old ABI-unsafe pseudo-10-arg design into an ABI-correct sysent[439] cave. Focused dry-run on PCC-CloudOS-26.1-23B85 kernelcache.research.vphone600 now emits 4 writes: cave 0x00AB1720, sy_call 0x0073E180, sy_arg_munge32 0x0073E188, and metadata 0x0073E190; the method was re-enabled in _GROUP_C_METHODS.
    • Observed failure symptom in current failing set: first boot panic before command injection (or boot process early exit).
  • Post-run schedule change (per user request):
    • commented out failing methods from default KernelJBPatcher._PATCH_METHODS schedule in scripts/patchers/kernel_jb.py:
      • patch_bsd_init_auth
      • patch_io_secure_bsd_root
      • patch_vm_fault_enter_prepare
      • patch_cred_label_update_execve
  • 2026-03-06 re-research note for patch_cred_label_update_execve:
    • old entry-time early-return strategy was identified as boot-unsafe because it skipped AMFI exec-time csflags and entitlement propagation entirely.
    • implementation was reworked to a success-tail trampoline that preserves normal AMFI processing and only clears restrictive csflags bits on the success path.
    • default JB schedule still keeps the method disabled until the reworked strategy is boot-validated.
  • Manual DEV+single (setup_machine + PATCH=<method>) working set now includes:
    • patch_amfi_cdhash_in_trustcache
    • patch_amfi_execve_kill_path
    • patch_task_conversion_eval_internal
    • patch_sandbox_hooks_extended
    • patch_post_validation_additional
  • 2026-03-07 host-side note:
    • reviewed private Virtualization.framework display APIs against the recorder pipeline in sources/vphone-cli/VPhoneScreenRecorder.swift.
    • replaced the old AppKit-first recorder path with a private-display-only implementation built around hidden VZGraphicsDisplay._takeScreenshotWithCompletionHandler: capture.
    • added still screenshot actions that can copy the captured image to the pasteboard or save a PNG to disk using the same private capture path.
    • make build is used as the sanity check path; live VM validation is still needed to confirm the exact screenshot object type returned on macOS 15.
  • 2026-03-15 tooling source sync update:
    • removed ad-hoc git clone source fetching from scripts/setup_tools.sh and scripts/setup_libimobiledevice.sh.
    • added pinned git-submodule sources under scripts/repos/ for: trustcache, insert_dylib, libplist, libimobiledevice-glue, libusbmuxd, libtatsu, libimobiledevice, libirecovery, idevicerestore.
    • setup scripts now initialize required submodules via git submodule update --init --recursive <path> and stage build copies under local tool build directories.