#!/usr/bin/env python3 """Audit original ordinary-ARM64 AES dispatch and saved live class metadata. The pinned ARM64e KDK file supplies symbol annotations only. It is never linked, executed, or used as a substitute target component. """ import argparse import hashlib import json from pathlib import Path import re import struct import subprocess import sys ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'kits/XnuPrelink')) import native_prelink as np parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--candidate', type=Path, default=ROOT / 'build/xnu-prelink/native-defaults-candidate.macho') parser.add_argument('--reference', type=Path, default=ROOT / '.cache/kdk-aes-reference/System/Library/Extensions/IOCryptoAcceleratorFamily.kext/Contents/MacOS/IOCryptoAcceleratorFamily') parser.add_argument('--snapshot', type=Path, default=ROOT / 'build/darwinboot-aes-native-metadata/gdb-stop.json') parser.add_argument('--output', type=Path, default=ROOT / 'build/image4-ignition/aes-interface-audit.json') args = parser.parse_args() def require(value, message): if not value: raise SystemExit(message) reference = args.reference.read_bytes() ref_sha = hashlib.sha256(reference).hexdigest() require(ref_sha == 'c487cb1e607a468e26fcc4da93a02a4d30a129360cd3ca9ca88ef3a51f8c8cd9', 'Unexpected KDK annotation input') require(struct.unpack_from('<3I', reference) == (0xfeedfacf, 0x100000c, 0xc0000002), 'Expected reference-only ARM64e Mach-O') segments, symbols, by_address = [], {}, {} at = 32 for _ in range(struct.unpack_from('> 9) imports.append(reference[offset:reference.index(b'\0', offset)].decode()) # All pointer-bearing reference segments must use the qualified kernel format. segment_count = struct.unpack_from(' vtable) offset = np.backed_offset(segments, vtable, end - vtable, len(reference)) reference_slots = [] for slot in range((end - vtable) // 8 - 2): pointer = struct.unpack_from('> 62 & 1) target = pointer & (0xffffffff if pointer >> 63 else (1 << 43) - 1) names = [imports[pointer & 0xffff]] if bind else by_address.get(target, []) reference_slots.append(dict(reference_slot=slot, names=names, bind=bind, target=target)) require(len(reference_slots) == 286 and not reference_slots[-1]['names'], 'Unexpected reference table boundary') require(not any(s['va'] <= reference_slots[-1]['target'] < s['va'] + s['filesize'] for s in segments if s['name'] == '__TEXT_EXEC'), 'Trailing reference word is executable') require(all(r['names'] for r in reference_slots[:-1]), 'Unannotated reference method') padding = [r for r in reference_slots[:-1] if any('_RESERVED' in n for n in r['names'])] require(len(padding) == 104, 'Unexpected macOS reserved-slot count') header = (ROOT / '.cache/xnu-work/libkern/libkern/c++/OSMetaClass.h').read_text() require(re.search(r'#if !XNU_TARGET_OS_OSX\s+#define APPLE_KEXT_VTABLE_PADDING\s+0', header), 'Source iOS padding policy changed') slots = [r for r in reference_slots[:-1] if r not in padding] require(len(slots) == 181, 'Unexpected iOS method count') candidate = args.candidate.read_bytes() plan = json.loads(args.candidate.with_suffix('.json').read_text()) candidate_sha = hashlib.sha256(candidate).hexdigest() require(candidate_sha == plan['candidate_sha256'], 'Candidate digest mismatch') require(plan['original_sha256'] == 'f18b4df865405f2a287b84977d7aca745e056d656d9fc9df1662970ce2969de7', 'Unexpected target kernel provenance') native = np.macho(candidate) # Deliberately retains the ordinary ARM64-only gate. exports = np.exports(candidate, native) module = [s for s in plan['extents'] if s['module'] == 'com.apple.iokit.IOCryptoAcceleratorFamily'] text = next(s for s in module if s['segment'] == '__TEXT_EXEC') const = next(s for s in module if s['segment'] == '__DATA_CONST') def read(va, size): at = np.backed_offset(native['segments'], va, size, len(candidate)) return candidate[at:at + size] def returned_address(va): a, add, ret = struct.unpack('<3I', read(va, 12)) if a & 0x9f00001f != 0x90000000 or add & 0xffc003ff != 0x91000000 or ret != 0xd65f03c0: return None immediate = ((a >> 5 & 0x7ffff) << 2) | (a >> 29 & 3) if immediate & 0x100000: immediate -= 0x200000 return (va & ~4095) + immediate * 4096 + (add >> 10 & 4095) algorithm_slot = next(i for i, r in enumerate(slots) if '__ZN16IOAESAccelerator16getAlgorithmNameEv' in r['names']) data = read(const['va'], const['size']) matches = [] for offset in range(0, len(data) - (len(slots) + 2) * 8 + 1, 8): if data[offset:offset + 16] != bytes(16): continue pointer = struct.unpack_from('= 40: return bytes.fromhex(value) for value in snapshot['indirect_memory'].values(): if int(value['address'], 16) == address: return bytes.fromhex(value['bytes']) raise SystemExit(f'Missing actual guest metadata at {address:#x}') aes_data, service_data = observed(meta), observed(service_meta) crypto_meta = struct.unpack_from('> 14 & 3, tcr >> 30 & 3) == (0, 2), 'Live translation is not native 4K on both halves') require(int(registers['SCTLR_EL1'], 16) & 1 and all(int(registers[r], 16) for r in ('TTBR0_EL1', 'TTBR1_EL1')), 'Inactive translation roots') result = dict(reference_annotation_sha256=ref_sha, reference_executed_or_linked=False, candidate_sha256=candidate_sha, original_sha256=plan['original_sha256'], macos_padding_slots=104, ordinary_ios_method_slots=181, inherited_kernel_matches=inherited, vtable=hex(table), algorithm='AES-CBC', algorithm_slot=algorithm_slot, methods=rows, compiled_metaclass_offsets=layout, live_metaclasses=dict(IOAESAccelerator=hex(meta), IOCryptoAccelerator=hex(crypto_meta), IOService=hex(service_meta)), live_instance_sizes=dict(zip(('IOAESAccelerator', 'IOCryptoAccelerator', 'IOService'), sizes)), live_native_4k=True, live_registers=registers, request_structure_qualified=False, aes_operations_tested=False, service_registered=False, hardware_observation=False) args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(result, indent=2) + '\n') print('PASS: 181 ordinary iOS slots, 162 inherited matches; live AES/parent/service sizes 0x120/0x88/0x88') print('Reference-only ARM64e annotations and native 4K metadata; this class audit does not qualify private requests or provider operations.')