mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
Fix C24: patch_kcall10 sysent table base, chained fixup encoding, PAC signing
Three bugs caused NOT_BOOT (timeout):
1. Wrong sysent table base: first _nosys match is entry 428, not entry 0.
Entry 0 is the indirect syscall handler. Fixed with backward scan.
2. Raw VA written to chained fixup pointer slot: struct.pack("<Q", cave_va)
corrupts the fixup chain from sysent[439] onward. Fixed with proper
auth rebase encoding (_encode_chained_auth_ptr).
3. Missing PAC parameters: dispatch uses BLRAA X8, X17 with X17=0xBCAD.
Chained fixup must encode diversity=0xBCAD, key=IA, addrDiv=0.
Chain 'next' field preserved from original entry.
Boot-tested OK via testing ramdisk.
This commit is contained in:
@@ -1,97 +1,162 @@
|
||||
# C24 `patch_kcall10`
|
||||
|
||||
## Status: NOT_BOOT (timeout, no panic)
|
||||
## Status: BOOT OK
|
||||
|
||||
Previous status: NOT_BOOT (timeout, no panic).
|
||||
|
||||
## How the patch works
|
||||
- Source: `scripts/patchers/kernel_jb_patch_kcall10.py`.
|
||||
- Locator strategy:
|
||||
1. Resolve `_nosys` (symbol or `mov w0,#0x4e; ret` pattern).
|
||||
2. Scan DATA segments for `sysent` table signature (entry decoding points to `_nosys`).
|
||||
3. Compute `sysent[439]` (`SYS_kas_info`) entry offset.
|
||||
2. Scan DATA segments for first entry whose decoded pointer == `_nosys`.
|
||||
3. **Scan backward** in 24-byte steps from the match to find the real table start
|
||||
(entry 0 is the indirect syscall handler, NOT `_nosys`).
|
||||
4. Compute `sysent[439]` (`SYS_kas_info`) entry offset from the real base.
|
||||
- Patch action:
|
||||
- Inject `kcall10` shellcode in code cave (argument marshalling + `blr x16` + result write-back).
|
||||
- Rewrite `sysent[439]` fields:
|
||||
- `sy_call` -> cave VA
|
||||
- `sy_munge32` -> `_munge_wwwwwwww` (if resolved)
|
||||
- return type + arg metadata.
|
||||
- Inject `kcall10` shellcode in code cave (argument marshalling + `BLR X16` + result write-back).
|
||||
- Rewrite `sysent[439]` fields using **proper chained fixup encoding**:
|
||||
- `sy_call` → auth rebase pointer to cave (diversity=0xBCAD, key=IA, addrDiv=0)
|
||||
- `sy_munge32` → `_munge_wwwwwwww` (if resolved, same encoding)
|
||||
- return type + arg metadata (non-pointer fields, written directly).
|
||||
|
||||
## Root cause analysis (completed)
|
||||
|
||||
Three bugs were identified, all contributing to the NOT_BOOT failure:
|
||||
|
||||
### Bug 1: Wrong sysent table base (CRITICAL)
|
||||
|
||||
The old code searched DATA segments for the first entry whose decoded pointer matched
|
||||
`_nosys` and treated that as `sysent[0]`. But in XNU, entry 0 is the **indirect syscall
|
||||
handler** (`sub_FFFFFE00080073B0`, calls audit then returns ENOSYS) — NOT the simple
|
||||
`_nosys` function (`sub_FFFFFE0007F6901C`, just returns 78).
|
||||
|
||||
The first `_nosys` match appeared **428 entries** into the table:
|
||||
- Old (wrong) sysent base: file 0x73E078, VA 0xFFFFFE0007742078
|
||||
- Real sysent base: file 0x73B858, VA 0xFFFFFE000773F858
|
||||
|
||||
This meant the patcher was writing to `sysent[439+428] = sysent[867]`, which is way
|
||||
past the end of the 558-entry table. The patcher was corrupting unrelated DATA.
|
||||
|
||||
**Verification via IDA:**
|
||||
- Syscall dispatch function `sub_FFFFFE00081279E4` uses `off_FFFFFE000773F858` as
|
||||
the sysent base: `v26 = &off_FFFFFE000773F858[3 * v25]` (3 qwords = 24 bytes/entry).
|
||||
- Dispatch caps syscall number at 0x22E (558 entries max).
|
||||
- Real `sysent[439]` at VA 0xFFFFFE0007742180 has `sy_call` = `sub_FFFFFE0008077978`
|
||||
(returns 45 / ENOTSUP = `kas_info` stub).
|
||||
|
||||
**Fix:** After finding any `_nosys` match, scan backward in 24-byte steps. Each step
|
||||
validates: (a) `sy_call` decodes to a code range, (b) metadata fields are reasonable
|
||||
(`narg ≤ 12`, `arg_bytes ≤ 96`). Stop when validation fails or segment boundary reached.
|
||||
Limited to 558 entries max to prevent runaway scanning.
|
||||
|
||||
### Bug 2: Raw VA written to chained fixup pointer (CRITICAL)
|
||||
|
||||
The old code wrote `struct.pack("<Q", cave_va)` — a raw 8-byte virtual address — to
|
||||
`sysent[439].sy_call`. On arm64e kernelcaches, DATA segment pointers use **chained fixup
|
||||
encoding**, not raw VAs:
|
||||
|
||||
## Patcher output
|
||||
```
|
||||
_nosys found
|
||||
sysent table at file offset 0x73E078
|
||||
Shellcode at file offset 0x00AB1720 (VA 0xFFFFFE0007AB5720)
|
||||
sysent[439] at file offset 0x007409A0 (VA 0xFFFFFE00077449A0)
|
||||
35 patches emitted (32 shellcode + 3 sysent fields)
|
||||
Note: _munge_wwwwwwww NOT found — sy_munge32 field not patched
|
||||
DYLD_CHAINED_PTR_64_KERNEL_CACHE auth rebase:
|
||||
bit[63]: isAuth = 1
|
||||
bits[62:51]: next (12 bits, 4-byte stride delta to next fixup)
|
||||
bits[50:49]: key (0=IA, 1=IB, 2=DA, 3=DB)
|
||||
bit[48]: addrDiv (1 = address-diversified)
|
||||
bits[47:32]: diversity (16-bit PAC discriminator)
|
||||
bits[31:30]: cacheLevel (0 for single-level)
|
||||
bits[29:0]: target (file offset)
|
||||
```
|
||||
|
||||
## Root cause analysis (in progress)
|
||||
Writing a raw VA (e.g., `0xFFFFFE0007AB5720`) produces:
|
||||
- `isAuth=1` (bit63 of kernel VA is 1)
|
||||
- `next`, `key`, `addrDiv`, `diversity` = **garbage** from VA bits
|
||||
- `target` = bits[31:0] of VA = wrong file offset
|
||||
|
||||
### Primary suspect: chained fixup pointer format mismatch
|
||||
This corrupts the chained fixup chain from `sysent[439]` onward, silently breaking
|
||||
all subsequent syscall entries. This explains the NOT_BOOT timeout: no panic because
|
||||
the corruption doesn't hit early boot syscalls, but init and daemons use corrupted
|
||||
handlers.
|
||||
|
||||
The sysent table lives in a DATA segment. On arm64e kernelcaches, DATA segment
|
||||
pointers use **chained fixup encoding**, not raw virtual addresses:
|
||||
**Fix:** Implemented `_encode_chained_auth_ptr()` that properly encodes:
|
||||
- `target` = cave file offset (bits[29:0])
|
||||
- `diversity` = 0xBCAD (bits[47:32])
|
||||
- `key` = 0/IA (bits[50:49])
|
||||
- `addrDiv` = 0 (bit[48])
|
||||
- `next` = preserved from original entry (bits[62:51])
|
||||
- `isAuth` = 1 (bit[63])
|
||||
|
||||
- **Auth rebase** (bit63=1): `file_offset = bits[31:0]`, plus diversity/key metadata
|
||||
- **Non-auth rebase** (bit63=0): `VA = (bits[50:43] << 56) | bits[42:0]`
|
||||
### Bug 3: Missing PAC signing parameters
|
||||
|
||||
The patcher writes `struct.pack("<Q", cave_va)` — a raw 8-byte VA — to `sysent[439].sy_call`.
|
||||
This is **not valid chained fixup format**. The kernel's pointer fixup chain will either:
|
||||
The syscall dispatch at `0xFFFFFE0008127CC8`:
|
||||
```asm
|
||||
MOV X17, #0xBCAD
|
||||
BLRAA X8, X17 ; PAC-authenticated indirect call
|
||||
```
|
||||
|
||||
1. Misinterpret the raw VA as a chained pointer and decode it to a wrong address
|
||||
2. Break the fixup chain, corrupting subsequent sysent entries
|
||||
3. The pointer simply won't be resolved, leaving a garbage function pointer
|
||||
ALL syscall `sy_call` pointers are called via `BLRAA X8, X17` with fixed discriminator
|
||||
`X17 = 0xBCAD`. The chained fixup resolver PAC-signs each pointer during boot according
|
||||
to its metadata (diversity, key, addrDiv). For the dispatch to authenticate correctly:
|
||||
- `diversity` must be `0xBCAD`
|
||||
- `key` must be `0` (IA, matching BLRAA = key A)
|
||||
- `addrDiv` must be `0` (fixed discriminator, not address-blended)
|
||||
|
||||
This explains the NOT_BOOT (timeout) behavior — no panic because the corrupted
|
||||
pointer is never dereferenced during early boot (syscall 439 is not called during
|
||||
init), but the fixup chain corruption may silently break other syscall entries,
|
||||
preventing the system from booting properly.
|
||||
The old code didn't set any of these — the raw VA had garbage metadata, so the
|
||||
fixup resolver would PAC-sign with wrong parameters, causing BLRAA to fail at runtime.
|
||||
|
||||
### Fix approach (TODO)
|
||||
**Fix:** `_encode_chained_auth_ptr()` sets all three fields correctly.
|
||||
|
||||
1. **Read raw bytes at sysent[0] and sysent[439]** via IDA MCP to confirm the
|
||||
chained fixup pointer format (auth vs non-auth rebase)
|
||||
2. **Implement `_encode_chained_ptr()`** that produces the correct encoding:
|
||||
- For auth rebase: set bit63=1, encode file offset in bits[31:0], set
|
||||
appropriate key/diversity fields
|
||||
- For non-auth rebase: encode VA with high8 bits in [50:43] and low43 in [42:0]
|
||||
3. **Use encoded pointer** when writing `sy_call` and `sy_munge32`
|
||||
4. **Verify the fixup chain** — sysent entries may be part of a linked chain
|
||||
where each entry's `next` field points to the next pointer to fix up.
|
||||
Breaking this chain corrupts all subsequent entries.
|
||||
### Non-issue: BLR X16 in shellcode
|
||||
|
||||
### Secondary concerns
|
||||
The shellcode uses `BLR X16` (raw indirect branch without PAC authentication) to call
|
||||
the user-provided kernel function pointer. This is correct:
|
||||
- `BLR Xn` strips PAC bits and branches to the resulting address
|
||||
- It does NOT authenticate — so it works regardless of whether the pointer is PAC-signed
|
||||
- The kernel function pointer is provided from userspace (raw VA), so no PAC involved
|
||||
|
||||
- **Missing `_munge_wwwwwwww`**: The symbol wasn't found. Without the correct
|
||||
munge function, the kernel may not properly marshal syscall arguments from
|
||||
userspace. This may cause a panic when the syscall is actually invoked.
|
||||
- **Code cave in __TEXT_EXEC**: The shellcode is placed at 0xAB1720 in __TEXT_EXEC.
|
||||
Need to verify this region is executable at runtime (KTRR/CTRR may lock it).
|
||||
- **BLR x16 in shellcode**: The shellcode uses `BLR X16` (raw encoding
|
||||
`0xD63F0200`). On PAC-enabled kernels, this may need to be `BLRAAZ X16` or
|
||||
similar authenticated branch to avoid PAC traps.
|
||||
### Note: Missing `_munge_wwwwwwww`
|
||||
|
||||
### Sysent table structure
|
||||
The symbol `_munge_wwwwwwww` was not found in this kernelcache. Without the munge
|
||||
function, the kernel won't marshal 32-bit userspace arguments for this syscall.
|
||||
This is only relevant for 32-bit callers; 64-bit callers pass arguments directly
|
||||
and should work fine. The `sy_munge32` field is left unpatched (original value).
|
||||
|
||||
## Sysent table structure
|
||||
```
|
||||
struct sysent {
|
||||
sy_call_t *sy_call; // +0: function pointer (8 bytes)
|
||||
munge_t *sy_arg_munge32; // +8: argument munge function (8 bytes)
|
||||
int32_t sy_return_type; // +16: return type (4 bytes)
|
||||
int16_t sy_narg; // +20: number of arguments (2 bytes)
|
||||
uint16_t sy_arg_bytes; // +22: argument byte count (2 bytes)
|
||||
}; // total: 24 bytes per entry
|
||||
sy_call_t *sy_call; // +0: function pointer (8 bytes, chained fixup)
|
||||
munge_t *sy_arg_munge32; // +8: argument munge function (8 bytes, chained fixup)
|
||||
int32_t sy_return_type; // +16: return type (4 bytes, plain int)
|
||||
int16_t sy_narg; // +20: number of arguments (2 bytes, plain int)
|
||||
uint16_t sy_arg_bytes; // +22: argument byte count (2 bytes, plain int)
|
||||
}; // total: 24 bytes per entry, max 558 entries (0x22E)
|
||||
```
|
||||
|
||||
### Key addresses
|
||||
- sysent table: file 0x73E078, VA 0xFFFFFE0007742078
|
||||
- sysent[439]: file 0x7409A0, VA 0xFFFFFE00077449A0
|
||||
- Code cave: file 0xAB1720, VA 0xFFFFFE0007AB5720
|
||||
- _nosys: found by pattern match
|
||||
## Key addresses (corrected)
|
||||
- Dispatch function: VA 0xFFFFFE00081279E4 (`sub_FFFFFE00081279E4`)
|
||||
- Real sysent base: file 0x73B858, VA 0xFFFFFE000773F858 (`off_FFFFFE000773F858`)
|
||||
- Old (wrong) sysent base: file 0x73E078, VA 0xFFFFFE0007742078 (428 entries in)
|
||||
- Real sysent[439]: file 0x73E180, VA 0xFFFFFE0007742180
|
||||
- Original `sy_call` = `sub_FFFFFE0008077978` (returns 45/ENOTSUP = kas_info stub)
|
||||
- Old (wrong) sysent[439]: file 0x7409A0, VA 0xFFFFFE00077449A0 (actually entry 867)
|
||||
- Code cave: file 0xAB1720, VA 0xFFFFFE0007AB5720 (in __TEXT_EXEC)
|
||||
- `_nosys`: `sub_FFFFFE0007F6901C` (file offset 0xF6501C), returns 78/ENOSYS
|
||||
|
||||
## Chained fixup data (from IDA analysis)
|
||||
```
|
||||
Dispatch sysent[0]: sy_call = sub_FFFFFE00080073B0 (indirect syscall, audit+ENOSYS)
|
||||
sy_munge32 = NULL, ret=1, narg=0, bytes=0
|
||||
Dispatch sysent[1]: sy_call = sub_FFFFFE0007FB0B6C (exit)
|
||||
sy_munge32 = sub_FFFFFE0007C6AC2C, ret=0, narg=1, bytes=4
|
||||
Dispatch sysent[439]: sy_call = sub_FFFFFE0008077978 (kas_info, returns ENOTSUP)
|
||||
sy_munge32 = sub_FFFFFE0007C6AC4C, ret=1, narg=3, bytes=12
|
||||
```
|
||||
|
||||
## Expected outcome
|
||||
- Replace syscall 439 handler with arbitrary 10-arg kernel call trampoline behavior.
|
||||
- Replace syscall 439 handler with arbitrary 10-arg kernel call trampoline.
|
||||
- Proper chained fixup encoding preserves the fixup chain for all subsequent entries.
|
||||
- PAC signing with diversity=0xBCAD matches the dispatch's BLRAA authentication.
|
||||
|
||||
## Risk
|
||||
- Syscall table rewrite is extremely invasive; wrong pointer encoding breaks
|
||||
the fixup chain and can silently corrupt many syscall handlers.
|
||||
- BLR without PAC authentication may cause kernel traps.
|
||||
- Syscall table rewrite is invasive, but proper chained fixup encoding and chain
|
||||
preservation should make it safe.
|
||||
- Code cave in __TEXT_EXEC is within the KTRR-protected region — already validated
|
||||
as executable in C23 testing.
|
||||
|
||||
@@ -277,6 +277,10 @@ with capstone semantic matching and keystone-generated patch bytes only:
|
||||
- Inline trampoline (B cave at function entry) replaces ops table pointer rewrite
|
||||
- Ops table pointer modification breaks chained fixup integrity → PAC failures
|
||||
24. `kcall10` syscall 439 replacement shellcode
|
||||
- Sysent table base found via backward scan from first `_nosys` match (entry 0 is indirect syscall, not `_nosys`)
|
||||
- `sy_call` encoded as auth rebase chained fixup pointer (diversity=0xBCAD, key=IA, addrDiv=0)
|
||||
- Matches dispatch's `BLRAA X8, X17` with `X17=0xBCAD` PAC authentication
|
||||
- Chain `next` field preserved from original entry to maintain fixup chain integrity
|
||||
|
||||
## Cross-Version Dynamic Snapshot
|
||||
|
||||
|
||||
@@ -2,13 +2,123 @@
|
||||
|
||||
from .kernel_jb_base import asm, _rd32, _rd64, RET, NOP, struct
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
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).
|
||||
"""
|
||||
# Step 1: find any _nosys-matching entry
|
||||
nosys_entry = -1
|
||||
seg_start = -1
|
||||
for seg_name, vmaddr, 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:
|
||||
# Verify: next entry should also have valid sy_call
|
||||
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"
|
||||
)
|
||||
|
||||
# Step 2: scan backward to find entry 0
|
||||
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
|
||||
# Check sy_call decodes to valid code
|
||||
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
|
||||
# Check metadata looks like a sysent entry
|
||||
narg = struct.unpack_from("<H", self.raw, prev + 20)[0]
|
||||
arg_bytes = struct.unpack_from("<H", self.raw, prev + 22)[0]
|
||||
if narg > 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.
|
||||
|
||||
Layout (DYLD_CHAINED_PTR_64_KERNEL_CACHE):
|
||||
bits[29:0]: target (file offset)
|
||||
bits[31:30]: cacheLevel (0)
|
||||
bits[47:32]: diversity (16 bits)
|
||||
bit[48]: addrDiv
|
||||
bits[50:49]: key (0=IA, 1=IB, 2=DA, 3=DB)
|
||||
bits[62:51]: next (12 bits, 4-byte stride delta to next fixup)
|
||||
bit[63]: isAuth (1)
|
||||
"""
|
||||
val = (
|
||||
(target_foff & 0x3FFFFFFF)
|
||||
| ((diversity & 0xFFFF) << 32)
|
||||
| ((addr_div & 1) << 48)
|
||||
| ((key & 3) << 49)
|
||||
| ((next_val & 0xFFF) << 51)
|
||||
| (1 << 63)
|
||||
)
|
||||
return struct.pack("<Q", val)
|
||||
|
||||
def _extract_chain_next(self, raw_val):
|
||||
"""Extract the 'next' chain field from a raw chained fixup pointer."""
|
||||
return (raw_val >> 51) & 0xFFF
|
||||
|
||||
def patch_kcall10(self):
|
||||
"""Replace SYS_kas_info (syscall 439) with kcall10 shellcode.
|
||||
|
||||
Anchor: find _nosys function by pattern, then search DATA segments
|
||||
for the sysent table (first entry points to _nosys).
|
||||
for the sysent table base (backward scan from first _nosys entry).
|
||||
|
||||
The sysent dispatch uses BLRAA X8, X17 with X17=0xBCAD, so all
|
||||
sy_call pointers must be PAC-signed with key=IA, diversity=0xBCAD,
|
||||
addrDiv=0. We encode the cave pointer as a proper auth rebase
|
||||
chained fixup entry to match.
|
||||
"""
|
||||
self._log("\n[JB] kcall10: syscall 439 replacement")
|
||||
|
||||
@@ -30,26 +140,8 @@ class KernelJBPatchKcall10Mixin:
|
||||
munge_off = off
|
||||
break
|
||||
|
||||
# Search for sysent table in DATA segments
|
||||
sysent_off = -1
|
||||
for seg_name, vmaddr, fileoff, filesize, _ in self.all_segments:
|
||||
if "DATA" not in seg_name:
|
||||
continue
|
||||
for off in range(fileoff, fileoff + filesize - 24, 8):
|
||||
val = _rd64(self.raw, off)
|
||||
decoded = self._decode_chained_ptr(val)
|
||||
if decoded == nosys_off:
|
||||
# Verify: sysent[1] should also point to valid code
|
||||
val2 = _rd64(self.raw, off + 24)
|
||||
decoded2 = self._decode_chained_ptr(val2)
|
||||
if decoded2 > 0 and any(
|
||||
s <= decoded2 < e for s, e in self.code_ranges
|
||||
):
|
||||
sysent_off = off
|
||||
break
|
||||
if sysent_off >= 0:
|
||||
break
|
||||
|
||||
# Find sysent table (real base via backward scan)
|
||||
sysent_off = self._find_sysent_table(nosys_off)
|
||||
if sysent_off < 0:
|
||||
self._log(" [-] sysent table not found")
|
||||
return False
|
||||
@@ -57,7 +149,7 @@ class KernelJBPatchKcall10Mixin:
|
||||
self._log(f" [+] sysent table at file offset 0x{sysent_off:X}")
|
||||
|
||||
# Entry 439 (SYS_kas_info)
|
||||
entry_439 = sysent_off + 439 * 24
|
||||
entry_439 = sysent_off + 439 * _SYSENT_ENTRY_SIZE
|
||||
|
||||
# Find code cave for kcall10 shellcode (~128 bytes = 32 instructions)
|
||||
cave = self._find_code_cave(128)
|
||||
@@ -66,58 +158,85 @@ class KernelJBPatchKcall10Mixin:
|
||||
return False
|
||||
|
||||
# Build kcall10 shellcode
|
||||
# Syscall args arrive via the saved state on the stack.
|
||||
# arg[0] = pointer to a userspace buffer with {func_ptr, arg0..arg9}.
|
||||
# We unpack, call func_ptr(arg0..arg9), write results back.
|
||||
parts = [
|
||||
asm("ldr x10, [sp, #0x40]"), # 0
|
||||
asm("ldp x0, x1, [x10, #0]"), # 1
|
||||
asm("ldp x2, x3, [x10, #0x10]"), # 2
|
||||
asm("ldp x4, x5, [x10, #0x20]"), # 3
|
||||
asm("ldp x6, x7, [x10, #0x30]"), # 4
|
||||
asm("ldp x8, x9, [x10, #0x40]"), # 5
|
||||
asm("ldr x10, [x10, #0x50]"), # 6
|
||||
asm("mov x16, x0"), # 7
|
||||
asm("mov x0, x1"), # 8
|
||||
asm("mov x1, x2"), # 9
|
||||
asm("mov x2, x3"), # 10
|
||||
asm("mov x3, x4"), # 11
|
||||
asm("mov x4, x5"), # 12
|
||||
asm("mov x5, x6"), # 13
|
||||
asm("mov x6, x7"), # 14
|
||||
asm("mov x7, x8"), # 15
|
||||
asm("mov x8, x9"), # 16
|
||||
asm("mov x9, x10"), # 17
|
||||
asm("ldr x10, [sp, #0x40]"), # 0
|
||||
asm("ldp x0, x1, [x10, #0]"), # 1
|
||||
asm("ldp x2, x3, [x10, #0x10]"), # 2
|
||||
asm("ldp x4, x5, [x10, #0x20]"), # 3
|
||||
asm("ldp x6, x7, [x10, #0x30]"), # 4
|
||||
asm("ldp x8, x9, [x10, #0x40]"), # 5
|
||||
asm("ldr x10, [x10, #0x50]"), # 6
|
||||
asm("mov x16, x0"), # 7
|
||||
asm("mov x0, x1"), # 8
|
||||
asm("mov x1, x2"), # 9
|
||||
asm("mov x2, x3"), # 10
|
||||
asm("mov x3, x4"), # 11
|
||||
asm("mov x4, x5"), # 12
|
||||
asm("mov x5, x6"), # 13
|
||||
asm("mov x6, x7"), # 14
|
||||
asm("mov x7, x8"), # 15
|
||||
asm("mov x8, x9"), # 16
|
||||
asm("mov x9, x10"), # 17
|
||||
asm("stp x29, x30, [sp, #-0x10]!"), # 18
|
||||
bytes([0x00, 0x02, 0x3F, 0xD6]), # 19: BLR x16
|
||||
asm("ldp x29, x30, [sp], #0x10"), # 20
|
||||
asm("ldr x11, [sp, #0x40]"), # 21
|
||||
NOP, # 22
|
||||
asm("stp x0, x1, [x11, #0]"), # 23
|
||||
asm("stp x2, x3, [x11, #0x10]"), # 24
|
||||
asm("stp x4, x5, [x11, #0x20]"), # 25
|
||||
asm("stp x6, x7, [x11, #0x30]"), # 26
|
||||
asm("stp x8, x9, [x11, #0x40]"), # 27
|
||||
asm("str x10, [x11, #0x50]"), # 28
|
||||
asm("mov x0, #0"), # 29
|
||||
asm("ret"), # 30
|
||||
NOP, # 31
|
||||
bytes([0x00, 0x02, 0x3F, 0xD6]), # 19: BLR x16
|
||||
asm("ldp x29, x30, [sp], #0x10"), # 20
|
||||
asm("ldr x11, [sp, #0x40]"), # 21
|
||||
NOP, # 22
|
||||
asm("stp x0, x1, [x11, #0]"), # 23
|
||||
asm("stp x2, x3, [x11, #0x10]"), # 24
|
||||
asm("stp x4, x5, [x11, #0x20]"), # 25
|
||||
asm("stp x6, x7, [x11, #0x30]"), # 26
|
||||
asm("stp x8, x9, [x11, #0x40]"), # 27
|
||||
asm("str x10, [x11, #0x50]"), # 28
|
||||
asm("mov x0, #0"), # 29
|
||||
asm("ret"), # 30
|
||||
NOP, # 31
|
||||
]
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
self.emit(cave + i * 4, part, f"shellcode+{i * 4} [kcall10]")
|
||||
|
||||
# Patch sysent[439]
|
||||
cave_va = self.base_va + cave
|
||||
# ── Patch sysent[439] with proper chained fixup encoding ────────
|
||||
#
|
||||
# The old code wrote raw VAs (struct.pack("<Q", cave_va)) which
|
||||
# breaks the chained fixup chain and produces invalid PAC-signed
|
||||
# pointers. We now encode as auth rebase pointers matching the
|
||||
# dispatch's BLRAA X8, X17 (X17=0xBCAD).
|
||||
|
||||
# Read original raw value to preserve the chain 'next' field
|
||||
old_sy_call_raw = _rd64(self.raw, entry_439)
|
||||
call_next = self._extract_chain_next(old_sy_call_raw)
|
||||
|
||||
self.emit(
|
||||
entry_439,
|
||||
struct.pack("<Q", cave_va),
|
||||
f"sysent[439].sy_call = 0x{cave_va:X} [kcall10]",
|
||||
self._encode_chained_auth_ptr(
|
||||
cave,
|
||||
next_val=call_next,
|
||||
diversity=_SYSENT_PAC_DIVERSITY,
|
||||
key=0, # IA
|
||||
addr_div=0, # fixed discriminator (not address-blended)
|
||||
),
|
||||
f"sysent[439].sy_call = cave 0x{cave:X} "
|
||||
f"(auth rebase, div=0xBCAD, next={call_next}) [kcall10]",
|
||||
)
|
||||
|
||||
if munge_off >= 0:
|
||||
munge_va = self.base_va + munge_off
|
||||
old_sy_munge_raw = _rd64(self.raw, entry_439 + 8)
|
||||
munge_next = self._extract_chain_next(old_sy_munge_raw)
|
||||
self.emit(
|
||||
entry_439 + 8,
|
||||
struct.pack("<Q", munge_va),
|
||||
f"sysent[439].sy_munge32 = 0x{munge_va:X} [kcall10]",
|
||||
self._encode_chained_auth_ptr(
|
||||
munge_off,
|
||||
next_val=munge_next,
|
||||
diversity=_SYSENT_PAC_DIVERSITY,
|
||||
key=0,
|
||||
addr_div=0,
|
||||
),
|
||||
f"sysent[439].sy_munge32 = 0x{munge_off:X} "
|
||||
f"(auth rebase, next={munge_next}) [kcall10]",
|
||||
)
|
||||
|
||||
# sy_return_type = SYSCALL_RET_UINT64_T (7)
|
||||
|
||||
Reference in New Issue
Block a user