"""Mixin: KernelJBPatchKcall10Mixin.""" from .kernel_jb_base import _rd64, struct from .kernel import asm from .kernel_asm import _PACIBSP_U32, _RETAB_U32 # Max sysent entries in XNU (dispatch clamps at 0x22E = 558). _SYSENT_MAX_ENTRIES = 558 # Each sysent entry is 24 bytes. _SYSENT_ENTRY_SIZE = 24 # PAC discriminator used by the syscall dispatch (MOV X17, #0xBCAD; BLRAA X8, X17). _SYSENT_PAC_DIVERSITY = 0xBCAD # Rebuilt PCC 26.1 semantics: # uap[0] = target function pointer # uap[1] = arg0 # ... # uap[7] = arg6 # Return path: # store X0 as 64-bit into retval, expose through sy_return_type=UINT64 _KCALL10_NARG = 8 _KCALL10_ARG_BYTES_32 = _KCALL10_NARG * 4 _KCALL10_RETURN_TYPE = 7 _KCALL10_EINVAL = 22 class KernelJBPatchKcall10Mixin: def _find_sysent_table(self, nosys_off): """Find the real sysent table base. Strategy: 1. Find any DATA entry whose decoded pointer == _nosys. 2. Scan backward in 24-byte steps to find the true table start (entry 0 is the indirect syscall handler, NOT _nosys). 3. Validate each backward entry: sy_call decodes to a code range AND the metadata fields (narg, arg_bytes) look reasonable. Previous bug: the old code took the first _nosys match as entry 0, but _nosys first appears at entry ~428 (varies by XNU build). """ nosys_entry = -1 seg_start = -1 for seg_name, _, fileoff, filesize, _ in self.all_segments: if "DATA" not in seg_name: continue for off in range(fileoff, fileoff + filesize - _SYSENT_ENTRY_SIZE, 8): val = _rd64(self.raw, off) decoded = self._decode_chained_ptr(val) if decoded == nosys_off: val2 = _rd64(self.raw, off + _SYSENT_ENTRY_SIZE) decoded2 = self._decode_chained_ptr(val2) if decoded2 > 0 and any( s <= decoded2 < e for s, e in self.code_ranges ): nosys_entry = off seg_start = fileoff break if nosys_entry >= 0: break if nosys_entry < 0: return -1 self._log( f" [*] _nosys entry found at foff 0x{nosys_entry:X}, " f"scanning backward for table start" ) base = nosys_entry entries_back = 0 while base - _SYSENT_ENTRY_SIZE >= seg_start: if entries_back >= _SYSENT_MAX_ENTRIES: break prev = base - _SYSENT_ENTRY_SIZE val = _rd64(self.raw, prev) decoded = self._decode_chained_ptr(val) if decoded <= 0 or not any(s <= decoded < e for s, e in self.code_ranges): break narg = struct.unpack_from(" 12 or arg_bytes > 96: break base = prev entries_back += 1 self._log( f" [+] sysent table base at foff 0x{base:X} " f"({entries_back} entries before first _nosys)" ) return base def _encode_chained_auth_ptr(self, target_foff, next_val, diversity=0, key=0, addr_div=0): """Encode an arm64e kernel cache auth rebase chained fixup pointer.""" val = ( (target_foff & 0x3FFFFFFF) | ((diversity & 0xFFFF) << 32) | ((addr_div & 1) << 48) | ((key & 3) << 49) | ((next_val & 0xFFF) << 51) | (1 << 63) ) return struct.pack("> 51) & 0xFFF def _extract_chain_diversity(self, raw_val): return (raw_val >> 32) & 0xFFFF def _extract_chain_addr_div(self, raw_val): return (raw_val >> 48) & 1 def _extract_chain_key(self, raw_val): return (raw_val >> 49) & 3 def _find_munge32_for_narg(self, sysent_off, narg, arg_bytes): """Find a reusable 32-bit munger entry with matching metadata. Returns `(target_foff, exemplar_entry, match_count)` or `(-1, -1, 0)`. Requires a unique decoded helper target across all matching sysent rows. """ candidates = {} for idx in range(_SYSENT_MAX_ENTRIES): entry = sysent_off + idx * _SYSENT_ENTRY_SIZE cur_narg = struct.unpack_from("uu_arg[0] x2 = &uthread->uu_rval[0] uap layout (8 qwords): [0] target function pointer [1] arg0 [2] arg1 [3] arg2 [4] arg3 [5] arg4 [6] arg5 [7] arg6 Behavior: - validates uap / retval / target are non-null - invokes target(arg0..arg6, x7=0) - stores 64-bit X0 into retval for `_SYSCALL_RET_UINT64_T` - returns 0 on success or EINVAL on malformed request """ code = [] code.append(struct.pack(" uint64 x0)", ) self.emit( entry_439, self._encode_chained_auth_ptr( cave_off, next_val=call_next, diversity=_SYSENT_PAC_DIVERSITY, key=0, addr_div=0, ), f"sysent[439].sy_call = cave 0x{cave_off:X} (auth rebase, div=0xBCAD, next={call_next}) [kcall10]", ) self.emit( entry_439 + 8, self._encode_chained_auth_ptr( munger_target, next_val=munge_next, diversity=munge_div, key=munge_key, addr_div=munge_addr_div, ), f"sysent[439].sy_arg_munge32 = 8-arg helper 0x{munger_target:X} [kcall10]", ) self.emit( entry_439 + 16, struct.pack("