# 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: > > - **Kernel** — `KernelEXPPatcher` 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=`. 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 ` | 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 ` — standalone-Mach-O subcommand (used for the 6 on-device files). * `scripts/patchers/cfw.py patch-hv-vmm-dsc ` — 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, add x0, x0, # ; "kern.hv_vmm_present" ...arg setup... bl _sysctlbyname cbnz w0, ; <-- patched: NOP ldur w8, [x29, #-4] cmp w8, #0 cset wN, ne ; <-- patched: mov wN, #1 adrp xM, strb wN, [xM, #] ; 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 ` — CLI subcommand. * `scripts/patch_hv_vmm_userland.sh watchdogd ` — 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` | `VPHONE600AP` → `D47AP` | HIGHER | | 2 | `device-tree` | `compatible[1]` | `iPhone99,11` → `iPhone17,3` (slot-preserving) | LOW | | 3 | `device-tree/product` | `fdr-product-type` | `iPhone99,11` → `iPhone17,3` | HIGHER | | 4 | `device-tree/product` | `sub-product-type` | `iPhone99,11` → `iPhone17,3` | LOW | | 5 | `device-tree/product` | `unique-model` | `VPHONE600AP` → `D47AP` | LOW | | 6 | `device-tree/arm-io` | `device_type` | `vresearch1-io` → `t8140-io` | MEDIUM | | 7 | `device-tree/arm-io` | `soc-generation` | `VResearch1` → `H17` | MEDIUM-LOW | | 8 | `device-tree/product/vphone600-gestalt-variants` | `name` (node rename) | `vphone600-gestalt-variants` → `d47-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//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,11` → `iPhone17,3` | | 2 | root `target-type` | `VPHONE600` → `D47` | | 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=` | |-----------------------------------------------------------------------------------|:------------------------------:| | `/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 --variant {regular|dev|jb}` - `vphone-cli patch-component --component {txm|kernel-base} --input --output ` 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=`) 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 ` and stage build copies under local tool build directories.