"""Kaleidostrobe Mach-O PPC helper: sections, symbols, stubs, annotated disassembly.""" import struct, sys, re import capstone import os BIN = os.environ.get('KALEIDOSTROBE_BIN', os.path.expanduser( '~/documents/misc/lil hacky bits/Kaleidostrobe.bundle/Contents/MacOS/Kaleidostrobe')) D = open(BIN, 'rb').read() def u32(a): return struct.unpack('>I', D[a:a+4])[0] def s16(v): return v - 0x10000 if v & 0x8000 else v sections = {} # name -> (addr,size,off,reserved1,segname) syms = [] # (addr, sect, name) allsyms = [] off = 28; ncmds = struct.unpack('>I', D[16:20])[0] dysym = None; symtab = None seclist = [] for _ in range(ncmds): cmd, sz = struct.unpack('>II', D[off:off+8]) if cmd == 1: ns = struct.unpack('>I', D[off+48:off+52])[0] so = off + 56 for _ in range(ns): sn = D[so:so+16].rstrip(b'\0').decode(); sg = D[so+16:so+32].rstrip(b'\0').decode() a, s, o, al, ro, nr, fl, r1, r2 = struct.unpack('>9I', D[so+32:so+68]) seclist.append((sg, sn, a, s, o, fl, r1, r2)) sections[(sg, sn)] = (a, s, o, r1, r2) so += 68 elif cmd == 2: symtab = struct.unpack('>4I', D[off+8:off+24]) elif cmd == 11: dysym = struct.unpack('>18I', D[off+8:off+80]) off += sz so, ns, stro, strs = symtab for i in range(ns): sx, ty, sect, desc, val = struct.unpack('>IBBhI', D[so+i*12:so+i*12+12]) name = D[stro+sx:D.index(b'\0', stro+sx)].decode() allsyms.append((name, ty, sect, val)) if not (ty & 0xe0) and (ty & 0x0e) == 0x0e: syms.append((val, sect, name)) syms.sort() addr2name = {} for v, s, n in syms: addr2name.setdefault(v, n) funcs = [(v, n) for v, s, n in syms if s == 1] func_size = {} for i, (v, n) in enumerate(funcs): func_size[n] = (v, (funcs[i+1][0] if i+1 < len(funcs) else 0x112e4) - v) # indirect symbols indirectsymoff, nindirect = dysym[12], dysym[13] ind = [u32(indirectsymoff + 4*i) for i in range(nindirect)] stubname = {} for (sg, sn, a, s, o, fl, r1, r2) in seclist: typ = fl & 0xff if typ in (6, 7): # non-lazy / lazy pointers, 4 bytes each for k in range(s // 4): idx = ind[r1 + k] nm = allsyms[idx][0] if idx < len(allsyms) and idx not in (0x80000000, 0x40000000) else '?local' stubname[a + 4*k] = ('ptr', nm) elif typ == 8: # symbol stubs for k in range(s // r2): idx = ind[r1 + k] nm = allsyms[idx][0] if idx < len(allsyms) else '?' stubname[a + r2*k] = ('stub', nm) def symfor(a): if a in addr2name: return addr2name[a] if 0x112e4 <= a < 0x11f5c and not (0x113c0 <= a < 0x1140c): return f'K_{a:x}' best = None for v, s, n in syms: if v <= a: best = (v, n) else: break if best: return f'{best[1]}+{a-best[0]:#x}' return hex(a) def section_of(a): for (sg, sn, sa, s, o, fl, r1, r2) in seclist: if sa <= a < sa + s: return sn return None def fval(a, dbl): if a + (8 if dbl else 4) > len(D): return None return struct.unpack('>d' if dbl else '>f', D[a:a+(8 if dbl else 4)])[0] md = capstone.Cs(capstone.CS_ARCH_PPC, capstone.CS_MODE_32 | capstone.CS_MODE_BIG_ENDIAN) def disasm(name, seed=None): start, size = func_size[name] out = [] base = dict(seed or {}) stack = {} last_call_ret = None for ins in md.disasm(D[start:start+size], start): m, ops = ins.mnemonic, ins.op_str note = '' regs = [r.strip() for r in ops.split(',')] word = u32(ins.address) opc = word >> 26 dest = regs[0] if regs and re.fullmatch(r'r\d+', regs[0]) else None handled = False if opc in (16, 18): mm = re.search(r'0x[0-9a-f]+$', ops) if mm: tgt = int(mm.group(0), 16) if word & 1: last_call_ret = ins.address + 4 note = stubname[tgt][1] if tgt in stubname else symfor(tgt) if tgt != ins.address + 4: for r in ('r0','r3','r4','r5','r6','r7','r8','r9','r10','r11','r12','ctr'): base.pop(r, None) base['lr'] = last_call_ret elif not (start <= tgt < start + size): note = symfor(tgt) handled = True elif m == 'mflr': v = base.get('lr', last_call_ret) if v is not None: base[dest] = v; note = f'= {v:#x}' handled = True elif m == 'mtlr': if regs[0] in base: base['lr'] = base[regs[0]] else: base.pop('lr', None) handled = True elif m == 'mtctr': if regs[0] in base: base['ctr'] = base[regs[0]] else: base.pop('ctr', None) handled = True elif m == 'mfctr': if 'ctr' in base: base[dest] = base['ctr'] else: base.pop(dest, None) handled = True elif m == 'mr': if regs[1] in base: base[dest] = base[regs[1]] else: base.pop(dest, None) handled = True elif m == 'addis': if regs[1] in base: base[dest] = (base[regs[1]] + (s16(word & 0xffff) << 16)) & 0xffffffff else: base.pop(dest, None) handled = True elif m == 'addi': if regs[1] in base: a = (base[regs[1]] + s16(word & 0xffff)) & 0xffffffff base[dest] = a; note = f'&{symfor(a)}' else: base.pop(dest, None) handled = True else: mm = re.match(r'(\S+), (-?(?:0x)?[0-9a-f]*)\((r\d+)\)$', ops) if mm and (m.startswith('l') or m.startswith('st')): breg = mm.group(3); rt = mm.group(1) d16 = s16(word & 0xffff) if breg == 'r1': if m == 'stw': if rt in base: stack[d16] = base[rt] else: stack.pop(d16, None) elif m == 'lwz': if d16 in stack: base[rt] = stack[d16]; note = f'(spill {stack[d16]:#x})' else: base.pop(rt, None) elif m.startswith('l') and rt.startswith('r'): base.pop(rt, None) handled = True elif breg in base: a = (base[breg] + d16) & 0xffffffff note = symfor(a) loaded = None if a in stubname and stubname[a][0] == 'ptr': note = f'[ptr->{stubname[a][1]}]' if section_of(a) == '__nl_symbol_ptr': loaded = u32(a); note += f' = {symfor(loaded)}' if m in ('lfs','lfd','stfs','stfd') and section_of(a) in ('__const','__data'): v = fval(a, m in ('lfd','stfd')) note += f' = {v!r}' if m.startswith('l') and rt.startswith('r'): if loaded is not None: base[rt] = loaded else: base.pop(rt, None) handled = True else: if m.startswith('l') and rt.startswith('r'): base.pop(rt, None) handled = True if not handled and dest and m not in ('cmpwi','cmplwi','cmpw','cmplw','stmw','fcmpu','stwx','stbx','sthx','stfsx','stfdx','stw','stb','sth','stfs','stfd'): base.pop(dest, None) out.append(f'{ins.address:5x} {m:8s} {ops:30s} {("; " + note) if note else ""}') return '\n'.join(out) if __name__ == '__main__': for n in sys.argv[1:]: print(f'===== {n} ({func_size[n][0]:#x}, {func_size[n][1]} bytes)') print(disasm(n))