"""scan_gmui_subclasses.py Scan for all aligned dwords equal to 0x007ccb60 (NoticeHandler vtable at offset 0x5F8 of every gm*UI instance). For each hit at address H, read the OUTER instance vtable at (H - 0x5F8) — that's the instance's primary vtable at offset 0. Histogram by outer vtable. Also dump the SECONDARY vtables at well-known offsets to help disambiguate subclasses (since gm*UI uses multiple inheritance). """ import struct, sys from collections import Counter, defaultdict from minidump.minidumpfile import MinidumpFile def _ei(v): if v is None: return 0 if hasattr(v, 'value'): return int(v.value) return int(v) NOTICE_VT = 0x007ccb60 NOTICE_OFF = 0x5F8 def main(): md = MinidumpFile.parse(sys.argv[1]) reader = md.get_reader().get_buffered_reader() # Module map mods = [] for m in md.modules.modules: mods.append((m.baseaddress, m.size, m.name)) def mod_of(addr): for b, s, n in mods: if b <= addr < b + s: return n.split("\\")[-1] return None # Image-region ranges (for vtable validation) image_ranges = [] for r in md.memory_info.infos: st, ty = _ei(r.State), _ei(r.Type) if st == 0x1000 and ty == 0x1000000: image_ranges.append((r.BaseAddress, r.BaseAddress + r.RegionSize)) image_ranges.sort() def is_image(addr): for lo, hi in image_ranges: if lo <= addr < hi: return True if addr < lo: return False return False # Scan all committed RW non-image regions for the NoticeHandler vtable value scan_regions = [] for r in md.memory_info.infos: st, ty, pr = _ei(r.State), _ei(r.Type), _ei(r.Protect) & 0xff if st != 0x1000: continue if ty == 0x1000000: continue if pr not in (0x04, 0x40): continue scan_regions.append((r.BaseAddress, r.RegionSize)) total_bytes = sum(s for _, s in scan_regions) print(f"scanning {len(scan_regions)} writable non-image regions ({total_bytes/(1024*1024):.1f} MB)") # All hit addresses (where the NoticeHandler vtable value appears) hit_addrs = [] region_buffers = {} # base -> (size, bytes) for base, size in scan_regions: try: reader.move(base) buf = reader.read(size) except Exception: continue if not buf: continue region_buffers[base] = (size, buf) end = (len(buf) // 4) * 4 for off in range(0, end, 4): v = struct.unpack_from("= sample_n * 0.7: # 70% agreement consistent_offsets.append((off, top_v, top_c, len(vh))) consistent_offsets.sort() print(f" consistent image-pointers (>=70% of {sample_n} samples):") for off, val, cnt, unique in consistent_offsets: tag = "" if off == 0: tag = " [PRIMARY VTABLE]" elif off == NOTICE_OFF: tag = " [NoticeHandler]" print(f" +0x{off:04x} val=0x{val:08x} ({cnt}/{sample_n}, {unique} unique){tag}") if __name__ == "__main__": main()