mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
174 lines
8.1 KiB
Python
174 lines
8.1 KiB
Python
"""Mixin: KernelJBPatchHookCredLabelMixin."""
|
|
|
|
from .kernel_jb_base import asm, _rd32, _rd64, RET, NOP, struct
|
|
|
|
|
|
class KernelJBPatchHookCredLabelMixin:
|
|
def patch_hook_cred_label_update_execve(self):
|
|
"""Redirect _hook_cred_label_update_execve ops table entry to shellcode.
|
|
|
|
Patches the sandbox MAC ops table entry for cred_label_update_execve
|
|
to point to custom shellcode that performs vnode_getattr ownership
|
|
propagation. Instead of calling vfs_context_current (which may not
|
|
exist as a BL-callable function), we construct a vfs_context on the
|
|
stack using current_thread (mrs tpidr_el1) and the caller's
|
|
credential (x0 = old_cred).
|
|
"""
|
|
self._log("\n[JB] _hook_cred_label_update_execve: ops table + shellcode")
|
|
|
|
# ── 1. Find vnode_getattr via string anchor ──────────────
|
|
vnode_getattr_off = self._resolve_symbol("_vnode_getattr")
|
|
if vnode_getattr_off < 0:
|
|
str_off = self.find_string(b"vnode_getattr")
|
|
if str_off >= 0:
|
|
refs = self.find_string_refs(str_off)
|
|
if refs:
|
|
vnode_getattr_off = self.find_function_start(refs[0][0])
|
|
if vnode_getattr_off >= 0:
|
|
self._log(
|
|
f" [+] vnode_getattr at 0x"
|
|
f"{vnode_getattr_off:X} (via string)"
|
|
)
|
|
|
|
if vnode_getattr_off < 0:
|
|
self._log(" [-] vnode_getattr not found")
|
|
return False
|
|
|
|
# ── 2. Find sandbox ops table ────────────────────────────
|
|
ops_table = self._find_sandbox_ops_table_via_conf()
|
|
if ops_table is None:
|
|
self._log(" [-] sandbox ops table not found")
|
|
return False
|
|
|
|
# ── 3. Find hook index dynamically ───────────────────────
|
|
# mpo_cred_label_update_execve is one of the largest sandbox
|
|
# hooks at an early index (< 30). Scan for it.
|
|
hook_index = -1
|
|
orig_hook = -1
|
|
best_size = 0
|
|
for idx in range(0, 30):
|
|
entry = self._read_ops_entry(ops_table, idx)
|
|
if entry is None or entry <= 0:
|
|
continue
|
|
if not any(s <= entry < e for s, e in self.code_ranges):
|
|
continue
|
|
fend = self._find_func_end(entry, 0x2000)
|
|
fsize = fend - entry
|
|
if fsize > best_size:
|
|
best_size = fsize
|
|
hook_index = idx
|
|
orig_hook = entry
|
|
|
|
if hook_index < 0 or best_size < 1000:
|
|
self._log(
|
|
" [-] hook entry not found in ops table "
|
|
f"(best: idx={hook_index}, size={best_size})"
|
|
)
|
|
return False
|
|
|
|
self._log(
|
|
f" [+] hook at ops[{hook_index}] = 0x{orig_hook:X} ({best_size} bytes)"
|
|
)
|
|
|
|
# ── 4. Find code cave ────────────────────────────────────
|
|
cave = self._find_code_cave(180)
|
|
if cave < 0:
|
|
self._log(" [-] no code cave found")
|
|
return False
|
|
self._log(f" [+] code cave at 0x{cave:X}")
|
|
|
|
# ── 5. Encode BL to vnode_getattr ────────────────────────
|
|
vnode_bl_off = cave + 17 * 4
|
|
vnode_bl = self._encode_bl(vnode_bl_off, vnode_getattr_off)
|
|
if not vnode_bl:
|
|
self._log(" [-] BL to vnode_getattr out of range")
|
|
return False
|
|
|
|
# ── 6. Encode B to original hook ─────────────────────────
|
|
b_back_off = cave + 44 * 4
|
|
b_back = self._encode_b(b_back_off, orig_hook)
|
|
if not b_back:
|
|
self._log(" [-] B to original hook out of range")
|
|
return False
|
|
|
|
# ── 7. Build shellcode ───────────────────────────────────
|
|
# MAC hook args: x0=old_cred, x1=new_cred, x2=proc, x3=vp
|
|
#
|
|
# Parts [8-10] construct a vfs_context on the stack instead
|
|
# of calling vfs_context_current, which may not exist as a
|
|
# direct BL target in stripped ARM64e kernels.
|
|
#
|
|
# struct vfs_context { thread_t vc_thread; kauth_cred_t vc_ucred; }
|
|
# We place it at [sp, #0x70] (between saved regs and vattr buffer).
|
|
parts = []
|
|
parts.append(NOP) # 0
|
|
parts.append(asm("cbz x3, #0xa8")) # 1
|
|
parts.append(asm("sub sp, sp, #0x400")) # 2
|
|
parts.append(asm("stp x29, x30, [sp]")) # 3
|
|
parts.append(asm("stp x0, x1, [sp, #16]")) # 4
|
|
parts.append(asm("stp x2, x3, [sp, #32]")) # 5
|
|
parts.append(asm("stp x4, x5, [sp, #48]")) # 6
|
|
parts.append(asm("stp x6, x7, [sp, #64]")) # 7
|
|
# Construct vfs_context inline (replaces BL vfs_context_current)
|
|
parts.append(asm("mrs x8, tpidr_el1")) # 8: current_thread
|
|
parts.append(asm("stp x8, x0, [sp, #0x70]")) # 9: {thread, cred}
|
|
parts.append(asm("add x2, sp, #0x70")) # 10: ctx = &vfs_ctx
|
|
# Setup vnode_getattr(vp, &vattr, ctx)
|
|
parts.append(asm("ldr x0, [sp, #0x28]")) # 11: x0 = vp
|
|
parts.append(asm("add x1, sp, #0x80")) # 12: x1 = &vattr
|
|
parts.append(asm("mov w8, #0x380")) # 13: vattr size
|
|
parts.append(asm("stp xzr, x8, [x1]")) # 14: init vattr
|
|
parts.append(asm("stp xzr, xzr, [x1, #0x10]")) # 15: init vattr
|
|
parts.append(NOP) # 16
|
|
parts.append(vnode_bl) # 17: BL vnode_getattr
|
|
# Check result + propagate ownership
|
|
parts.append(asm("cbnz x0, #0x50")) # 18: error → skip
|
|
parts.append(asm("mov w2, #0")) # 19: changed = 0
|
|
parts.append(asm("ldr w8, [sp, #0xCC]")) # 20: va_mode
|
|
parts.append(bytes([0xA8, 0x00, 0x58, 0x36])) # 21: tbz w8,#11
|
|
parts.append(asm("ldr w8, [sp, #0xC4]")) # 22: va_uid
|
|
parts.append(asm("ldr x0, [sp, #0x18]")) # 23: new_cred
|
|
parts.append(asm("str w8, [x0, #0x18]")) # 24: cred->uid
|
|
parts.append(asm("mov w2, #1")) # 25: changed = 1
|
|
parts.append(asm("ldr w8, [sp, #0xCC]")) # 26: va_mode
|
|
parts.append(bytes([0xA8, 0x00, 0x50, 0x36])) # 27: tbz w8,#10
|
|
parts.append(asm("mov w2, #1")) # 28: changed = 1
|
|
parts.append(asm("ldr w8, [sp, #0xC8]")) # 29: va_gid
|
|
parts.append(asm("ldr x0, [sp, #0x18]")) # 30: new_cred
|
|
parts.append(asm("str w8, [x0, #0x28]")) # 31: cred->gid
|
|
parts.append(asm("cbz w2, #0x1c")) # 32: if !changed
|
|
parts.append(asm("ldr x0, [sp, #0x20]")) # 33: proc
|
|
parts.append(asm("ldr w8, [x0, #0x454]")) # 34: p_csflags
|
|
parts.append(asm("orr w8, w8, #0x100")) # 35: CS_VALID
|
|
parts.append(asm("str w8, [x0, #0x454]")) # 36: store
|
|
parts.append(asm("ldp x0, x1, [sp, #16]")) # 37: restore
|
|
parts.append(asm("ldp x2, x3, [sp, #32]")) # 38
|
|
parts.append(asm("ldp x4, x5, [sp, #48]")) # 39
|
|
parts.append(asm("ldp x6, x7, [sp, #64]")) # 40
|
|
parts.append(asm("ldp x29, x30, [sp]")) # 41
|
|
parts.append(asm("add sp, sp, #0x400")) # 42
|
|
parts.append(NOP) # 43
|
|
parts.append(b_back) # 44: B orig_hook
|
|
|
|
for i, part in enumerate(parts):
|
|
self.emit(
|
|
cave + i * 4,
|
|
part,
|
|
f"shellcode+{i * 4} [_hook_cred_label_update_execve]",
|
|
)
|
|
|
|
# ── 8. Rewrite ops table entry ───────────────────────────
|
|
# Preserve auth rebase upper 32 bits (PAC key, diversity,
|
|
# chain next) and replace lower 32 bits with cave foff.
|
|
entry_off = ops_table + hook_index * 8
|
|
orig_raw = _rd64(self.raw, entry_off)
|
|
new_raw = (orig_raw & 0xFFFFFFFF00000000) | (cave & 0xFFFFFFFF)
|
|
self.emit(
|
|
entry_off,
|
|
struct.pack("<Q", new_raw),
|
|
f"ops_table[{hook_index}] = cave 0x{cave:X} "
|
|
f"[_hook_cred_label_update_execve]",
|
|
)
|
|
|
|
return True
|