#!/usr/bin/env python3 """ ppcxref.py -- TOC-aware PPC annotator for the Console 4.10 PEF. Resolves r2(TOC)-relative loads to data addresses and, when those point at C strings, annotates the disassembly. Also builds a string cross-reference: which code addresses reference which strings. r2 (TOC base) = data section offset 0x8000 (from the main transition vector). Usage: py -3.13 ppcxref.py annotate # annotated disasm py -3.13 ppcxref.py xref # find code refs to strings matching substring py -3.13 ppcxref.py toc # what does TOC[-off(r2)] point to """ import sys, struct, capstone sys.path.insert(0, __import__('os').path.dirname(__file__)) from pefparse import PEF BIN = r"C:\VWE\TeslaRel410\410console\4_10-console-extracted\Console 4.10" TOC_BASE = 0x8000 # r2 value = data section offset pef = PEF(open(BIN,'rb').read()) CODE = pef.sections[0].data() DATA = pef.sections[1].data() def d32(off): if off < 0 or off+4 > len(DATA): return None return struct.unpack_from('>I', DATA, off)[0] def cstr_at(off, maxlen=80): if off < 0 or off >= len(DATA): return None e = off while e < len(DATA) and DATA[e] != 0 and (e-off) < maxlen: c = DATA[e] if c < 0x09 or (c > 0x0d and c < 0x20) or c > 0x7e: # allow only printable; stop otherwise if c != 0xc9: # ellipsis break e += 1 s = DATA[off:e] try: txt = s.decode('mac_roman') except Exception: return None # require mostly printable and length>=3 printable = sum(1 for c in s if 0x20 <= c <= 0x7e) if len(s) >= 3 and printable >= max(3, int(len(s)*0.8)): return txt return None def toc_target(disp): """disp is signed offset from r2. Returns (data_off, pointed_value, string_or_None).""" off = TOC_BASE + disp val = d32(off) s = None if val is not None: s = cstr_at(val) return off, val, s md = capstone.Cs(capstone.CS_ARCH_PPC, capstone.CS_MODE_32 | capstone.CS_MODE_BIG_ENDIAN) md.detail = True def annotate(start, count): base = 0 for insn in md.disasm(CODE[start:start+count*4], base+start): note = '' # detect r2-relative: op_str contains "r2)" with a displacement ops = insn.op_str # capstone PPC prints e.g. "r4, -0x6c9c(r2)" or "r3, r2, -0x6848" disp = None if '(r2)' in ops: # form: rX, disp(r2) try: d = ops.split(',')[-1].strip() d = d[:d.index('(r2)')] disp = int(d, 16) if d.startswith(('0x','-0x')) else int(d) except Exception: disp = None if disp is not None: o, val, s = toc_target(disp) if val is not None: note = "; TOC[0x%x]=*0x%x=0x%x" % (o, o, val) if s: note += ' "%s"' % s elif 'r2,' in ops and insn.mnemonic in ('addi','addic','subi'): # form: rX, r2, disp -> address = TOC_BASE+disp (pointer to data directly) try: d = ops.split(',')[-1].strip() disp = int(d, 16) if d.startswith(('0x','-0x')) else int(d) except Exception: disp = None if disp is not None: addr = TOC_BASE + disp s = cstr_at(addr) note = "; &data[0x%x]" % addr if s: note += ' "%s"' % s elif insn.mnemonic == 'lis': # high-half load, often followed by addi to form an absolute data ptr note = '' print("0x%08x %-8s %-28s %s" % (insn.address, insn.mnemonic, insn.op_str, note)) def build_string_table(): """All C strings in data with >=4 printable chars -> {addr: text}.""" tbl = {} i = 0 n = len(DATA) while i < n: if 0x20 <= DATA[i] <= 0x7e: j = i while j < n and (0x20 <= DATA[j] <= 0x7e): j += 1 if j - i >= 4: tbl[i] = DATA[i:j].decode('ascii','replace') i = j else: i += 1 return tbl def xref(substr): # find data offsets of strings containing substr strtbl = build_string_table() targets = {a:t for a,t in strtbl.items() if substr.lower() in t.lower()} if not targets: print("no strings match", substr); return print("=== target strings ===") for a,t in sorted(targets.items()): print(" 0x%06x %s" % (a, t)) # Build TOC reverse map: which TOC slots hold pointers into these strings # (pointer may target start OR interior). Map value->slot. tocslots = {} for slot in range(0, len(DATA), 4): v = d32(slot) if v in targets: tocslots.setdefault(v, []).append(slot) # Also direct addi r2 references use TOC_BASE+disp == string addr directly print("\n=== code references ===") # Disassemble whole code, look for r2-relative producing an address in targets for insn in md.disasm(CODE, 0): ops = insn.op_str hit = None if '(r2)' in ops: try: d = ops.split(',')[-1].strip(); d = d[:d.index('(r2)')] disp = int(d,16) if d.startswith(('0x','-0x')) else int(d) o = TOC_BASE+disp; v = d32(o) if v in targets: hit = (v, targets[v], 'TOC') except Exception: pass elif 'r2,' in ops and insn.mnemonic in ('addi','subi'): try: d = ops.split(',')[-1].strip() disp = int(d,16) if d.startswith(('0x','-0x')) else int(d) addr = TOC_BASE+disp if addr in targets: hit = (addr, targets[addr], 'direct') except Exception: pass if hit: print(" 0x%08x %-8s %-24s -> %s \"%s\"" % (insn.address, insn.mnemonic, ops, hit[2], hit[1])) if __name__ == '__main__': cmd = sys.argv[1] if cmd == 'annotate': annotate(int(sys.argv[2],0), int(sys.argv[3],0)) elif cmd == 'xref': xref(sys.argv[2]) elif cmd == 'toc': o,v,s = toc_target(int(sys.argv[2],0)) print("TOC off=0x%x val=0x%x str=%s" % (o, v if v else 0, s))