#!/usr/bin/env python3 """Recover absolute x86_64 ELF syscall switch tables, retaining case evidence.""" import gzip import hashlib import json from pathlib import Path import re import struct from analyze_native import parse, take from analyze_qvm import write_json def recover(data, disassembly, symbol): layout, _ = parse(data) if layout['format'] != 'ELF' or layout['bits'] != 64: raise ValueError('this switch-table recognizer supports ELF64 only') function = re.search(r'(?m)^([0-9a-f]+) <'+re.escape(symbol)+r'>:\n(.*?)(?=\n[0-9a-f]+ <|\Z)', disassembly, re.S) if not function: raise ValueError(f'missing disassembly symbol {symbol}') prologue = '\n'.join(function[2].splitlines()[:24]) maximum = re.search(r'cmpq\s+\$0x([0-9a-f]+), %(\w+)', prologue) table = re.search(r'jmpq\s+\*0x([0-9a-f]+)\(,%(\w+),8\)', prologue) if not maximum or not table or maximum[2] != table[2]: raise ValueError(f'unrecognized bounds/table pattern in {symbol}') maximum, table_address = int(maximum[1], 16), int(table[1], 16) if maximum > 4096: raise ValueError('unreasonable syscall switch size') table_section = next((s for s in layout['sections'] if not s['zero_fill'] and s['address'] <= table_address and table_address+(maximum+1)*8 <= s['address']+s['bytes']), None) if table_section is None: raise ValueError('switch table outside a file-backed section') offset = table_section['file_offset']+table_address-table_section['address'] targets = struct.unpack('<'+'Q'*(maximum+1), take(data, offset, (maximum+1)*8)) lines = [] for line in disassembly.splitlines(): match = re.match(r'\s*([0-9a-f]+):\s', line) if match: lines.append((int(match[1], 16), line.strip())) indexes = {address: n for n, (address, _) in enumerate(lines)} groups = {} for number, target in enumerate(targets): if not any(s['executable'] and s['address'] <= target < s['address']+s['bytes'] for s in layout['sections']) or target not in indexes: raise ValueError('switch target is not a decoded executable instruction') groups.setdefault(target, []).append(number) cases = [] for target, numbers in sorted(groups.items()): snippet = [] for _, line in lines[indexes[target]:indexes[target]+24]: snippet.append(line) if re.search(r'\t(?:retq?|jmpq?)\s', line): break names = sorted(set(re.findall(r'\b(?:callq?|jmpq?)\s+[^\n<]*<([^>+]+)(?:\+[^>]*)?>', '\n'.join(snippet)))) cases.append({'target_address': target, 'numbers_sharing_target': numbers, 'first_instructions': snippet, 'direct_call_or_jump_labels': names}) return {'symbol': symbol, 'entry_address': int(function[1], 16), 'maximum_case_number': maximum, 'table_address': table_address, 'table_file_offset': offset, 'case_target_count': len(groups), 'cases': cases, 'limitations': 'Recognized bounds-check and absolute pointer-table pattern. Shared targets can be default/aliases; 24-instruction snippets are evidence, not complete case semantics or syscall signatures.'} def main(): output = [] for label in ('linux--Quake3-UrT-Ded.x86_64', 'linux--Quake3-UrT.x86_64'): report = json.loads(Path(f'docs/research/native/{label}.json').read_text()) path = Path('.cache/UrbanTerror43')/report['source']['path'] data = path.read_bytes() if hashlib.sha256(data).hexdigest() != report['source']['sha256']: raise ValueError('native snapshot mismatch') artifact = report['artifacts']['disassembly.txt'] disassembly = gzip.open(artifact['path'], 'rb').read() if hashlib.sha256(disassembly).hexdigest() != artifact['uncompressed_sha256']: raise ValueError('disassembly artifact changed') symbols = ['SV_GameSystemCalls'] if 'Ded' in label else ['SV_GameSystemCalls', 'CL_CgameSystemCalls', 'CL_UISystemCalls'] rows = [recover(data, disassembly.decode(), symbol) for symbol in symbols] result = {'source': report['source'], 'dispatches': rows} write_json(Path(f'docs/research/native/{label}.dispatch.json'), result) output.extend({'image': label, 'symbol': r['symbol'], 'maximum': r['maximum_case_number'], 'table_address': r['table_address'], 'distinct_targets': r['case_target_count']} for r in rows) write_json(Path('docs/research/native/dispatch-summary.json'), output) print('Recovered four native syscall dispatch tables with address-keyed case snippets') if __name__ == '__main__': main()