mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
Fix B6 proc_security_policy: was stubbing copyio instead of real target
Root cause: "most-called BL target" heuristic in _proc_info picked copyio (4 calls, 0x28C bytes) over the real _proc_security_policy (2 calls, 0x134 bytes). Lowered size filter threshold from 0x300 to 0x200 to correctly exclude utility functions like copyio. Boot-tested: PASS.
This commit is contained in:
@@ -1,25 +1,61 @@
|
||||
# B6 `patch_proc_security_policy`
|
||||
|
||||
## How the patch works
|
||||
- Source: `scripts/patchers/kernel_jb_patch_proc_security.py`.
|
||||
## Status: FIXED (was PANIC)
|
||||
|
||||
## Root cause of failure
|
||||
The patcher's heuristic picked the **wrong function** to stub:
|
||||
- Found `_proc_info` correctly via `sub wN,wM,#1; cmp wN,#0x21` switch pattern
|
||||
- Took the "most-called BL target" within proc_info as `_proc_security_policy`
|
||||
- The most-called function (4 calls) was actually **copyio** (`sub_FFFFFE0007C4DD48`), a
|
||||
generic copy-to-userspace utility used everywhere in the kernel (100+ xrefs)
|
||||
- Stubbing copyio with `mov x0,#0; ret` broke all copyin/copyout operations
|
||||
- Result: "Process 1 exec of /sbin/launchd failed, errno 2" (can't load launchd binary)
|
||||
|
||||
## Fix applied
|
||||
Changed the heuristic from "most-called BL target" to a filtered approach:
|
||||
1. Only count BL targets AFTER the switch dispatch (security policy is called within
|
||||
switch cases, not in the prologue)
|
||||
2. Filter by function size: skip large functions >0x300 bytes (copyio and other utilities
|
||||
are large; `_proc_security_policy` is ~0x130 bytes)
|
||||
3. Skip tiny functions <0x40 bytes (trivial helpers)
|
||||
|
||||
## IDA MCP evidence
|
||||
|
||||
### The wrong target (copyio)
|
||||
- VA: `0xFFFFFE0007C4DD48` (file offset `0xC49D48`)
|
||||
- References "copyio.c" and "copy_ensure_address_space_spec"
|
||||
- 100+ xrefs from across the entire kernel
|
||||
- Large function handling address space operations
|
||||
|
||||
### The real `_proc_security_policy`
|
||||
- VA: `0xFFFFFE0008067148` (file offset `0x1063148`)
|
||||
- Only 6 xrefs, all from proc_info-related functions:
|
||||
- `sub_FFFFFE0008064A30` (_proc_info main handler) x2
|
||||
- `sub_FFFFFE0008065540` x1
|
||||
- `sub_FFFFFE0008065F6C` x1
|
||||
- `sub_FFFFFE0008066624` x1
|
||||
- `sub_FFFFFE0008064078` x1
|
||||
- Function size: ~0x128 bytes
|
||||
- Behavior: calls current_proc, does MAC policy check via indirect call, returns 0/error
|
||||
|
||||
### `_proc_info` function
|
||||
- VA: `0xFFFFFE0008064A30`, size `0x9F8`
|
||||
- Switch table at `0xFFFFFE0008064AA0`: `SUB W28, W25, #1; CMP W28, #0x21`
|
||||
- BL target counts in proc_info:
|
||||
- copyio (0x7C4DD48): 4 calls (most-called, WRONG target)
|
||||
- proc_security_policy (0x8067148): 2 calls (correct target)
|
||||
|
||||
## How the patch works (fixed version)
|
||||
- Locator strategy:
|
||||
1. Try symbol `_proc_security_policy`.
|
||||
2. If stripped, locate `_proc_info` by a switch-shape signature (`sub wN, wM, #1` + `cmp wN, #0x21`).
|
||||
3. Inside that function, count BL targets and pick the most-called one as `_proc_security_policy`.
|
||||
- Patch action: overwrite target function entry with:
|
||||
- `mov x0, #0`
|
||||
- `ret`
|
||||
2. If stripped, locate `_proc_info` by switch-shape signature.
|
||||
3. Count BL targets after the switch dispatch.
|
||||
4. Filter candidates by size (0x40-0x300 bytes) to exclude utilities.
|
||||
5. Pick the best match.
|
||||
- Patch action: overwrite entry with `mov x0, #0; ret`
|
||||
|
||||
## Expected outcome
|
||||
- Force `proc_security_policy` checks to return success.
|
||||
|
||||
## Target
|
||||
- Targeted logic is the centralized process-security policy gate used by `proc_info` family paths.
|
||||
|
||||
## IDA MCP evidence (current state)
|
||||
- This patch is mainly pattern-driven and symbol-first; this IDB is stripped.
|
||||
- I validated related immediate patterns and candidate comparison sites in kernel text, but did not obtain a single uniquely provable `_proc_info -> most-called callee` chain within MCP single-call time limits.
|
||||
- Status: behavior-level analysis confirmed from patch code; exact final function address is pending a longer multi-pass IDA sweep.
|
||||
- Force `proc_security_policy` checks to return success (allow any process to query proc_info).
|
||||
|
||||
## Risk
|
||||
- Turning this policy function into unconditional success can over-broaden process introspection and privilege surfaces.
|
||||
- Over-broadens process introspection (any process can read info about any other process).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Mixin: KernelJBPatchProcSecurityMixin."""
|
||||
|
||||
from .kernel_jb_base import ARM64_OP_IMM, MOV_X0_0, RET, Counter
|
||||
from .kernel_jb_base import ARM64_OP_IMM, MOV_X0_0, RET, Counter, _rd32, struct
|
||||
|
||||
|
||||
class KernelJBPatchProcSecurityMixin:
|
||||
@@ -8,8 +8,10 @@ class KernelJBPatchProcSecurityMixin:
|
||||
"""Stub _proc_security_policy: mov x0,#0; ret.
|
||||
|
||||
Anchor: find _proc_info via its distinctive switch-table pattern
|
||||
(sub wN,wM,#1; cmp wN,#0x21), then identify the most-called BL
|
||||
target within that function — that's _proc_security_policy.
|
||||
(sub wN,wM,#1; cmp wN,#0x21), then identify _proc_security_policy
|
||||
among BL targets — it's called 2+ times, is a small function
|
||||
(<0x200 bytes), and is NOT called from the proc_info prologue
|
||||
(it's called within switch cases, not before the switch dispatch).
|
||||
"""
|
||||
self._log("\n[JB] _proc_security_policy: mov x0,#0; ret")
|
||||
|
||||
@@ -23,6 +25,7 @@ class KernelJBPatchProcSecurityMixin:
|
||||
# Find _proc_info by its distinctive switch table
|
||||
# Pattern: sub wN, wM, #1; cmp wN, #0x21 (33 = max proc_info callnum)
|
||||
proc_info_func = -1
|
||||
switch_off = -1
|
||||
ks, ke = self.kern_text
|
||||
for off in range(ks, ke - 8, 4):
|
||||
d = self._disas_at(off, 2)
|
||||
@@ -31,21 +34,18 @@ class KernelJBPatchProcSecurityMixin:
|
||||
i0, i1 = d[0], d[1]
|
||||
if i0.mnemonic != "sub" or i1.mnemonic != "cmp":
|
||||
continue
|
||||
# sub wN, wM, #1
|
||||
if len(i0.operands) < 3:
|
||||
continue
|
||||
if i0.operands[2].type != ARM64_OP_IMM or i0.operands[2].imm != 1:
|
||||
continue
|
||||
# cmp wN, #0x21
|
||||
if len(i1.operands) < 2:
|
||||
continue
|
||||
if i1.operands[1].type != ARM64_OP_IMM or i1.operands[1].imm != 0x21:
|
||||
continue
|
||||
# Verify same register
|
||||
if i0.operands[0].reg != i1.operands[0].reg:
|
||||
continue
|
||||
# Found it — find function start
|
||||
proc_info_func = self.find_function_start(off)
|
||||
switch_off = off
|
||||
break
|
||||
|
||||
if proc_info_func < 0:
|
||||
@@ -54,31 +54,54 @@ class KernelJBPatchProcSecurityMixin:
|
||||
|
||||
proc_info_end = self._find_func_end(proc_info_func, 0x4000)
|
||||
self._log(
|
||||
f" [+] _proc_info at 0x{proc_info_func:X} (size 0x{proc_info_end - proc_info_func:X})"
|
||||
f" [+] _proc_info at 0x{proc_info_func:X} "
|
||||
f"(size 0x{proc_info_end - proc_info_func:X})"
|
||||
)
|
||||
|
||||
# Count BL targets within _proc_info — the most frequent one
|
||||
# is _proc_security_policy (called once per switch case)
|
||||
# Count BL targets within _proc_info (only AFTER the switch dispatch,
|
||||
# since security policy is called from switch cases not the prologue)
|
||||
bl_targets = Counter()
|
||||
for off in range(proc_info_func, proc_info_end, 4):
|
||||
for off in range(switch_off, proc_info_end, 4):
|
||||
target = self._is_bl(off)
|
||||
if target >= 0 and ks <= target < ke:
|
||||
bl_targets[target] += 1
|
||||
|
||||
if not bl_targets:
|
||||
self._log(" [-] no BL targets found in _proc_info")
|
||||
self._log(" [-] no BL targets found in _proc_info switch cases")
|
||||
return False
|
||||
|
||||
# The security policy check is called the most (once per case)
|
||||
most_called = bl_targets.most_common(1)[0]
|
||||
foff = most_called[0]
|
||||
count = most_called[1]
|
||||
self._log(f" [+] most-called BL target: 0x{foff:X} ({count} calls)")
|
||||
# Find _proc_security_policy among candidates.
|
||||
# It's called 2+ times, is a small function (<0x300 bytes),
|
||||
# and is NOT a utility like copyio (which is much larger).
|
||||
for foff, count in bl_targets.most_common():
|
||||
if count < 2:
|
||||
break
|
||||
|
||||
if count < 3:
|
||||
self._log(" [-] most-called target has too few calls")
|
||||
return False
|
||||
func_end = self._find_func_end(foff, 0x400)
|
||||
func_size = func_end - foff
|
||||
|
||||
self.emit(foff, MOV_X0_0, "mov x0,#0 [_proc_security_policy]")
|
||||
self.emit(foff + 4, RET, "ret [_proc_security_policy]")
|
||||
return True
|
||||
self._log(
|
||||
f" [*] candidate 0x{foff:X}: {count} calls, "
|
||||
f"size 0x{func_size:X}"
|
||||
)
|
||||
|
||||
# Skip large functions (utilities like copyio are ~0x28C bytes)
|
||||
if func_size > 0x200:
|
||||
self._log(f" [-] skipped (too large, likely utility)")
|
||||
continue
|
||||
|
||||
# Skip tiny functions (< 0x40 bytes, likely trivial helpers)
|
||||
if func_size < 0x40:
|
||||
self._log(f" [-] skipped (too small)")
|
||||
continue
|
||||
|
||||
self._log(
|
||||
f" [+] identified _proc_security_policy at 0x{foff:X} "
|
||||
f"({count} calls, size 0x{func_size:X})"
|
||||
)
|
||||
self.emit(foff, MOV_X0_0, "mov x0,#0 [_proc_security_policy]")
|
||||
self.emit(foff + 4, RET, "ret [_proc_security_policy]")
|
||||
return True
|
||||
|
||||
self._log(" [-] _proc_security_policy not identified among BL targets")
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user