#!/usr/bin/env python3 """ hunt_actions.py -- static hunt for undecoded vr_action ids in shipped FLYK.EXE. Method: parse the LE fixup tables to find code sites that reference a given data-segment string (raw LE pages hold placeholders at fixup sites, so naive scanning cannot work), then disassemble the surrounding function with capstone and report the immediate constants loaded near velocirender_transmit calls -- the action id is among them. python hunt_actions.py "get_geom_vertices" [path-to-exe] """ import os, sys, struct sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from leimage import LE, rd EXE = sys.argv[2] if len(sys.argv) > 2 else \ r"s:\OneDrive\Tesla III\DaveMcCoy\sda4\HPDAVE\FLYK.EXE" def parse_fixups(le): """Yield (src_linear, target_obj, target_off) for 32-bit offset fixups.""" with open(le.path, 'rb') as f: # page fixup offset table: n_pages+1 dwords pgtab = rd(f, le.fixup_pg_off, 4 * (le.n_pages + 1)) offs = struct.unpack('<%dI' % (le.n_pages + 1), pgtab) recs_base = le.fixup_rec_off # page -> linear address mapping: need object bases + page ranges objs = rd(f, le.obj_tab_off, 24 * le.n_objects) page_lin = {} for i in range(le.n_objects): vsize, base, flags, pgidx, pgcnt, _ = struct.unpack_from('<6I', objs, i * 24) for k in range(pgcnt): page_lin[pgidx + k] = base + k * le.page_size out = [] for pg in range(1, le.n_pages + 1): lo, hi = offs[pg - 1], offs[pg] if hi <= lo: continue blob = rd(f, recs_base + lo, hi - lo) p = 0 while p < len(blob) - 3: src = blob[p]; flags = blob[p + 1]; p += 2 srcoffs = [] if src & 0x20: # source list cnt = blob[p]; p += 1 else: srcoffs.append(struct.unpack_from(' 1 else b"get_geom_vertices" le = LE(EXE) base1, code = le.image(1) base2, data = le.image(2) print(f"code obj1 @ {base1:#x} ({len(code)} bytes), data obj2 @ {base2:#x}") # all occurrences of the pattern in the data object targets = [] i = 0 while True: i = data.find(pat, i) if i < 0: break s = max(0, i - 60) start = data.rfind(b'\x00', s, i) + 1 targets.append((start, data[start:data.find(b'\x00', i)])) i += 1 if not targets: print("string not found"); return for off, s in targets: print(f"data+{off:#x} (lin {base2+off:#x}): {s[:70]!r}") fixups = parse_fixups(le) print(f"{len(fixups)} 32-bit fixups parsed") import capstone md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32) md.detail = False for off, s in targets: want = {base2 + off} sites = [src for src, tobj, toff in fixups if tobj == 2 and (base2 + toff) in want] print(f"\n=== refs to {s[:50]!r}: {len(sites)} site(s)") for src in sites: # disassemble a window before the reference site lo = max(base1, src - 0x90) blob = code[lo - base1: src - base1 + 0x60] print(f"-- code around {src:#x}:") for ins in md.disasm(blob, lo): mark = ' <== strref' if ins.address <= src < ins.address + ins.size else '' if ins.mnemonic in ('mov', 'push', 'call', 'cmp') or mark: txt = f" {ins.address:#x}: {ins.mnemonic} {ins.op_str}{mark}" if ins.mnemonic in ('mov', 'push') and ',' in ins.op_str: try: imm = int(ins.op_str.split(',')[1].strip(), 0) if 0 < imm < 0x60: txt += f" ** small imm {imm:#x}" except ValueError: pass print(txt) if __name__ == '__main__': main()