Root cause: backward branch search found PAC validation B.EQ (target fell within error region range) instead of the kernel_map guard B.NE. New approach walks backward from panic string ADRP to find CMP+B.cond where the branch goes forward past the panic, then makes it unconditional. Also enables B6 and B8 in find_all(). Boot-tested: PASS.
21 KiB
Patch Comparison: Regular / Development / Jailbreak
Three firmware variants are available, each building on the previous:
- Regular (
make fw_patch+make cfw_install) — Minimal patches for VM boot with signature bypass and SSV override. - Development (
make fw_patch_dev+make cfw_install_dev) — Regular + TXM entitlement/developer-mode bypasses + launchd jetsam fix. Enables debugging and code signing flexibility without full jailbreak. - Jailbreak (
make fw_patch_jb+make cfw_install_jb) — Regular + comprehensive security bypass across iBSS, TXM, kernel, and userland. Full code execution, sandbox escape, and package management.
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 |
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
TXM patch composition by variant:
- Regular:
txm.py(1 patch). - Dev:
txm.py(1 patch) +txm_dev.py(11 patches) = 12 total. - JB: same as Dev (selector24 bypass now in
txm_dev.py, no separate JB patcher).
| # | 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 — allow get-task-allow |
— | 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 — allow com.apple.private.cs.debugger |
— | Y | Y |
| 12 | Developer mode bypass | NOP conditional guard before deny path | — | Y | Y |
Kernelcache
Regular and Dev share the same 25 base kernel patches. JB adds 34 additional patches.
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–25 | mov x0,#0; ret (5 hooks) |
Sandbox MACF ops table | Stub 5 sandbox hooks | Y | Y | Y |
JB-only kernel patches
| # | Patch | Function | Purpose | Regular | Dev | JB |
|---|---|---|---|---|---|---|
| 26 | Function rewrite | AMFIIsCDHashInTrustCache |
Always return true + store hash | — | — | Y |
| 27 | Shellcode + branch | _cred_label_update_execve |
Set cs_flags (platform+entitlements) | — | — | Y |
| 28 | cmp w0,w0 |
_postValidation (additional) |
Force validation pass | — | — | Y |
| 29 | Shellcode + branch | _syscallmask_apply_to_proc |
Patch zalloc_ro_mut for syscall mask | — | — | Y |
| 30 | Shellcode + ops redirect | _hook_cred_label_update_execve |
vnode_getattr ownership + suid propagation | — | — | Y |
| 31 | mov x0,#0; ret (20+ hooks) |
Sandbox MACF ops (extended) | Stub remaining 20+ sandbox hooks | — | — | Y |
| 32 | cmp xzr,xzr |
_task_conversion_eval_internal |
Allow task conversion | — | — | Y |
| 33 | mov x0,#0; ret |
_proc_security_policy |
Bypass security policy | — | — | Y |
| 34 | NOP (2x) | _proc_pidinfo |
Allow pid 0 info | — | — | Y |
| 35 | b (skip panic) |
_convert_port_to_map_with_flavor |
Skip kernel map panic | — | — | Y |
| 36 | NOP | _vm_fault_enter_prepare |
Skip fault check | — | — | Y |
| 37 | b (skip check) |
_vm_map_protect |
Allow VM protect | — | — | Y |
| 38 | NOP + mov x8,xzr |
___mac_mount |
Bypass MAC mount check | — | — | Y |
| 39 | NOP | _dounmount |
Allow unmount | — | — | Y |
| 40 | mov x0,#0 |
_bsd_init (2nd) |
Skip auth at @%s:%d | — | — | Y |
| 41 | NOP (2x) | _spawn_validate_persona |
Skip persona validation | — | — | Y |
| 42 | NOP | _task_for_pid |
Allow task_for_pid | — | — | Y |
| 43 | b (skip check) |
_load_dylinker |
Allow dylinker loading | — | — | Y |
| 44 | cmp x0,x0 |
_shared_region_map_and_slide_setup |
Force shared region | — | — | Y |
| 45 | NOP BL | _verifyPermission (NVRAM) |
Allow NVRAM writes | — | — | Y |
| 46 | b (skip check) |
_IOSecureBSDRoot |
Skip secure root check | — | — | Y |
| 47 | Syscall 439 + shellcode | kcall10 (SYS_kas_info replacement) |
Kernel arbitrary call from userspace | — | — | Y |
| 48 | Zero out | _thid_should_crash |
Prevent GUARD_TYPE_MACH_PORT crash | — | — | 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 /cores/launchdhook.dylib at launch |
— | — | 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 |
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 | 25 | 25 | 59 |
| Boot chain total | 38 | 49 | 84 |
| CFW binary patches | 4 | 5 | 6 |
| CFW installed components | 6 | 7 | 8 |
| CFW total | 10 | 12 | 14 |
| Grand total | 48 | 61 | 98 |
What each variant adds
Regular → Dev (+13 patches):
- TXM: +11 patches (selector24 force-pass, get-task-allow, selector42|29 shellcode, debugger entitlement, developer mode bypass)
- CFW: +1 binary patch (launchd jetsam), +1 component (dev rpcserver_ios overlay)
Regular → JB (+50 patches):
- iBSS: +1 (nonce skip)
- TXM: +11 (same as dev — selector24, get-task-allow, selector42|29 shellcode, debugger entitlement, dev mode bypass)
- Kernel: +34 (trustcache, execve, sandbox, task/VM, memory, kcall10)
- CFW: +2 binary patches (launchd jetsam + dylib injection), +2 components (procursus + BaseBin hooks)
JB Install Flow (make cfw_install_jb)
- Entry:
scripts/cfw_install_jb.shrunsscripts/cfw_install.shwithCFW_SKIP_HALT=1, then continues with JB phases. - Added JB phases in install pipeline:
JB-1: patch/mnt1/sbin/launchdviainject-dylib(adds/cores/launchdhook.dylibLC_LOAD_DYLIB) +patch-launchd-jetsam(dynamic string+xref).JB-2: unpack procursus bootstrap (bootstrap-iphoneos-arm64.tar.zst) into/mnt5/<bootManifestHash>/jb-vphone/procursus.JB-3: deploy BaseBin hook dylibs (systemhook.dylib,launchdhook.dylib,libellekit.dylib) to/mnt1/cores/, re-signed with ldid + signcert.p12.
- JB resources now packaged in:
scripts/resources/cfw_jb_input.tar.zst- contains:
jb/bootstrap-iphoneos-arm64.tar.zstjb/org.coolstar.sileo_2.5.1_iphoneos-arm64.debbasebin/*.dylib(BaseBin hooks for JB-3)
Dynamic Implementation Log (JB Patchers)
TXM (txm_dev.py)
All TXM dev patches are implemented with dynamic binary analysis and keystone/capstone-encoded instructions only.
selector24 force-pass(2 instructions after prologue)- Locator: unique guarded
mov w0,#0xa1site, scan forldr x1,[xN,#0x38] ; add x2 ; bl ; ldppattern, walk back to PACIBSP. - Patch bytes:
mov w0, #0xa1 ; b <epilogue>after prologue — returns 0xA1 (PASS) unconditionally. - Return code semantics: caller checks
tst w0, #0xff00— byte 1 = 0 is PASS, non-zero is FAIL. - History: v1 was 2x NOP (LDR + BL) which broke flags extraction. v2 was
mov w0, #0x30a1; movk; retwhich returned FAIL (0x130A1 has byte 1 = 0x30). v3 (current) returns 0xA1 (byte 1 = 0 = PASS). Seeresearch/txm_selector24_analysis.md.
- Locator: unique guarded
selector41/29 get-task-allow- Locator: xref to
"get-task-allow"+ nearbyblfollowed bytbnz w0,#0. - Patch bytes: keystone
mov x0, #1.
- Locator: xref to
selector42/29 shellcode trampoline- Locator:
- Find dispatch stub pattern
bti j ; mov x0,x20 ; bl ; mov x1,x21 ; mov x2,x22 ; bl ; b. - Select stub whose second
bltarget is the debugger-gate function (pattern verified by string-xref + call-shape). - Find executable UDF cave dynamically.
- Find dispatch stub pattern
- Patch bytes:
- Stub head -> keystone
b #cave. - Cave payload ->
nop ; mov x0,#1 ; strb w0,[x20,#0x30] ; mov x0,x20 ; b #return.
- Stub head -> keystone
- Locator:
selector42/37 debugger entitlement- Locator: xref to
"com.apple.private.cs.debugger"+ strict nearby call-shape (mov x0,#0 ; mov x2,#0 ; bl ; tbnz w0,#0). - Patch bytes: keystone
mov w0, #1.
- Locator: xref to
developer mode bypass- Locator: xref to
"developer mode enabled due to system policy configuration"- nearest guard branch on
w9.
- nearest guard branch on
- Patch bytes: keystone
nop.
- Locator: xref to
TXM Binary-Alignment Validation
patch.upstream.rawgenerated from upstream-equivalent TXM static patch semantics.patch.dyn.rawgenerated byTXMPatcher(txm_dev.py) on the same input.- Result: byte-identical (
cmp -ssuccess, SHA-256 matched).
Kernelcache (kernel_jb.py)
All 24 kernel JB patch methods are implemented in scripts/patchers/kernel_jb.py
with capstone semantic matching and keystone-generated patch bytes only:
Group A: Core patches
AMFIIsCDHashInTrustCachefunction rewrite- Locator: semantic function-body matcher in AMFI text.
- Patch:
mov x0,#1 ; cbz x2,+8 ; str x0,[x2] ; ret.
- AMFI execve kill path bypass (shared return value)
- Locator: string xref to
"AMFI: hook..execve() killing"(fallback"execve() killing"), then backward scan from function end forMOV W0, #1+LDP x29, x30epilogue. - Patch:
MOV W0, #1 -> MOV W0, #0at the shared kill-return instruction. - All kill paths (unsigned code, restricted exec mode, VPN plugin, dyld sig, etc.) converge on this single return value.
- Previous approach (BL→MOV X0,#0 at two early sites) was patching vnode-type precondition assertions, not the actual kill checks — caused CBZ→panic.
- Locator: string xref to
task_conversion_eval_internalguard bypass- Locator: unique cmp/branch motif:
ldr xN,[xN,#imm] ; cmp xN,x0 ; b.eq ; cmp xN,x1 ; b.eq. - Patch:
cmp xN,x0 -> cmp xzr,xzr.
- Locator: unique cmp/branch motif:
- Extended sandbox MACF hook stubs (25 hooks, JB-only set)
- Locator: dynamic
mac_policy_conf -> mpc_opsdiscovery, then hook-index resolution. - Patch per hook function:
mov x0,#0 ; ret. - JB extended indices include vnode/proc hooks beyond base 5 hooks.
- Locator: dynamic
Group B: Simple patches (string-anchored / pattern-matched)
_postValidationadditional CMP bypass_proc_security_policystub (mov x0,#0; ret) — FIXED: was patching copyio instead_proc_pidinfopid-0 guard NOP (2 sites)_convert_port_to_map_with_flavorpanic skip — FIXED: was patching PAC check instead_vm_fault_enter_preparePMAP check NOP_vm_map_protectpermission check skip___mac_mountMAC check bypass (NOP + mov x8,xzr)_dounmountMAC check NOP_bsd_initauth bypass (mov x0,#0)_spawn_validate_personaNOP (2 sites)_task_for_pidproc_ro security copy NOP_load_dylinkerPAC rebase bypass_shared_region_map_and_slide_setupforce (cmp x0,x0)_verifyPermission(NVRAM) NOP_IOSecureBSDRootcheck skip_thid_should_crashzero out
Group C: Complex shellcode patches
_cred_label_update_execvecs_flags shellcode_syscallmask_apply_to_procfilter mask shellcode_hook_cred_label_update_execveops table + vnode_getattr shellcode- Code cave restricted to __TEXT_EXEC only (__PRELINK_TEXT excluded due to KTRR)
kcall10syscall 439 replacement shellcode
Cross-Version Dynamic Snapshot
Validated using pristine inputs from updates-cdn/:
| 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 |
Note: These emit counts were captured at validation time and may differ from the current source if methods were subsequently refactored. The TXM JB patcher currently has 5 methods emitting 11 patches in txm_dev.py (selector24 force-pass = 2 emits); the kernel JB patcher has 24 methods. Actual emit counts depend on how many dynamic targets resolve per binary.
All patches are applied dynamically via string anchors, instruction patterns, and cross-reference analysis — no hardcoded offsets — ensuring portability across iOS versions.