#!/usr/bin/env python3 """Qualify the unchanged target's mobile framebuffer dispatch contract on host.""" 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')) from native_prelink import backed_offset, exports, macho def require(value, message): if not value: raise SystemExit(message) candidate = ROOT / 'build/xnu-prelink/native-owner-mobile-graphics-candidate.macho' image = candidate.read_bytes() plan = json.loads(candidate.with_suffix('.json').read_text()) original = (ROOT / '.cache/firmware/kernelcache.macho/kernelcache.release.ipad7.decompressed').read_bytes() require(hashlib.sha256(image).hexdigest() == plan['candidate_sha256'] == 'aded28d3c61dafaf7b8a7c96b14427a798e9e95470ae6f287bcd038c07626798' and hashlib.sha256(original).hexdigest() == plan['original_sha256'] == 'f18b4df865405f2a287b84977d7aca745e056d656d9fc9df1662970ce2969de7', 'Require exact original and qualified candidate') segments, original_segments = macho(image)['segments'], macho(original)['segments'] symbols = exports(image, macho(image)) by_name = {name: va for va, names in symbols.items() for name in names} extents = [e for e in plan['extents'] if e['module'] == 'com.apple.iokit.IOMobileGraphicsFamily'] text = next(e for e in extents if e['segment'] == '__TEXT_EXEC') def read(va, size): at = backed_offset(segments, va, size, len(image)) return image[at:at + size] def word(va): return struct.unpack('> 5) & 0x7ffff) << 2 | ((first >> 29) & 3) if imm & (1 << 20): imm -= 1 << 21 return (pc & ~4095) + (imm << 12) + ((second >> 10) & 4095) require(hashlib.sha256(read(text['va'], text['size'])).hexdigest() == plan['original_executable_sha256']['com.apple.iokit.IOMobileGraphicsFamily'], 'Original executable bytes changed') obj = ROOT / 'build/xnu/ios-obj/DEVELOPMENT_ARM64_BCM2837/iokit/DEVELOPMENT/IOUserClient.cpo' layouts = {} for name, size, members in ( ('IOExternalMethodDispatch', 24, dict(function=0, checkScalarInputCount=8, checkStructureInputSize=12, checkScalarOutputCount=16, checkStructureOutputSize=20)), ('IOExternalMethodArguments', 248, dict(scalarInput=32, scalarInputCount=40, structureInput=48, structureInputSize=56, scalarOutput=72, scalarOutputCount=80, structureOutput=88, structureOutputSize=96))): dwarf = subprocess.check_output(['xcrun', 'dwarfdump', '--name=' + name, '--show-children', obj], text=True) found = re.search(r'DW_AT_name\s+\("' + name + r'"\)[\s\S]{0,150}?DW_AT_byte_size\s+\((0x[0-9a-f]+)\)', dwarf) require(found and int(found[1], 16) == size, 'Actual compiler structure size differs') for member, offset in members.items(): found = re.search(r'DW_AT_name\s+\("' + member + r'"\)[\s\S]*?DW_AT_data_member_location\s+\((0x[0-9a-f]+)\)', dwarf) require(found and int(found[1], 16) == offset, 'Actual compiler field differs: ' + member) layouts[name] = dict(bytes=size, fields=members) # The concrete factory's original constructor supplies this object's vptr. vptr = address_pair(0xfffffff005776358, 8) + 16 require(word(0xfffffff005776360) == 0x91004108 and vptr == 0xfffffff005f34dd0, 'Original userclient constructor vptr differs') compiled = obj.read_bytes() compiled_image = macho(compiled) sym_at = next(at for cmd, at, _ in compiled_image['commands'] if cmd == 2) symoff, count, stroff, _ = struct.unpack_from('<4I', compiled, sym_at + 8) compiled_symbols = [] for i in range(count): name, _, _, _, value = struct.unpack_from('> 27 & 1 and bits >> 28 == 0 and bits >> 25 & 3 == 3: relocations[section['va'] + offset] = compiled_symbols[bits & 0xffffff][0] slots = 0 while compiled_base + 16 + slots * 8 in relocations: slots += 1 base = by_name['__ZTV12IOUserClient'] require(slots == 186 and read(vptr - 16, 16) == bytes(16), 'Compiler-qualified native iOS IOUserClient table shape differs') overrides = [] for slot in range(slots): parent, = struct.unpack('