#!/usr/bin/env python3 """xref + slack scanner: LE32 pointers into code/rodata, NUL strings, zero gaps. Usage: lofi_xref.py "Lofi-12 XT.bin" [--find TEXT] [--strings-min 6] """ import argparse, os, struct, sys sys.path.insert(0, os.path.dirname(__file__)) from lofi_image import parse_image, find_sect_by_role def le_words(payload): for i in range(0, len(payload) - 3, 4): yield i, struct.unpack('= minlen: out.append((i, j - i)) i = j else: i += 1 return out def c_strings(payload, minlen=6): out, i, n = [], 0, len(payload) while i < n: if 32 <= payload[i] < 127: j = i while j < n and 32 <= payload[j] < 127: j += 1 if j - i >= minlen and j < n and payload[j] == 0: out.append((i, payload[i:j].decode())) i = max(j, i + 1) else: i += 1 return out def main(): ap = argparse.ArgumentParser() ap.add_argument('image') ap.add_argument('--find', default=None) ap.add_argument('--strings-min', type=int, default=6) ap.add_argument('--slack-min', type=int, default=64) a = ap.parse_args() d = open(a.image, 'rb').read() info = parse_image(d) code = find_sect_by_role(info, 'code') ro = find_sect_by_role(info, 'rodata') print(f"code: sect{code['index']} {code['addr']:08X}..{code['end']:08X} len={code['len']}") print(f"rodata: sect{ro['index']} {ro['addr']:08X}..{ro['end']:08X} len={ro['len']}") # pointers in rodata -> code, and rodata -> self to_code = to_self = 0 code_hits = [] for off, w in le_words(ro['payload']): if code['addr'] <= w < code['end']: to_code += 1 code_hits.append((off, w)) elif ro['addr'] <= w < ro['end']: to_self += 1 print(f"rodata xrefs: {to_code} -> code, {to_self} -> self") gaps = zero_runs(ro['payload'], a.slack_min) gaps.sort(key=lambda t: -t[1]) print(f"top zero gaps in rodata (min {a.slack_min}):") for off, ln in gaps[:10]: print(f" +{off:#x} (file {ro['off']+12+off:#x}) len={ln}") if a.find: needle = a.find.encode() hits = [] p = ro['payload'] s = 0 while True: i = p.find(needle, s) if i < 0: break hits.append(i) s = i + 1 print(f"'{a.find}': {len(hits)} hit(s) in rodata") for h in hits: faddr = ro['addr'] + h refs = [off for off, w in code_hits if False] # placeholder # find pointers TO this string: scan rodata words == faddr # (vtable-adjacent tables) — cheap exact scan ptrs = [] for off, w in le_words(ro['payload']): if w == faddr: ptrs.append(ro['off'] + 12 + off) print(f" +{h:#x} file={ro['off']+12+h:#x} load={faddr:08X} " f"referenced_by_{len(ptrs)}={['%#x' % x for x in ptrs[:8]]}") else: strs = c_strings(ro['payload'], a.strings_min) print(f"NUL strings len>={a.strings_min} in rodata: {len(strs)}") if __name__ == '__main__': main()