#!/usr/bin/env python3 """Discover the runtime class that carries the parsed `UN_*` unit definitions. No vtable VA is assumed. Procedure: 1. locate every disc unit-ID string in the snapshot; 2. find every 4-byte-aligned word in RAM that points at (string_va - d) for a few plausible name-record deltas; 3. for each such pointer site P and each small back-offset k, read the word at P-k and tally it across *distinct* unit IDs. A word that appears at the same (d, k) for many different units is that class's vtable pointer, and k is the ID-pointer offset inside the object. """ import os import re import struct import sys from collections import defaultdict sys.path.insert(0, "/home/fabi/RE - Project Sylpheed/sylpheed-reborn/tools/re-capture") import gmem # noqa: E402 DELTAS = [0x00, 0x10, 0x08, 0x04, 0x0C, 0x14, 0x18, 0x20] BACK = list(range(0, 0x60, 4)) def load_ids(path): ids = [] for line in open(path): if line.startswith("REC "): p = line.split() if p[3] == "Generic" and p[4].startswith("UN_"): ids.append(p[4]) return sorted(set(ids)) def scan_strings(f, size, ids): """{id: [va,...]} — every NUL-terminated occurrence of each id string.""" pats = {i: (i.encode() + b"\0") for i in ids} rx = re.compile(b"|".join(re.escape(p) for p in pats.values())) out = defaultdict(list) for a, b in gmem.extents(f.fileno(), size): f.seek(a) blob = f.read(b - a) for m in rx.finditer(blob): off = a + m.start() va = gmem.primary_va(off) if va is not None: out[m.group(0)[:-1].decode()].append(va) return out def scan_pointers(f, size, targets): """{target_va: [site_va,...]} for every aligned word equal to a target.""" want = {struct.pack(">I", t): t for t in targets} rx = re.compile(b"|".join(re.escape(k) for k in want)) out = defaultdict(list) for a, b in gmem.extents(f.fileno(), size): f.seek(a) blob = f.read(b - a) for m in rx.finditer(blob): off = a + m.start() if off % 4: continue va = gmem.primary_va(off) if va is not None: out[want[m.group(0)]].append(va) return out def main(): ids = load_ids(sys.argv[1]) path = gmem.mem_path() size = os.path.getsize(path) with open(path, "rb") as f: strs = scan_strings(f, size, ids) print(f"# {len(ids)} disc unit IDs, {len(strs)} found in RAM " f"({sum(len(v) for v in strs.values())} occurrences)", file=sys.stderr) # every (id, string_va - delta) we want pointers to targets, owner = set(), defaultdict(set) for uid, vas in strs.items(): for va in vas: for d in DELTAS: t = va - d targets.add(t) owner[t].add((uid, d)) ptrs = scan_pointers(f, size, targets) print(f"# {len(ptrs)} of {len(targets)} candidate targets are pointed at", file=sys.stderr) # tally (delta, back-offset, word) over distinct unit IDs tally = defaultdict(set) sites = defaultdict(list) for tgt, plist in ptrs.items(): for uid, d in owner[tgt]: for p in plist: for k in BACK: base = p - k try: f.seek(gmem.va_to_off(base)) except ValueError: continue w = f.read(4) if len(w) < 4: continue (vt,) = struct.unpack(">I", w) if 0x82000000 <= vt < 0x83000000: tally[(d, k, vt)].add(uid) sites[(d, k, vt)].append((base, uid)) rank = sorted(tally.items(), key=lambda kv: -len(kv[1])) print(f"{'delta':>6} {'idoff':>6} {'word':>10} {'#ids':>5}") for (d, k, vt), s in rank[:25]: print(f"{d:#6x} {k:#6x} {vt:#010x} {len(s):5d} e.g. {sorted(s)[:2]}") if rank: best = rank[0][0] print("\n# object bases for the top candidate (first 20):", file=sys.stderr) for base, uid in sorted(set(sites[best]))[:20]: print(f"# {base:#010x} {uid}", file=sys.stderr) if __name__ == "__main__": main()