Files
UniXNU/scripts/audit-mobile-graphics-legacy.py

88 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Identify the original generic framebuffer implementation and its abstract leaves."""
import hashlib
import json
from pathlib import Path
import struct
import sys
ROOT=Path(__file__).resolve().parents[1]
sys.path.insert(0,str(ROOT/'kits/XnuPrelink'))
from native_prelink import macho,backed_offset,exports
def require(v,why):
if not v:raise SystemExit(why)
path=ROOT/'build/xnu-prelink/native-owner-mobile-graphics-candidate.macho'
FAMILY_TEXT_SHA='2e044ee68c1650397e1435234ca6459206262e4c7ed709cb7131c6f664f9bd9b'
if not path.exists():
# Build products are pruned: any later candidate built from the same original kernelcache carries the identical original
# IOMobileGraphicsFamily executable (checked by hash below), and every layout check in this audit runs against it.
later=sorted((ROOT/'build/xnu-prelink').glob('native-owner-ufs-cpu-desktop-*-candidate.macho'),key=lambda f:f.stat().st_mtime)
require(later,'No reference candidate available')
path=later[-1]
blob=path.read_bytes();plan=json.loads(path.with_suffix('.json').read_text());image=macho(blob)
require(hashlib.sha256(blob).hexdigest()==plan['candidate_sha256'] and
(plan['candidate_sha256']=='aded28d3c61dafaf7b8a7c96b14427a798e9e95470ae6f287bcd038c07626798' or
plan['original_executable_sha256']['com.apple.iokit.IOMobileGraphicsFamily']==FAMILY_TEXT_SHA),
'Exact previously qualified original-family candidate')
def read(va,n):
at=backed_offset(image['segments'],va,n,len(blob));return blob[at:at+n]
names={n:v for v,group in exports(blob,image).items() for n in group}
family='com.apple.iokit.IOMobileGraphicsFamily'
text=next(e for e in plan['extents'] if e['module']==family and e['segment']=='__TEXT_EXEC')
require(hashlib.sha256(read(text['va'],text['size'])).hexdigest()==plan['original_executable_sha256'][family],
'Complete original executable remains unchanged')
def vptr(pc):
a,b,c=struct.unpack('<3I',read(pc,12))
require(a&0x9f00001f==0x90000008 and b&0xffc003ff==0x91000108 and c==0x91004108,'Actual constructor ADRP/ADD/vptr header')
imm=((a>>5)&0x7ffff)*4+((a>>29)&3)
if imm&(1<<20):imm-=1<<21
return (pc&~4095)+(imm<<12)+((b>>10)&4095)+16
def branch(pc):
w=struct.unpack('<I',read(pc,4))[0];require(w&0xfc000000==0x94000000,'Actual direct constructor call')
value=w&0x3ffffff;return pc+4*(value-(1<<26) if value&(1<<25) else value)
base_vptr=vptr(0xfffffff00577b324);service_vptr=vptr(0xfffffff00577b51c);legacy_vptr=vptr(0xfffffff0057865d8)
require(branch(0xfffffff0057865fc)==0xfffffff00577b510
and branch(0xfffffff00577b540)==0xfffffff00577b318,'Actual protected Legacy -> Service -> Framebuffer constructor chain')
base=struct.unpack('<262Q',read(base_vptr,262*8));service=struct.unpack('<262Q',read(service_vptr,262*8))
require([i for i,(a,b) in enumerate(zip(base,service)) if a!=b]==[0,1,7],'Service adds no generic operational overrides')
legacy=struct.unpack('<358Q',read(legacy_vptr,358*8))
require(all(legacy) and read(legacy_vptr+358*8,24)==bytes(24),'Exact nonnull method extent and next aligned vtable boundary')
pure=[i for i,va in enumerate(legacy) if va==names['___cxa_pure_virtual']]
require(pure==[211,213,245,246,306,307,337,338,345],'Actual remaining abstract slots')
changed=[i for i,(a,b) in enumerate(zip(base,legacy)) if a!=b]
require(len(changed)==103 and [i for i in changed if i<168]==[0,1,7,14,21,44,79,85,86,87,111,141,155],
'Actual original generic implementation overrides')
require(names['__ZN9IOService13newUserClientEP4taskPvjPP12IOUserClient']==base[141],
'Actual newUserClient override occupies the native four-argument ABI slot')
for i,va in enumerate(legacy):
if i>=168 and i not in pure:require(text['va']<=va<text['va']+text['size'],'Original family owns inherited generic method')
runtime=ROOT/'build/darwinboot-owner-hvf-mobile-graphics-legacy-class/gdb-stop.json'
out=ROOT/'build/display-analysis/mobile-graphics-legacy-abi.json'
RUNTIME_SHA='3044c2beff552911605994c0dc7aa73182066cbb5aed0b396999ceeb5a96da2b'
if runtime.exists():
d=json.loads(runtime.read_text());args=[int(v,16) for v in d['function_entry_arguments']]
require(d['kernel_sha256']==plan['candidate_sha256'] and d['function_return_stack_matched']
and args[0]==0xfffffff006b35940 and args[2:4]==[0xfffffff006b31400,0x18a0]
and d['entry_kernel_bytes_match']['bytes']==b'IOMobileFramebufferLegacy\0'.hex(),
'Actual original Legacy identity, parent and object size')
runtime_sha=hashlib.sha256(runtime.read_bytes()).hexdigest()
else:
# The QEMU observation of the original class (identity IOMobileFramebufferLegacy, parent 0xfffffff006b31400, object size 0x18a0)
# was pruned. It concerns the unchanged original kernelcache, not the native kernel, so only its recorded hash is carried over.
require(out.exists() and json.loads(out.read_text())['runtime_metadata_checkpoint_sha256']==RUNTIME_SHA,
'Recorded runtime observation hash')
runtime_sha=RUNTIME_SHA
out.write_text(json.dumps(dict(candidate_sha256=plan['candidate_sha256'],original_sha256=plan['original_sha256'],
original_family_text_sha256=plan['original_executable_sha256'][family],
runtime_metadata_checkpoint_sha256=runtime_sha,
base_vptr=hex(base_vptr),service_vptr=hex(service_vptr),legacy_vptr=hex(legacy_vptr),
legacy_instance_bytes=0x18a0,legacy_virtual_entries=358,abstract_slots=pure,
inherited_generic_changes=changed,source_service_operation_overrides=0,
original_new_user_client=hex(legacy[141]),original_init=hex(legacy[21]),original_start=hex(legacy[86]),
new_user_client_abi='IOService::newUserClient(task_t,void*,UInt32,IOUserClient**)',
original_stop=hex(legacy[87]),original_client_initializer=hex(0xfffffff005776810),
methods=[dict(slot=i,address=hex(va),abstract=i in pure,prototype_qualified=False) for i,va in enumerate(legacy)],
static_dispatch_audit=True,legacy_runtime_instance_created=False,backend_contract_complete=False,
original_instructions_modified=False,desktop=False),indent=2)+'\n')
print('PASS: original Legacy has 358 slots with nine abstract leaves; original generic client/start/dispatch entries identified')
print('Service adds no operational overrides; Legacy initialization/start/backend contracts still require implementation and runtime qualification')