Automate JB patch testing workflow and update patch schedules

This commit is contained in:
Lakr
2026-03-06 13:31:22 +08:00
parent 239476ee38
commit 4c18166be5
8 changed files with 642 additions and 143 deletions
+15 -1
View File
@@ -46,6 +46,9 @@ help:
@echo " Options: JB=1 Jailbreak firmware/CFW path (WIP)"
@echo " DEV=1 Dev firmware/CFW path (dev TXM + cfw_install_dev)"
@echo " SKIP_PROJECT_SETUP=1 Skip setup_tools/build"
@echo " NONE_INTERACTIVE=1 Auto-continue prompts + boot analysis"
@echo " SUDO_PASSWORD=... Preload sudo credential for setup flow"
@echo " PATCH=patch_xxx Apply single JB patch test on top of dev patch"
@echo ""
@echo "Setup (one-time):"
@echo " make setup_tools Install all tools (brew, trustcache, libimobiledevice, venv)"
@@ -67,6 +70,8 @@ help:
@echo " make fw_patch Patch boot chain (6 components)"
@echo " make fw_patch_dev Patch boot chain (dev mode TXM patcher)"
@echo " make fw_patch_jb Run fw_patch + JB extension patches (WIP)"
@echo " make fw_patch_test PATCH=... Apply one JB kernel patch method (after fw_patch_dev)"
@echo " make jb_patch_autotest Run setup_machine per JB patch method with logs"
@echo ""
@echo "Restore:"
@echo " make restore_get_shsh Fetch SHSH blob from device"
@@ -94,6 +99,9 @@ setup_machine:
echo "Error: JB=1 and DEV=1 are mutually exclusive"; \
exit 1; \
fi
SUDO_PASSWORD="$(SUDO_PASSWORD)" \
NONE_INTERACTIVE="$(NONE_INTERACTIVE)" \
PATCH="$(PATCH)" \
zsh $(SCRIPTS)/setup_machine.sh \
$(if $(filter 1 true yes YES TRUE,$(JB)),--jb,) \
$(if $(filter 1 true yes YES TRUE,$(DEV)),--dev,) \
@@ -191,7 +199,7 @@ boot_dfu: build
# Firmware pipeline
# ═══════════════════════════════════════════════════════════════════
.PHONY: fw_prepare fw_patch fw_patch_dev fw_patch_jb
.PHONY: fw_prepare fw_patch fw_patch_dev fw_patch_jb fw_patch_test jb_patch_autotest
fw_prepare:
cd $(VM_DIR) && bash "$(CURDIR)/$(SCRIPTS)/fw_prepare.sh"
@@ -205,6 +213,12 @@ fw_patch_dev:
fw_patch_jb:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/fw_patch_jb.py" .
fw_patch_test:
cd $(VM_DIR) && PATCH="$(PATCH)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/fw_patch_test.py" .
jb_patch_autotest:
zsh "$(CURDIR)/$(SCRIPTS)/jb_patch_autotest.sh"
# ═══════════════════════════════════════════════════════════════════
# Restore
# ═══════════════════════════════════════════════════════════════════
+3
View File
@@ -85,6 +85,7 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
```bash
make setup_machine # full automation through "First Boot" (includes restore/ramdisk/CFW)
# options: NONE_INTERACTIVE=1 SUDO_PASSWORD=... PATCH=patch_xxx
```
## Manual Setup
@@ -97,6 +98,8 @@ make fw_prepare # download IPSWs, extract, merge, generate manifes
make fw_patch # patch boot chain (regular variant)
# or: make fw_patch_dev # dev variant (+ TXM entitlement/debug bypasses)
# or: make fw_patch_jb # jailbreak variant (+ full security bypass) (WIP)
# or: make fw_patch_test PATCH=patch_xxx # one JB kernel method on top of dev patch
# or: make jb_patch_autotest # test all JB methods (single-thread, full setup flow)
```
## Restore
+183 -98
View File
@@ -1,125 +1,210 @@
# Binary Patch Matrix: Regular / Development / Jailbreak
# Patch Comparison: Regular / Development / Jailbreak
Only binary patch items are kept in this document.
Non-binary content (deployment flow, installed components, runtime defaults, toolchain notes) is intentionally excluded.
## Boot Chain Binary Patches
## Boot Chain Patches
### AVPBooter
| # | Patch | Purpose | Regular | Dev | JB |
| --- | --- | --- | :---: | :---: | :---: |
| 1 | `mov x0, #0` | DGST signature validation bypass | Y | Y | Y |
| # | 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` serial marker | 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 |
| # | 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` serial marker | 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 |
| # | 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 |
### LLB
| # | Patch | Purpose | Regular | Dev | JB |
| --- | --- | --- | :---: | :---: | :---: |
| 1 | Serial labels (2x) | `Loaded LLB` serial marker | 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 |
| # | 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 |
| # | 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 Binary Patches
## 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 |
| # | 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 |
### JB-Only Kernel Methods
### JB-Only Kernel Methods (Reference List)
| # | Group | Method | Function | Purpose | JB |
| --- | --- | --- | --- | --- | :---: |
| 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 | Y |
| JB-03 | C | `patch_cred_label_update_execve` | `_cred_label_update_execve` | Early-return low-riskized cs_flags path | Y |
| JB-04 | C | `patch_hook_cred_label_update_execve` | `_hook_cred_label_update_execve` | Low-riskized early-return hook gate | Y |
| JB-05 | C | `patch_kcall10` | `sysent[439]` (`SYS_kas_info` replacement) | Kernel arbitrary call from userspace | 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_to_proc` | Low-riskized early return for syscall mask gate | 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 | Bypass shared IOUserClient MACF deny path | 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` (2nd auth gate) | Skip auth at @%s:%d | Y |
| JB-15 | B | `patch_dounmount` | `_dounmount` | Allow unmount (strict in-function match) | Y |
| JB-16 | B | `patch_io_secure_bsd_root` | `_IOSecureBSDRoot` | Skip secure root check (guard-site filter) | 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` | Bypass MAC mount deny path (strict site) | 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 shared region path | Y |
| JB-21 | B | `patch_spawn_validate_persona` | `_spawn_validate_persona` | Skip persona validation | Y |
| JB-22 | B | `patch_task_for_pid` | `_task_for_pid` | Allow task_for_pid | 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` | Skip fault check | Y |
| JB-25 | B | `patch_vm_map_protect` | `_vm_map_protect` | Allow VM protect | Y |
Current default schedule note (2026-03-06): `patch_bsd_init_auth`, `patch_io_secure_bsd_root`, `patch_vm_fault_enter_prepare`, and `patch_cred_label_update_execve` are temporarily excluded from `_PATCH_METHODS` pending rework.
## CFW Binary Patches
| # | 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 | Y |
| JB-03 | C | `patch_cred_label_update_execve` | `_cred_label_update_execve` | Early-return low-riskized cs_flags path | Y |
| JB-04 | C | `patch_hook_cred_label_update_execve` | `_hook_cred_label_update_execve` | Low-riskized early-return hook gate | Y |
| JB-05 | C | `patch_kcall10` | `sysent[439]` (`SYS_kas_info` replacement) | Kernel arbitrary call from userspace | 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_to_proc` | Low-riskized early return for syscall mask gate | 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 | Bypass shared IOUserClient MACF deny path | 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` (2nd auth gate) | Skip auth at @%s:%d | Y |
| JB-15 | B | `patch_dounmount` | `_dounmount` | Allow unmount (strict in-function match) | Y |
| JB-16 | B | `patch_io_secure_bsd_root` | `_IOSecureBSDRoot` | Skip secure root check (guard-site filter) | 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` | Bypass MAC mount deny path (strict site) | 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 shared region path | Y |
| JB-21 | B | `patch_spawn_validate_persona` | `_spawn_validate_persona` | Skip persona validation | Y |
| JB-22 | B | `patch_task_for_pid` | `_task_for_pid` | Allow task_for_pid | 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` | Skip fault check | Y |
| JB-25 | B | `patch_vm_map_protect` | `_vm_map_protect` | Allow VM protect | Y |
### Patches Applied Over SSH Ramdisk
## CFW Installation Patches
| # | 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` | Add 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 `/cores/launchdhook.dylib` | - | - | Y |
### Binary Patches Applied Over SSH Ramdisk
## Ramdisk Binary Patch
| # | 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 `/cores/launchdhook.dylib` at launch | - | - | Y |
| # | Patch Site | Purpose | Regular | Dev | JB |
| --- | --- | --- | :---: | :---: | :---: |
| 1 | `ramdisk_input/ssh/usr/local/bin/restored_external` | Replace default USBMux serial label (`SSHRD_Script ...`) with `UDID` | Y | Y | Y |
### 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/` | - | - | Y |
### CFW Installer Flow Matrix (Script-Level)
| Flow Item | Regular (`cfw_install.sh`) | Dev (`cfw_install_dev.sh`) | JB (`cfw_install_jb.sh`) |
| ----------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------- | --------------------------------------------- | ------ | --------------------------------------------- |
| Base CFW phases (1/7 -> 7/7) | Runs directly | Runs directly | 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) |
| `remote_mount` behavior | Ensures mountpoint and verifies mount success | Best-effort mount only (`mount_apfs ... | | true`) | Ensures mountpoint and verifies mount success |
| launchd jetsam patch (`patch-launchd-jetsam`) | - | Y (base-flow injection) | Y (JB-1) |
| launchd dylib injection (`inject-dylib /cores/launchdhook.dylib`) | - | - | Y (JB-1) |
| Procursus bootstrap deployment | - | - | Y (JB-2) |
| BaseBin hook deployment (`*.dylib` -> `/mnt1/cores`) | - | - | Y (JB-3) |
| Additional input resources | `cfw_input` | `cfw_input` + `resources/cfw_dev/rpcserver_ios` | `cfw_input` + `cfw_jb_input` |
| Extra tool requirement beyond base | - | - | `zstd` |
| Halt behavior | Halts unless `CFW_SKIP_HALT=1` | Halts unless `CFW_SKIP_HALT=1` | Always halts after JB phases |
## Summary
| Component | Regular | Dev | JB |
| ------------------------ | ------: | --: | --: |
| AVPBooter | 1 | 1 | 1 |
| iBSS | 2 | 2 | 3 |
| iBEC | 3 | 3 | 3 |
| LLB | 6 | 6 | 6 |
| TXM | 1 | 12 | 12 |
| Kernel | 28 | 28 | 53 |
| Boot chain total | 41 | 52 | 78 |
| CFW binary patches | 4 | 5 | 6 |
| CFW installed components | 6 | 7 | 8 |
| CFW total | 10 | 12 | 14 |
| Grand total | 51 | 64 | 92 |
## 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` (28) | `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` (53) | `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 |
## 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: `patch_bsd_init_auth`, `patch_io_secure_bsd_root`, `patch_vm_fault_enter_prepare`, `patch_cred_label_update_execve`.
- Final case: `[19/19] patch_syscallmask_apply_to_proc` (`PASS`).
- 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`
- 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`
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
fw_patch_test.py — apply a single JB kernel patch method onto a dev-patched image.
Usage:
PATCH=patch_xxx python3 fw_patch_test.py [vm_directory]
"""
import os
import sys
from fw_patch import find_file, find_restore_dir, load_firmware, save_firmware
from patchers.kernel_jb import KernelJBPatcher
def _build_single_patch_plan(patcher, method_name):
all_methods = getattr(KernelJBPatcher, "_PATCH_METHODS", ())
if method_name not in all_methods:
available = "\n".join(f" - {name}" for name in all_methods)
raise ValueError(
f"Unknown JB patch method: {method_name}\nAvailable methods:\n{available}"
)
if not callable(getattr(patcher, method_name, None)):
raise ValueError(f"Method is not callable on patcher: {method_name}")
return (method_name,)
def patch_kernelcache_single(data, method_name):
patcher = KernelJBPatcher(data)
plan = _build_single_patch_plan(patcher, method_name)
original_plan = patcher._PATCH_METHODS
patcher._PATCH_METHODS = plan
try:
patches = list(patcher.find_all())
finally:
patcher._PATCH_METHODS = original_plan
if not patches:
print(f" [-] No patches emitted by method: {method_name}")
return False
for off, patch_bytes, _ in patches:
data[off : off + len(patch_bytes)] = patch_bytes
print(f" [+] {len(patches)} patch(es) emitted by {method_name}")
return True
def main():
method_name = os.environ.get("PATCH", "").strip()
if not method_name:
print("[-] PATCH environment variable is required (example: PATCH=<jb_patch_method>)")
sys.exit(1)
vm_dir = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
vm_dir = os.path.abspath(vm_dir)
if not os.path.isdir(vm_dir):
print(f"[-] Not a directory: {vm_dir}")
sys.exit(1)
restore_dir = find_restore_dir(vm_dir)
if not restore_dir:
print(f"[-] No *Restore* directory found in {vm_dir}")
sys.exit(1)
kernel_path = find_file(restore_dir, ["kernelcache.research.vphone600"], "kernelcache")
print(f"[*] VM directory: {vm_dir}")
print(f"[*] Restore directory: {restore_dir}")
print(f"[*] Testing JB method: {method_name}")
print(f"[*] Target file: {kernel_path}")
im4p, data, was_im4p, original_raw = load_firmware(kernel_path)
if not patch_kernelcache_single(data, method_name):
sys.exit(1)
save_firmware(kernel_path, im4p, data, was_im4p, original_raw)
print("[+] Single JB patch test applied successfully")
if __name__ == "__main__":
main()
+97
View File
@@ -0,0 +1,97 @@
#!/bin/zsh
# jb_patch_autotest.sh — run full setup_machine flow for each JB kernel patch method.
# Strategy: apply each single JB kernel method on top of the dev baseline, one case at a time.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "$PROJECT_ROOT"
LOG_ROOT="${PROJECT_ROOT}/setup_logs/jb_patch_tests_$(date +%Y%m%d_%H%M%S)"
SUMMARY_CSV="${LOG_ROOT}/summary.csv"
MASTER_LOG="${LOG_ROOT}/run.log"
INCLUDE_WORKING="${JB_AUTOTEST_INCLUDE_WORKING:-0}"
mkdir -p "$LOG_ROOT"
touch "$MASTER_LOG"
if [[ -x "${PROJECT_ROOT}/.venv/bin/python3" ]]; then
PYTHON_BIN="${PROJECT_ROOT}/.venv/bin/python3"
else
PYTHON_BIN="$(command -v python3)"
fi
PATCH_METHODS=("${(@f)$(
cd "${PROJECT_ROOT}/scripts" && "$PYTHON_BIN" - <<'PY'
import os
from patchers.kernel_jb import KernelJBPatcher
def _env_enabled(name, default=False):
raw = os.environ.get(name)
if raw is None:
return default
return raw.strip().lower() in {"1", "true", "yes", "on"}
include_working = _env_enabled("JB_AUTOTEST_INCLUDE_WORKING", default=False)
all_methods = list(getattr(KernelJBPatcher, "_PATCH_METHODS", ()))
if include_working:
selected_methods = all_methods
else:
working = set(getattr(KernelJBPatcher, "_DEV_SINGLE_WORKING_METHODS", ()))
selected_methods = [m for m in all_methods if m not in working]
for method in selected_methods:
print(method)
PY
)}")
if (( ${#PATCH_METHODS[@]} == 0 )); then
echo "[*] No JB patch methods selected (all already marked working or list empty)" | tee -a "$MASTER_LOG"
echo "[*] Set JB_AUTOTEST_INCLUDE_WORKING=1 to run the full list." | tee -a "$MASTER_LOG"
exit 0
fi
echo "index,patch,status,exit_code,log_file" >"$SUMMARY_CSV"
echo "[*] JB patch single-method automation started" | tee -a "$MASTER_LOG"
echo "[*] Logs: $LOG_ROOT" | tee -a "$MASTER_LOG"
echo "[*] Include already-working methods: ${INCLUDE_WORKING}" | tee -a "$MASTER_LOG"
echo "[*] Total methods: ${#PATCH_METHODS[@]}" | tee -a "$MASTER_LOG"
idx=0
for patch_method in "${PATCH_METHODS[@]}"; do
(( ++idx ))
case_log="${LOG_ROOT}/$(printf '%02d' "$idx")_${patch_method}.log"
{
echo ""
echo "============================================================"
echo "[*] [$idx/${#PATCH_METHODS[@]}] Testing PATCH=${patch_method}"
echo "============================================================"
} | tee -a "$MASTER_LOG"
set +e
# Test matrix assumption: each JB kernel method is validated on top of dev patch baseline.
case_skip_project_setup="${SKIP_PROJECT_SETUP:-1}"
echo "[*] Env: NONE_INTERACTIVE=1 DEV=1 SKIP_PROJECT_SETUP=${case_skip_project_setup} PATCH=${patch_method}" | tee -a "$MASTER_LOG"
SUDO_PASSWORD="${SUDO_PASSWORD:-}" \
NONE_INTERACTIVE=1 \
DEV=1 \
SKIP_PROJECT_SETUP="${case_skip_project_setup}" \
PATCH="$patch_method" \
make setup_machine >"$case_log" 2>&1
rc=$?
set -e
if (( rc == 0 )); then
case_status="PASS"
else
case_status="FAIL"
fi
echo "${idx},${patch_method},${case_status},${rc},${case_log}" >>"$SUMMARY_CSV"
echo "[*] Result: ${case_status} (rc=${rc}) log=${case_log}" | tee -a "$MASTER_LOG"
done
echo ""
echo "[*] Completed JB patch automation. Summary: $SUMMARY_CSV" | tee -a "$MASTER_LOG"
+16 -20
View File
@@ -60,27 +60,23 @@ class KernelJBPatcher(
):
_TIMING_LOG_MIN_SECONDS = 10.0
# Problematic Patches
# | JB-14 | patch_bsd_init_auth | _bsd_init (2nd auth gate) | Skip auth at @%s:%d |
# | JB-15 | patch_dounmount | _dounmount | Allow unmount (strict in-function match) |
# | JB-16 | patch_io_secure_bsd_root | _IOSecureBSDRoot | Skip secure root check (guard-site filter) |
# | JB-17 | patch_load_dylinker | _load_dylinker | Skip strict `LC_LOAD_DYLINKER == "/usr/lib/dyld"` gate |
# | JB-18 | patch_mac_mount | ___mac_mount | Bypass MAC mount deny path (strict site) |
# | JB-19 | patch_nvram_verify_permission | _verifyPermission (NVRAM) | Allow NVRAM writes |
# | JB-20 | patch_shared_region_map | _shared_region_map_and_slide_setup | Force shared region path |
# | JB-21 | patch_spawn_validate_persona | _spawn_validate_persona | Skip persona validation |
# | JB-22 | patch_task_for_pid | _task_for_pid | Allow task_for_pid |
# | JB-23 | patch_thid_should_crash | _thid_should_crash | Prevent GUARD_TYPE_MACH_PORT crash |
# | JB-24 | patch_vm_fault_enter_prepare | _vm_fault_enter_prepare | Skip fault check |
# | JB-25 | patch_vm_map_protect | _vm_map_protect | Allow VM protect |
# Manual single-method validation status (DEV=1 + setup_machine + PATCH=<method>).
# This is documentation-only metadata and does not affect scheduling/runtime behavior.
_DEV_SINGLE_WORKING_METHODS = (
"patch_amfi_cdhash_in_trustcache",
"patch_amfi_execve_kill_path",
"patch_task_conversion_eval_internal",
"patch_sandbox_hooks_extended",
"patch_post_validation_additional",
)
# Group A: Core gate-bypass methods.
_GROUP_A_METHODS = (
"patch_amfi_cdhash_in_trustcache", # JB-01 / A1
"patch_amfi_execve_kill_path", # JB-02 / A2
"patch_task_conversion_eval_internal", # JB-08 / A3
"patch_sandbox_hooks_extended", # JB-09 / A4
"patch_iouc_failed_macf", # JB-10 / A5
# "patch_iouc_failed_macf", # JB-10 / A5
)
# Group B: Pattern/string anchored methods.
@@ -89,9 +85,9 @@ class KernelJBPatcher(
"patch_proc_security_policy", # JB-11 / B6
"patch_proc_pidinfo", # JB-12 / B7
"patch_convert_port_to_map", # JB-13 / B8
"patch_bsd_init_auth", # JB-14 / B13
# "patch_bsd_init_auth", # JB-14 / B13 (disabled: autotest FAIL rc=2 on 2026-03-06)
"patch_dounmount", # JB-15 / B12
"patch_io_secure_bsd_root", # JB-16 / B19
# "patch_io_secure_bsd_root", # JB-16 / B19 (disabled: autotest FAIL rc=2 on 2026-03-06)
"patch_load_dylinker", # JB-17 / B16
"patch_mac_mount", # JB-18 / B11
"patch_nvram_verify_permission", # JB-19 / B18
@@ -99,19 +95,19 @@ class KernelJBPatcher(
"patch_spawn_validate_persona", # JB-21 / B14
"patch_task_for_pid", # JB-22 / B15
"patch_thid_should_crash", # JB-23 / B20
"patch_vm_fault_enter_prepare", # JB-24 / B9
# "patch_vm_fault_enter_prepare", # JB-24 / B9 (disabled: autotest FAIL rc=2 on 2026-03-06)
"patch_vm_map_protect", # JB-25 / B10
)
# Group C: Shellcode/trampoline heavy methods.
_GROUP_C_METHODS = (
"patch_cred_label_update_execve", # JB-03 / C21 (low-riskized)
# "patch_cred_label_update_execve", # JB-03 / C21 (disabled: autotest FAIL rc=2 on 2026-03-06)
"patch_hook_cred_label_update_execve", # JB-04 / C23 (low-riskized)
"patch_kcall10", # JB-05 / C24 (low-riskized)
"patch_syscallmask_apply_to_proc", # JB-07 / C22
)
# Full JB patch schedule (all validated patch methods enabled by default).
# Active JB patch schedule (known failing methods are temporarily excluded).
_PATCH_METHODS = _GROUP_A_METHODS + _GROUP_B_METHODS + _GROUP_C_METHODS
def __init__(self, data, verbose=False):
+188 -24
View File
@@ -18,12 +18,14 @@ cd "$PROJECT_ROOT"
LOG_DIR="${PROJECT_ROOT}/setup_logs"
DFU_LOG="${LOG_DIR}/boot_dfu.log"
IPROXY_LOG=""
BOOT_LOG="${LOG_DIR}/boot.log"
DFU_PID=""
IPROXY_PID=""
BOOT_PID=""
BOOT_FIFO=""
BOOT_FIFO_FD=""
SUDO_ASKPASS_SCRIPT=""
VM_DIR="${VM_DIR:-vm}"
VM_DIR_ABS="${VM_DIR:A}"
@@ -47,6 +49,12 @@ DEVICE_UDID=""
DEVICE_ECID=""
IPROXY_TARGET_UDID=""
IPROXY_RESOLVE_REASON=""
BOOT_ANALYSIS_TIMEOUT="${BOOT_ANALYSIS_TIMEOUT:-300}"
BOOT_PROMPT_FALLBACK_TIMEOUT="${BOOT_PROMPT_FALLBACK_TIMEOUT:-60}"
BOOT_BASH_PROMPT_REGEX="${BOOT_BASH_PROMPT_REGEX:-bash-[0-9]+(\.[0-9]+)+#}"
BOOT_PANIC_REGEX="${BOOT_PANIC_REGEX:-panic|kernel panic|panic\\.apple\\.com|stackshot succeeded}"
NONE_INTERACTIVE_RAW="${NONE_INTERACTIVE:-0}"
NONE_INTERACTIVE=0
JB_MODE=0
DEV_MODE=0
SKIP_PROJECT_SETUP=0
@@ -283,6 +291,33 @@ choose_ramdisk_ssh_port() {
|| die "Failed to allocate a random local SSH forward port"
}
parse_bool() {
local raw="${1:-0}"
# zsh `${var:l}` lowercases value for tolerant bool parsing.
case "${raw:l}" in
1|true|yes|on) return 0 ;;
*) return 1 ;;
esac
}
setup_sudo_noninteractive() {
[[ -n "${SUDO_PASSWORD:-}" ]] || return 0
SUDO_ASKPASS_SCRIPT="$(mktemp "${TMPDIR:-/tmp}/vphone-sudo-askpass.XXXXXX")"
cat >"$SUDO_ASKPASS_SCRIPT" <<'EOF'
#!/bin/sh
printf '%s\n' "${SUDO_PASSWORD:-}"
EOF
chmod 700 "$SUDO_ASKPASS_SCRIPT"
export SUDO_ASKPASS="$SUDO_ASKPASS_SCRIPT"
if sudo -A -v >/dev/null 2>&1; then
echo "[+] sudo credential preloaded via SUDO_PASSWORD"
else
echo "[!] SUDO_PASSWORD provided but sudo -A validation failed; continuing without preload"
fi
}
collect_vm_lock_pids() {
local -a paths pids
local path pid
@@ -331,8 +366,7 @@ check_vm_storage_locks() {
echo "[*] AUTO_KILL_VM_LOCKS=1 set; terminating lock holder processes..."
for pid in "${lock_pids[@]}"; do
[[ -z "$pid" || "$pid" == "$$" ]] && continue
kill_descendants "$pid"
kill -9 "$pid" >/dev/null 2>&1 || true
stop_process_tree "$pid"
done
sleep 1
@@ -362,6 +396,20 @@ kill_descendants() {
[[ ${#descendants[@]} -gt 0 ]] && kill -9 "${descendants[@]}" >/dev/null 2>&1 || true
}
stop_process_tree() {
local pid="$1"
[[ -n "$pid" && "$pid" == <-> ]] || return 0
kill -0 "$pid" 2>/dev/null || return 0
kill_descendants "$pid"
kill "$pid" >/dev/null 2>&1 || true
sleep 1
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" >/dev/null 2>&1 || true
fi
wait "$pid" 2>/dev/null || true
}
force_release_vm_locks() {
local -a lock_pids
local pid
@@ -372,8 +420,7 @@ force_release_vm_locks() {
echo "[*] Releasing lingering VM lock holders..."
for pid in "${lock_pids[@]}"; do
[[ -z "$pid" || "$pid" == "$$" ]] && continue
kill_descendants "$pid"
kill -9 "$pid" >/dev/null 2>&1 || true
stop_process_tree "$pid"
done
sleep 1
@@ -386,9 +433,7 @@ cleanup() {
fi
if [[ -n "$BOOT_PID" ]] && kill -0 "$BOOT_PID" 2>/dev/null; then
kill_descendants "$BOOT_PID"
kill -9 "$BOOT_PID" >/dev/null 2>&1 || true
wait "$BOOT_PID" 2>/dev/null || true
stop_process_tree "$BOOT_PID"
BOOT_PID=""
fi
@@ -398,26 +443,30 @@ cleanup() {
fi
if [[ -n "$IPROXY_PID" ]]; then
kill -9 "$IPROXY_PID" >/dev/null 2>&1 || true
wait "$IPROXY_PID" 2>/dev/null || true
stop_process_tree "$IPROXY_PID"
IPROXY_PID=""
fi
if [[ -n "$DFU_PID" ]]; then
kill_descendants "$DFU_PID"
kill -9 "$DFU_PID" >/dev/null 2>&1 || true
wait "$DFU_PID" 2>/dev/null || true
stop_process_tree "$DFU_PID"
DFU_PID=""
fi
if [[ -n "$SUDO_ASKPASS_SCRIPT" && -f "$SUDO_ASKPASS_SCRIPT" ]]; then
rm -f "$SUDO_ASKPASS_SCRIPT" || true
SUDO_ASKPASS_SCRIPT=""
fi
}
start_first_boot() {
check_vm_storage_locks
mkdir -p "$LOG_DIR"
: > "$BOOT_LOG"
BOOT_FIFO="$(mktemp -u "${TMPDIR:-/tmp}/vphone-first-boot.XXXXXX")"
mkfifo "$BOOT_FIFO"
make boot <"$BOOT_FIFO" &
(make boot <"$BOOT_FIFO" >"$BOOT_LOG" 2>&1) &
BOOT_PID=$!
exec {BOOT_FIFO_FD}>"$BOOT_FIFO"
@@ -447,6 +496,102 @@ send_first_boot_commands() {
done
}
monitor_boot_log_until() {
local timeout="$1"
local waited=0
[[ "$timeout" == <-> ]] || die "monitor timeout must be integer seconds"
(( timeout > 0 )) || die "monitor timeout must be > 0"
while (( waited < timeout )); do
if [[ -f "$BOOT_LOG" ]] && grep -Eiq "$BOOT_PANIC_REGEX" "$BOOT_LOG"; then
echo "panic"
return 0
fi
if [[ -f "$BOOT_LOG" ]] && grep -Eq "$BOOT_BASH_PROMPT_REGEX" "$BOOT_LOG"; then
echo "bash"
return 0
fi
if [[ -n "$BOOT_PID" ]] && ! kill -0 "$BOOT_PID" 2>/dev/null; then
echo "exited"
return 0
fi
sleep 1
(( ++waited ))
done
echo "timeout"
}
wait_for_first_boot_prompt_auto() {
local boot_state
boot_state="$(monitor_boot_log_until "$BOOT_PROMPT_FALLBACK_TIMEOUT")"
case "$boot_state" in
panic)
echo "[-] Panic detected while waiting for first-boot shell prompt."
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "First boot panicked before command injection."
;;
bash)
echo "[+] First-boot shell prompt detected"
;;
exited)
echo "[-] make boot exited before first-boot command injection."
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "First boot exited before command injection."
;;
timeout)
echo "[!] Shell prompt not detected within ${BOOT_PROMPT_FALLBACK_TIMEOUT}s; fallback to timed continue."
;;
esac
}
run_boot_analysis() {
local boot_state
check_vm_storage_locks
mkdir -p "$LOG_DIR"
: > "$BOOT_LOG"
(make boot >"$BOOT_LOG" 2>&1) &
BOOT_PID=$!
sleep 2
if ! kill -0 "$BOOT_PID" 2>/dev/null; then
echo "[-] make boot exited early during boot analysis."
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "Boot analysis failed: process exited early."
fi
boot_state="$(monitor_boot_log_until "$BOOT_ANALYSIS_TIMEOUT")"
case "$boot_state" in
panic)
echo "[-] Boot analysis: panic detected, stopping VM."
stop_process_tree "$BOOT_PID"
BOOT_PID=""
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "Boot analysis failed: panic detected."
;;
bash)
echo "[+] Boot analysis: bash prompt detected, boot success."
stop_process_tree "$BOOT_PID"
BOOT_PID=""
;;
exited)
echo "[-] Boot analysis: VM process exited before success marker."
BOOT_PID=""
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "Boot analysis failed: process exited."
;;
timeout)
echo "[-] Boot analysis timeout (${BOOT_ANALYSIS_TIMEOUT}s); stopping VM."
stop_process_tree "$BOOT_PID"
BOOT_PID=""
tail -n 80 "$BOOT_LOG" 2>/dev/null || true
die "Boot analysis timeout."
;;
esac
}
trap cleanup EXIT INT TERM
check_platform() {
@@ -498,6 +643,9 @@ run_make() {
echo ""
echo "=== ${label} ==="
if [[ -n "${SUDO_PASSWORD:-}" ]]; then
sudo -A -v >/dev/null 2>&1 || true
fi
make "$@"
}
@@ -528,9 +676,7 @@ start_boot_dfu() {
stop_boot_dfu() {
if [[ -n "$DFU_PID" ]] && kill -0 "$DFU_PID" 2>/dev/null; then
echo "[*] Stopping background DFU boot (pid=$DFU_PID)..."
kill_descendants "$DFU_PID"
kill -9 "$DFU_PID" >/dev/null 2>&1 || true
wait "$DFU_PID" 2>/dev/null || true
stop_process_tree "$DFU_PID"
fi
DFU_PID=""
force_release_vm_locks
@@ -687,9 +833,7 @@ wait_for_ramdisk_ssh() {
stop_iproxy() {
if [[ -n "$IPROXY_PID" ]] && kill -0 "$IPROXY_PID" 2>/dev/null; then
echo "[*] Stopping iproxy (pid=$IPROXY_PID)..."
kill_descendants "$IPROXY_PID"
kill -9 "$IPROXY_PID" >/dev/null 2>&1 || true
wait "$IPROXY_PID" 2>/dev/null || true
stop_process_tree "$IPROXY_PID"
fi
IPROXY_PID=""
}
@@ -715,6 +859,11 @@ Options:
--jb Use jailbreak firmware patching + jailbreak CFW install.
--dev Use dev firmware patching + dev CFW install.
--skip-project-setup Skip setup_tools/build stage.
Environment:
NONE_INTERACTIVE=1 Auto-continue first-boot prompts + run final boot analysis.
PATCH=patch_xxx Run `make fw_patch_test` after the main fw_patch target.
SUDO_PASSWORD=... Preload sudo credential via askpass.
EOF
exit 0
;;
@@ -727,6 +876,10 @@ EOF
main() {
parse_args "$@"
if parse_bool "$NONE_INTERACTIVE_RAW"; then
NONE_INTERACTIVE=1
fi
setup_sudo_noninteractive
local fw_patch_target="fw_patch"
local cfw_install_target="cfw_install"
@@ -746,7 +899,7 @@ main() {
mode_label="dev"
fi
echo "[*] setup_machine mode: ${mode_label}, project_setup=$([[ "$SKIP_PROJECT_SETUP" -eq 1 ]] && echo "skip" || echo "run")"
echo "[*] setup_machine mode: ${mode_label}, project_setup=$([[ "$SKIP_PROJECT_SETUP" -eq 1 ]] && echo "skip" || echo "run"), non_interactive=${NONE_INTERACTIVE}"
if [[ "$SKIP_PROJECT_SETUP" -eq 1 ]]; then
echo ""
@@ -764,6 +917,9 @@ main() {
run_make "Firmware prep" vm_new
run_make "Firmware prep" fw_prepare
run_make "Firmware patch" "$fw_patch_target"
if [[ -n "${PATCH:-}" ]]; then
run_make "Firmware patch test" fw_patch_test
fi
echo ""
echo "=== Restore phase ==="
@@ -795,11 +951,19 @@ main() {
echo ""
echo "=== First boot ==="
read -r "?[*] press Enter to start VM, after the VM has finished booting, press Enter again to finish last stage"
if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] press Enter to start VM, after the VM has finished booting, press Enter again to finish last stage"
else
echo "[*] NONE_INTERACTIVE=1: auto-starting first boot"
fi
start_first_boot
read -r "?[*] Press Enter once the VM is fully booted"
if [[ "$NONE_INTERACTIVE" -eq 0 ]]; then
read -r "?[*] Press Enter once the VM is fully booted"
else
wait_for_first_boot_prompt_auto
fi
send_first_boot_commands
echo "[*] Commands sent. Waiting for VM shutdown..."
@@ -815,8 +979,8 @@ main() {
echo "=== Done ==="
echo "Setup completed."
echo "=== Booting VM ==="
run_make "Booting VM" boot
echo "=== Boot analysis ==="
run_boot_analysis
}
main "$@"
+58
View File
@@ -0,0 +1,58 @@
#!/bin/zsh
set -euo pipefail
unsetopt BG_NICE 2>/dev/null || true
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TARGET_DIR="${1:-${PROJECT_ROOT}/setup_logs/jb_patch_tests_20260306_113510}"
TAIL_LINES="${2:-20}"
SCAN_INTERVAL="${SCAN_INTERVAL:-1}"
if [[ ! -d "$TARGET_DIR" ]]; then
echo "error: directory not found: $TARGET_DIR" >&2
echo "usage: $0 [log_dir] [tail_lines]" >&2
exit 1
fi
typeset -A seen_files
typeset -a tail_pids
cleanup() {
local pid
for pid in "${tail_pids[@]-}"; do
kill "$pid" 2>/dev/null || true
done
}
trap cleanup EXIT INT TERM
start_tail_for_file() {
local file="$1"
local label="${file:t}"
[[ -n "${seen_files[$file]-}" ]] && return 0
seen_files["$file"]=1
echo "[watch] $file"
(
tail -n "$TAIL_LINES" -F -- "$file" 2>&1 \
| awk -v p="$label" '{ print "[" p "] " $0; fflush(); }'
) &
tail_pids+=("$!")
}
discover_files() {
local file
for file in "$TARGET_DIR"/**/*(.N); do
start_tail_for_file "$file"
done
}
echo "Following logs in: $TARGET_DIR"
echo "Tail lines per file: $TAIL_LINES"
echo "Scan interval: ${SCAN_INTERVAL}s"
discover_files
while true; do
sleep "$SCAN_INTERVAL"
discover_files
done