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:
Lakr
2026-03-04 21:41:44 +08:00
parent 894c2d1551
commit c9fd521659
3 changed files with 314 additions and 126 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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)