Kernel JB Remaining Patches — Research Notes
Last updated: 2026-03-07
Historical. These notes describe
scripts/patchers/kernel_jb.py, which was deleted with the rest ofscripts/patchers/at commitd90371a. The findings — what each patch targets, why a matcher overfit, which sites are the XNU-backed ones — carried into the Swift port and are why this file is kept; the commands are not runnable as written. There is no Python in this repository and no environment to install any into. The port lives inSources/FirmwarePatcher/Kernel/JailbreakPatches/; to run anything below, recover the patchers from git history at78cbeea.
Overview
scripts/patchers/kernel_jb.py has 24 patch methods in find_all(). Current status:
- 24 PASSING: All patches implemented and functional
- 0 FAILING
Two methods added since initial document: patch_shared_region_map, patch_io_secure_bsd_root.
Three previously failing patches (patch_nvram_verify_permission, patch_thid_should_crash, patch_hook_cred_label_update_execve) have been implemented — see details below.
On 2026-03-06, three patches were retargeted after IDA-MCP re-analysis revealed their matchers were hitting wrong sites:
patch_bsd_init_auth— was hittingexec_handle_sugidinstead of the realbsd_initrootauth gatepatch_io_secure_bsd_root— was patching the"SecureRoot"dispatch branch instead of the"SecureRootName"deny-returnpatch_vm_fault_enter_prepare— was NOPing apmap_lock_phys_page()call instead of the upstreamcs_bypassgate
Upstream reference: /Users/qaq/Documents/GitHub/super-tart-vphone/CFW/patch_fw.py
Test kernel: vm/iPhone17,3_26.1_23B85_Restore/kernelcache.release.vphone600 (IM4P-wrapped, bvx2 compressed)
Key facts about the kernel:
- 0 symbols resolved (fully stripped)
base_va = 0xFFFFFE0007004000(typical PCC)kern_text = 0xA74000 - 0x24B0000- All offsets in
kernel.pyhelpers are file offsets (not VA) bl_callersdict: keyed by file offset → list of caller file offsets
Patch 1: patch_nvram_verify_permission — FAILING
Upstream Reference
One single NOP at file offset 0x1234034. The BL being NOPed calls memmove (3114 callers).
Function Analysis
Function start: 0x1233E40 (PACIBSP)
Function end: 0x1234094 (next PACIBSP)
Size: 0x254 bytes
BL callers: 0 (IOKit virtual method, dispatched via vtable)
Instruction: retab at end
Full BL targets in the function:
Key instructions in the function:
CASAat +0x54 (offset 0x1233E94) — atomic compare-and-swap for lock acquisitionCASLat 3 locations — lock release- 4x
BLRAA— authenticated indirect calls through vtable pointers movk x17, #0xcda1, lsl #48— PAC discriminator for IONVRAMController classRETAB— PAC returnmov x8, #-1; str x8, [x19]— cleanup pattern near endubfiz x2, x8, #3, #0x20before BL memmove — size = count * 8
"Remove from array" pattern (at patch site):
What I've Tried (and Failed)
-
"krn." string anchor → Leads to function at
0x11F7EE8, NOT0x1233E40. Wrong function entirely. -
"nvram-write-access" entitlement string → Also leads to a different function.
-
CASA + 0 callers + retab + ubfiz + memmove filter → 332 matches. All IOKit virtual methods follow the same "remove observer from array" pattern with CASA locking.
-
IONVRAMController metaclass string → Found at
0xA2FEB. Has ADRP+ADD refs at0x125D2C0,0x125D310,0x125D38C(metaclass constructors). These set up the metaclass, NOT instance methods. -
Chained fixup pointer search for IONVRAMController string → Failed (different encoding).
Findings That DO Work
IONVRAMController vtable found via chained fixup search:
The verifyPermission function at 0x1233E40 is referenced as a chained fixup pointer in __DATA_CONST:
Vtable layout at 0x7410B8:
IONVRAMController metaclass constructor pattern:
There's ALSO a combined class registration function at 0x12376D8 that registers multiple classes and references the instance vtable:
Wait — it actually points to 0x7410A8, not 0x7410B8. The vtable pointer with the +0x10 adjustment gives 0x7410A8 + 0x10 = 0x7410B8 which is entry [0]. This is how IOKit vtables work: the isa pointer stores vtable_base + 0x10 to skip the RTTI header.
Proposed Dynamic Strategy
Chain: "IONVRAMController" string → ADRP+ADD refs → metaclass constructor → extract instance size 0x88 → find the combined class registration function (0x12376D8) that calls OSMetaClass::OSMetaClass() with mov w3, #0x88 AND uses "IONVRAMController" name → extract the vtable base from the ADRP+ADD+ADD that follows → vtable[0] = verifyPermission → find BL to memmove-like target (>2000 callers) and NOP it.
Alternative (simpler): From the metaclass constructor, extract the PAC discriminator #0xcda1 and the instance size #0x88. Then search __DATA_CONST for chained fixup pointer entries where:
- The preceding 3 entries (at -8, -16, -24) are NULL (vtable header)
- The decoded function pointer has 0 BL callers
- The function contains CASA
- The function ends with RETAB
- The function contains a BL to memmove (>2000 callers)
- The function contains
movk x17, #0xcda1(the IONVRAMController PAC discriminator)
This last filter is the KEY discriminator. Among the 332 candidate functions, only IONVRAMController methods use PAC disc 0xcda1. Combined with "first entry in vtable" (preceded by 3 nulls), this should be unique.
Simplest approach: Search all chained fixup pointers in __DATA_CONST where:
- Preceded by 3 null entries (vtable start)
- Decoded target is a function in kern_text
- Function contains
movk x17, #0xcda1, lsl #48 - Function contains BL to target with >2000 callers (memmove)
- NOP that BL
Patch 2: patch_thid_should_crash — FAILING
Upstream Reference
Writes 4 bytes of zero at file offset 0x67EB50.
Analysis
- Offset
0x67EB50is in a DATA segment (not code) - The current value at this offset is already 0x00000000 in the test kernel
- This is a sysctl boolean variable (
kern.thid_should_crash) - The patch is effectively a no-op on this kernel
What I've Tried
- Symbol resolution → 0 symbols, fails.
- "thid_should_crash" string → Found, but has no ADRP+ADD code references. The string is in
__PRELINK_INFO(XML plist), not in a standalone__cstringsection. - Sysctl structure search → Searched for a raw VA pointer to the string in DATA segments. Failed because the string VA is in the plist text, not a standalone pointer.
- Pattern search for value=1 → The value is already 0 at the upstream offset, so searching for value=1 finds nothing.
Proposed Dynamic Strategy
The variable at 0x67EB50 is in the kernel's __DATA segment (BSS or initialized data). Since:
- The string is only in
__PRELINK_INFO(plist), not usable as a code anchor - The variable has no symbols
- The value is already 0
Option A: Skip this patch gracefully. If the value is already 0, the patch has no effect. Log a message and return True (success, nothing to do).
Option B: Find via sysctl table structure. The sysctl_oid structure in __DATA contains:
- A pointer to the name string
- A pointer to the data variable
- Various flags
But the name string pointer would be a chained fixup pointer to the string in __PRELINK_INFO, which is hard to search for.
Option C: Find via __PRELINK_INFO plist parsing. Parse the XML plist to find the _PrelinkKCID or sysctl registration info. This is complex and fragile.
Recommended: Option A — the variable is already 0 in PCC kernels. Emit a write-zero anyway at the upstream-equivalent location if we can find it, or just return True if we can't find the variable (safe no-op).
Actually, better approach: search __DATA segments for a sysctl_oid struct. The struct layout includes:
So search all __DATA segments for an 8-byte value at offset +0x28 that decodes to the "thid_should_crash" string offset. Then read +0x18 to get the variable pointer.
But the string is in __PRELINK_INFO, which complicates decoding the chained fixup pointer.
Patch 3: patch_hook_cred_label_update_execve — FAILING
Upstream Reference
Why It Fails
The patch needs two kernel functions that have no symbols:
_vfs_context_currentat file offset0xCC5EAC_vnode_getattrat file offset0xCC91C0
Without these, the shellcode can't be assembled (the BL offsets depend on the target addresses).
Analysis of _vfs_context_current (0xCC5EAC)
Let me verify: check bl_callers.get(0xCC5EAC, []) — should have many callers.
Analysis of _vnode_getattr (0xCC91C0)
Finding Strategy for _vfs_context_current
-
From sandbox ops table: We already have
_find_sandbox_ops_table_via_conf(). The hook_cred_label_update_execve entry (index 16) in the ops table points to the original sandbox hook function (at0x239A0B4per upstream). -
From the original hook function: Disassemble the original hook function. It likely calls
_vfs_context_current(to get the VFS context for vnode operations). Find the BL target in the hook that has a very high caller count — that's likely_vfs_context_current. -
Pattern match: Search kern_text for short functions (size < 0x20) with:
mrs xN, TPIDR_EL1instruction- Very high caller count (>1000)
- Return type is pointer (loads from struct offset)
Finding Strategy for _vnode_getattr
-
From the original hook function: The hook function likely also calls
_vnode_getattr. Find BL targets in the hook that have moderate caller count. -
String anchor: Search for
"vnode_getattr"string (not in plist but in__cstring). Find ADRP+ADD refs, trace to function. -
Pattern match: The function signature includes a
vnode_attrstructure initialization with size0x380.
Proposed Implementation
Patch Status Summary
Previously Fixed Patches
patch_task_for_pid — FIXED
Problem: Old code searched for "proc_ro_ref_task" string → wrong function.
Solution: Pattern search: 0 BL callers + 2x ldadda + 2x ldr wN,[xN,#0x490]; str wN,[xN,#0xc] + movk #0xc8a2 + non-panic BL >500 callers. NOP the second ldr wN,[xN,#0x490].
Upstream: patch(0xFC383C, 0xd503201f) — NOP in function at 0xFC3718.
patch_load_dylinker — FIXED
Problem: Old code searched for "/usr/lib/dyld" → wrong function (0 BL callers, no string ref).
Solution: Search for functions with 3+ TST xN, #-0x40000000000000; B.EQ; MOVK xN, #0xc8a2 triplets and 0 BL callers. Replace LAST TST with unconditional B to B.EQ target.
Upstream: patch(0x1052A28, B #0x44) — in function at 0x105239C.
patch_syscallmask_apply_to_proc — FIXED
Historical problem: the earlier repo-side “fix” still matched the wrong place. Runtime verification later showed the old hit landed in _profile_syscallmask_destroy underflow handling, not the real syscallmask apply wrapper.
Current understanding: faithful upstream C22 is a low-wrapper shellcode patch that mutates the effective Unix/Mach/KOBJ mask bytes to all 0xFF, then continues into the normal setter. It is not a NULL-mask install and not an early-return patch.
Current status: rebuilt structurally as a 3-write retarget (save selector, branch to cave, all-ones cave + setter tail) and separately documented in Research/KernelJailbreakPatches/patch_syscallmask_apply_to_proc.md; user reported boot success with the rebuilt C22 on 2026-03-06.
patch_iouc_failed_macf — RETARGETED
Historical repo behavior: patched 0xFFFFFE000825B0C0 at entry with mov x0, xzr ; retab after PACIBSP.
Problem: fresh IDA review shows this is a large IOUserClient open/setup path, not a tiny standalone deny helper; entry early-return skips broader work including output-state preparation.
Current status: rebuilt as A5-v2. It now patches only the narrow post-mac_iokit_check_open gate in the same function: 0xFFFFFE000825BA98 (CBZ W0, allow) becomes unconditional B allow. Focused dry-run emits exactly one write at file offset 0x01257A98.
patch_nvram_verify_permission — FIXED
Problem: 332 identical IOKit methods match structural filter; "krn." string leads to wrong function. Solution: Uses "krn." string anchor to find the function, then NOPs TBZ/TBNZ guard near the string ref. Different mechanism from upstream (NOP BL memmove) but achieves the same NVRAM bypass.
patch_thid_should_crash — FIXED
Problem: String in __PRELINK_INFO plist (no code refs); value already 0x00000000 in PCC kernel.
Solution: Multi-strategy approach — symbol lookup, string search + sysctl_oid struct scanning (checking forward 128 bytes for chained fixup pointers), and ADRP+ADD fallback.
patch_hook_cred_label_update_execve — FIXED
Problem: Needed _vfs_context_current and _vnode_getattr — 0 symbols available.
Solution: Eliminated _vfs_context_current entirely — shellcode constructs vfs_context inline on stack via mrs x8, tpidr_el1 + stp x8, x0, [sp, #0x70]. _vnode_getattr found via "vnode_getattr" string anchor. Hook index found dynamically (scan first 30 ops entries). Code cave allocated via _find_code_cave(180).
patch_bsd_init_auth — RETARGETED (2026-03-06)
Historical repo behavior: matched ldr x0,[xN,#0x2b8]; cbz x0; bl pattern, which landed on exec_handle_sugid at 0xFFFFFE0007FB09DC — a false positive caused by /dev/null string overlap in the heuristic scoring.
Problem: the old matcher targeted the wrong function entirely; patching exec_handle_sugid instead of the real bsd_init rootauth gate could break boot by mutating an exec/credential path.
Current status: retargeted to the real FSIOC_KERNEL_ROOTAUTH return check in bsd_init. The new matcher recovers bsd_init via in-kernel string xrefs, locates the rootvp panic block ("rootvp not authenticated after mounting"), finds the unique in-function indirect call (BLRAA) preceded by the 0x80046833 (FSIOC_KERNEL_ROOTAUTH) literal, and NOPs the subsequent CBNZ W0, panic. Live patch hit: 0xFFFFFE0007F7B98C / file offset 0x00F7798C. See Research/KernelJailbreakPatches/patch_bsd_init_auth.md.
patch_io_secure_bsd_root — RETARGETED (2026-03-06)
Historical repo behavior: fallback heuristic selected the first BL* + CBZ W0 site in AppleARMPE::callPlatformFunction, landing on the "SecureRoot" name-match gate at 0xFFFFFE000836E1F0 / file offset 0x0136A1F0. This changed generic platform-function dispatch routing, not just the deny return.
Problem: the patched branch was the isEqualTo("SecureRoot") check, not the "SecureRootName" policy result used by IOSecureBSDRoot(). The old CBZ->B rewrite could corrupt control flow for unrelated platform-function calls.
Current status: retargeted to the final "SecureRootName" deny-return selector: CSEL W22, WZR, W9, NE at 0xFFFFFE000836E464 / file offset 0x0136A464 is replaced with MOV W22, #0. This preserves the string comparison, callback synchronization, and state updates, and only forces the final policy return from kIOReturnNotPrivileged to success. See Research/KernelJailbreakPatches/patch_io_secure_bsd_root.md.
patch_vm_fault_enter_prepare — RETARGETED (2026-03-06)
Historical repo behavior: matcher looked for BL(rare) + LDRB [xN,#0x2c] + TBZ and NOPed the BL at 0xFFFFFE0007BB898C, which was actually a pmap_lock_phys_page() call inside the VM_PAGE_CONSUME_CLUSTERED macro — breaking lock/unlock pairing in the VM fault path.
Problem: the derived matcher overfit the wrong local shape. The upstream 26.1 patch targeted the cs_bypass fast-path gate (TBZ W22, #3), not the clustered-page lock helper. NOPing only the lock acquire while the unlock still ran caused unbalanced lock state, explaining boot failures.
Current status: retargeted to the upstream semantic site — TBZ W22, #3, ... (where W22 bit 3 = fault_info->cs_bypass) at file offset 0x00BA9E1C / VA 0xFFFFFE0007BADE1C is replaced with NOP, forcing the cs_bypass fast path unconditionally. This matches XNU's vm_fault_cs_check_violation() logic and preserves lock pairing and page accounting. See Research/KernelJailbreakPatches/patch_vm_fault_enter_prepare.md.
Environment Notes
Both recipes below are records of how this was run on 2026-03-07, not instructions. The Python environment they assume does not exist any more.
Running on macOS (as it was then)
Running on Linux (cloud)
Requirements:
- Python 3.10+
pip install capstone keystone-engine pyimg4- Note:
keystone-enginemay needcmakeand C++ compiler on Linux - Copy the kernelcache file and upstream reference
- The
setup_venv.shscript has macOS-specific keystone dylib handling — on Linux, pip install should work directly
Files needed:
scripts/patchers/kernel.py(base class)scripts/patchers/kernel_jb.py(JB patcher)scripts/patchers/__init__.pyscripts/fw_patch.py(forload_firmware())vm/iPhone17,3_26.1_23B85_Restore/kernelcache.release.vphone600(test kernel)/Users/qaq/Documents/GitHub/super-tart-vphone/CFW/patch_fw.py(upstream reference)