#!/usr/bin/env python3 """Offline helper (runs HERE, stdlib-only): cluster vtable candidates. A vtable = run of >=3 consecutive LE32 words in rodata, each pointing into the code sect. Output CSV is consumed by the Ghidra-side script. Usage: python3 ghidra/gen_vtable_csv.py "Lofi-12XT_v1.5.205/.../Lofi-12 XT.bin" /tmp/vtables-1.5.205.csv """ import os import struct import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'tools')) from lofi_image import parse_image, find_sect_by_role def main(): image, out = sys.argv[1], sys.argv[2] d = open(image, 'rb').read() info = parse_image(d) code = find_sect_by_role(info, 'code') ro = find_sect_by_role(info, 'rodata') print("code sect%d %08X..%08X" % (code['index'], code['addr'], code['end'])) print("rodata sect%d %08X..%08X" % (ro['index'], ro['addr'], ro['end'])) p = ro['payload'] words = [struct.unpack('= 3: runs.append((i * 4, j - i, words[i:j])) i = j else: i += 1 with open(out, 'w') as f: f.write("rodata_off,load_addr,nentries,entries\n") for off, cnt, ws in runs: f.write("%d,%08X,%d,%s\n" % (off, ro['addr'] + off, cnt, ";".join("%08X" % w for w in ws))) to_code = sum(is_code_ptr) print("rodata LE words -> code: %d, vtable runs(>=3): %d -> %s" % (to_code, len(runs), out)) if __name__ == '__main__': main()