Files
vphone-cli/scripts/patchers/kernel_jb_patch_hook_cred_label.py
Lakr fd8e8d184c Squash merge iunctqwiocmfxhigkcunamxoi into main
Included commits:

- f8a54b8 Update JB kernel patch research notes
  Refresh and revalidate jailbreak kernel-patcher documentation and runtime-verification notes. Key updates: re-analyzed B13 (patch_bsd_init_auth) and retargeted recommended site to the FSIOC_KERNEL_ROOTAUTH return check in bsd_init rather than the old ldr/cbz/bl heuristic; clarified preferred NOP-of-CBNZ vs forcing ioctl return. Reworked C21 (patch_cred_label_update_execve) to preserve AMFI exec-time flow and instead clear restrictive csflags in a success-tail trampoline; disabled in default schedule until boot validation. Documented that C23 (patch_hook_cred_label_update_execve) was mis-targeting the wrapper (sub_FFFFFE00093D2CE4) instead of the real hook body (_hook_cred_label_update_execve), explaining boot failures and recommending retargeting. Noted syscallmask and vm_fault matcher problems (patch_syscallmask_apply_to_proc historical hit targeted _profile_syscallmask_destroy; patch_vm_fault_enter_prepare matcher resolves to pmap_lock_phys_page path), and updated the runtime-verification summary with follow-up findings and which methods are temporarily commented out/disabled in the default KernelJBPatcher schedule pending staged re-validation.
- 6ebac65 fix: patch_bsd_init_auth
- 5b224d3 fix: patch_io_secure_bsd_root
- e6806bf docs: update patch notes
- 0d89c5c Retarget vm_fault_enter_prepare jailbreak patch
- 6b9d79b Rework C21 late-exit cred_label patch
- ece8cc0 Clean C21 mov matcher encodings
- ad2ea7c enabled fixed patch_cred_label_update_execve
- c37b6b1 Rebuild syscallmask C22 patch
- 363dd7a Rebuild JB C23 as faithful upstream trampoline
- 129e648 Disable IOUC MACF; rebuild kcall10 & C22 docs
  Re-evaluate and rework several JB kernel patches and docs: mark patch_iouc_failed_macf as reverted/disabled (repo-local, over-broad early-return) and replace its patcher with a no-op implementation to emit zero writes by default; update research notes to explain the reanalysis and rationale. Rebuild patch_kcall10: replace the historical 10-arg design with an ABI-correct syscall-439 cave (target + 7 args -> uint64 return), add a new cave builder and munge32 reuse logic in the kcall10 patcher, and enable the method in KernelJBPatcher group. Clarify syscallmask (C22) semantics in docs: upstream C22 is an all-ones-mask retarget (not a NULL install) and keep the rebuilt all-ones wrapper as the authoritative baseline. Misc: minor refactors and helper additions (chained-pointer helpers, cave size/constants, validation and dry-run safeguards) to improve correctness and alignment with IDA/runtime verification.
- e1b2365 Rebuild kcall10 as ABI-correct syscall cave
- 23090d0 fix patch_iouc_failed_macf
- 0056be2 Normalize formatting in research docs
  Apply whitespace and formatting cleanup across research markdown files for consistency and readability. Adjust table alignment and spacing in 00_patch_comparison_all_variants.md, normalize list/indentation spacing in patch_bsd_init_auth.md and patch_syscallmask_apply_to_proc.md, and add/clean blank lines and minor spacing in patch_kcall10.md. These are non-functional documentation changes only.
2026-03-06 19:08:16 +08:00

256 lines
9.5 KiB
Python

"""Mixin: KernelJBPatchHookCredLabelMixin."""
import struct
from .kernel_asm import asm, _PACIBSP_U32, _asm_u32
from .kernel_jb_base import _rd32, _rd64
class KernelJBPatchHookCredLabelMixin:
_HOOK_CRED_LABEL_INDEX = 18
_C23_CAVE_WORDS = 46
_VFS_CONTEXT_CURRENT_SHAPE = (
_PACIBSP_U32,
_asm_u32("stp x29, x30, [sp, #-0x10]!"),
_asm_u32("mov x29, sp"),
_asm_u32("mrs x0, tpidr_el1"),
_asm_u32("ldr x1, [x0, #0x3e0]"),
)
def _find_vnode_getattr_via_string(self):
"""Resolve vnode_getattr from a nearby BL around its log string."""
str_off = self.find_string(b"vnode_getattr")
if str_off < 0:
return -1
refs = self.find_string_refs(str_off)
if not refs:
return -1
start = str_off
for _ in range(6):
refs = self.find_string_refs(start)
if refs:
ref_off = refs[0][0]
for scan_off in range(ref_off - 4, ref_off - 80, -4):
if scan_off < 0:
break
insn = _rd32(self.raw, scan_off)
if (insn >> 26) != 0x25:
continue
imm26 = insn & 0x3FFFFFF
if imm26 & (1 << 25):
imm26 -= 1 << 26
target = scan_off + imm26 * 4
if any(s <= target < e for s, e in self.code_ranges):
self._log(
f" [+] vnode_getattr at 0x{target:X} "
f"(via BL at 0x{scan_off:X}, near string ref 0x{ref_off:X})"
)
return target
next_off = self.find_string(b"vnode_getattr", start + 1)
if next_off < 0:
break
start = next_off
return -1
def _find_vfs_context_current_via_shape(self):
"""Locate the concrete vfs_context_current body by its unique prologue."""
key = ("c23_vfs_context_current", self.kern_text)
cached = self._jb_scan_cache.get(key)
if cached is not None:
return cached
ks, ke = self.kern_text
hits = []
pat = self._VFS_CONTEXT_CURRENT_SHAPE
for off in range(ks, ke - len(pat) * 4, 4):
if all(_rd32(self.raw, off + i * 4) == pat[i] for i in range(len(pat))):
hits.append(off)
result = hits[0] if len(hits) == 1 else -1
if result >= 0:
self._log(f" [+] vfs_context_current body at 0x{result:X} (shape match)")
else:
self._log(f" [-] vfs_context_current shape scan ambiguous ({len(hits)} hits)")
self._jb_scan_cache[key] = result
return result
def _find_hook_cred_label_update_execve_wrapper(self):
"""Resolve the faithful upstream C23 target: sandbox ops[18] wrapper."""
ops_table = self._find_sandbox_ops_table_via_conf()
if ops_table is None:
self._log(" [-] sandbox ops table not found")
return None
entry_off = ops_table + self._HOOK_CRED_LABEL_INDEX * 8
if entry_off + 8 > self.size:
self._log(" [-] hook ops entry outside file")
return None
entry_raw = _rd64(self.raw, entry_off)
if entry_raw == 0:
self._log(" [-] hook ops entry is null")
return None
if (entry_raw & (1 << 63)) == 0:
self._log(
f" [-] hook ops entry is not auth-rebase encoded: 0x{entry_raw:016X}"
)
return None
wrapper_off = self._decode_chained_ptr(entry_raw)
if wrapper_off < 0 or not any(s <= wrapper_off < e for s, e in self.code_ranges):
self._log(f" [-] decoded wrapper target invalid: 0x{wrapper_off:X}")
return None
self._log(
f" [+] hook cred-label wrapper ops[{self._HOOK_CRED_LABEL_INDEX}] "
f"entry 0x{entry_off:X} -> 0x{wrapper_off:X}"
)
return ops_table, entry_off, entry_raw, wrapper_off
def _encode_auth_rebase_like(self, orig_val, target_off):
"""Retarget an auth-rebase chained pointer while preserving PAC metadata."""
if (orig_val & (1 << 63)) == 0:
return None
return struct.pack("<Q", (orig_val & ~0xFFFFFFFF) | (target_off & 0xFFFFFFFF))
def _build_upstream_c23_cave(
self,
cave_off,
vfs_context_current_off,
vnode_getattr_off,
wrapper_off,
):
code = []
code.append(asm("nop"))
code.append(asm("cbz x3, #0xa8"))
code.append(asm("sub sp, sp, #0x400"))
code.append(asm("stp x29, x30, [sp]"))
code.append(asm("stp x0, x1, [sp, #0x10]"))
code.append(asm("stp x2, x3, [sp, #0x20]"))
code.append(asm("stp x4, x5, [sp, #0x30]"))
code.append(asm("stp x6, x7, [sp, #0x40]"))
code.append(asm("nop"))
bl_vfs_off = cave_off + len(code) * 4
bl_vfs = self._encode_bl(bl_vfs_off, vfs_context_current_off)
if not bl_vfs:
return None
code.append(bl_vfs)
code.append(asm("mov x2, x0"))
code.append(asm("ldr x0, [sp, #0x28]"))
code.append(asm("add x1, sp, #0x80"))
code.append(asm("mov w8, #0x380"))
code.append(asm("stp xzr, x8, [x1]"))
code.append(asm("stp xzr, xzr, [x1, #0x10]"))
code.append(asm("nop"))
bl_getattr_off = cave_off + len(code) * 4
bl_getattr = self._encode_bl(bl_getattr_off, vnode_getattr_off)
if not bl_getattr:
return None
code.append(bl_getattr)
code.append(asm("cbnz x0, #0x4c"))
code.append(asm("mov w2, #0"))
code.append(asm("ldr w8, [sp, #0xcc]"))
code.append(asm("tbz w8, #0xb, #0x14"))
code.append(asm("ldr w8, [sp, #0xc4]"))
code.append(asm("ldr x0, [sp, #0x18]"))
code.append(asm("str w8, [x0, #0x18]"))
code.append(asm("mov w2, #1"))
code.append(asm("ldr w8, [sp, #0xcc]"))
code.append(asm("tbz w8, #0xa, #0x14"))
code.append(asm("mov w2, #1"))
code.append(asm("ldr w8, [sp, #0xc8]"))
code.append(asm("ldr x0, [sp, #0x18]"))
code.append(asm("str w8, [x0, #0x28]"))
code.append(asm("cbz w2, #0x14"))
code.append(asm("ldr x0, [sp, #0x20]"))
code.append(asm("ldr w8, [x0, #0x454]"))
code.append(asm("orr w8, w8, #0x100"))
code.append(asm("str w8, [x0, #0x454]"))
code.append(asm("ldp x0, x1, [sp, #0x10]"))
code.append(asm("ldp x2, x3, [sp, #0x20]"))
code.append(asm("ldp x4, x5, [sp, #0x30]"))
code.append(asm("ldp x6, x7, [sp, #0x40]"))
code.append(asm("ldp x29, x30, [sp]"))
code.append(asm("add sp, sp, #0x400"))
code.append(asm("nop"))
branch_back_off = cave_off + len(code) * 4
branch_back = self._encode_b(branch_back_off, wrapper_off)
if not branch_back:
return None
code.append(branch_back)
code.append(asm("nop"))
if len(code) != self._C23_CAVE_WORDS:
raise RuntimeError(
f"C23 cave length drifted: {len(code)} insns, expected {self._C23_CAVE_WORDS}"
)
return b"".join(code)
def patch_hook_cred_label_update_execve(self):
"""Faithful upstream C23: wrapper trampoline + setugid credential fixup.
Historical upstream behavior does not short-circuit the sandbox execve
update hook. It redirects `mac_policy_ops[18]` to a code cave that:
- fetches vnode owner/mode via vnode_getattr(vp, vap, vfs_context_current()),
- copies VSUID/VSGID owner values into the pending new credential,
- sets P_SUGID when either credential field changes,
- then branches back to the original sandbox wrapper.
"""
self._log("\n[JB] _hook_cred_label_update_execve: faithful upstream C23")
wrapper_info = self._find_hook_cred_label_update_execve_wrapper()
if wrapper_info is None:
return False
_, entry_off, entry_raw, wrapper_off = wrapper_info
vfs_context_current_off = self._find_vfs_context_current_via_shape()
if vfs_context_current_off < 0:
self._log(" [-] vfs_context_current not resolved")
return False
vnode_getattr_off = self._find_vnode_getattr_via_string()
if vnode_getattr_off < 0:
self._log(" [-] vnode_getattr not resolved")
return False
cave_size = self._C23_CAVE_WORDS * 4
cave_off = self._find_code_cave(cave_size)
if cave_off < 0:
self._log(" [-] no executable code cave found for faithful C23")
return False
cave_bytes = self._build_upstream_c23_cave(
cave_off,
vfs_context_current_off,
vnode_getattr_off,
wrapper_off,
)
if cave_bytes is None:
self._log(" [-] failed to encode faithful C23 branch/call relocations")
return False
new_entry = self._encode_auth_rebase_like(entry_raw, cave_off)
if new_entry is None:
self._log(" [-] failed to encode hook ops entry retarget")
return False
self.emit(
entry_off,
new_entry,
"retarget ops[18] to faithful C23 cave [_hook_cred_label_update_execve]",
)
self.emit(
cave_off,
cave_bytes,
"faithful upstream C23 cave (vnode getattr -> uid/gid/P_SUGID fixup -> wrapper)",
)
return True