"""classify_0x0079385c_hits.py For each occurrence of the DWORD 0x0079385c in private RW memory of : - Record the absolute address of the hit - Walk BACKWARDS 16-byte aligned, looking for a plausible vtable pointer in the 0x00400000-0x00900000 (acclient .rdata) range with a small nonzero offset distance (<= 0x400 bytes). That's the object's start and offset-of-the-marker. - Group hits by: * offset-of-marker (e.g. +0x30, +0x54 confirms CObjCell) * the head vtable found (what class the object actually is) - Then independently bucket by REGION size of the containing VirtualAlloc region (informational, not allocation size). Output: * Total hits * Offset histogram (top 10) * Head-vtable histogram (top 10) — includes vtable address + count * Sample hex dumps (first 64 bytes) for top 3 head-vtable groups """ import argparse, ctypes, ctypes.wintypes as wt, struct, sys, time from collections import Counter, defaultdict PROCESS_VM_READ = 0x10 PROCESS_QUERY_INFORMATION = 0x400 MEM_COMMIT = 0x1000 MEM_PRIVATE = 0x20000 TARGET = 0x0079385c # acclient.exe is loaded around 0x00400000 with .rdata vtables typically # in the 0x00700000-0x00880000 range. Accept slightly wider for safety. VTABLE_LO = 0x00400000 VTABLE_HI = 0x00900000 class MBI(ctypes.Structure): _fields_ = [('BaseAddress', ctypes.c_void_p), ('AllocationBase', ctypes.c_void_p), ('AllocationProtect', wt.DWORD), ('PartitionId', wt.WORD), ('RegionSize', ctypes.c_size_t), ('State', wt.DWORD), ('Protect', wt.DWORD), ('Type', wt.DWORD)] k = ctypes.windll.kernel32 k.OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]; k.OpenProcess.restype = wt.HANDLE k.ReadProcessMemory.argtypes = [wt.HANDLE, wt.LPCVOID, wt.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] k.ReadProcessMemory.restype = wt.BOOL k.VirtualQueryEx.argtypes = [wt.HANDLE, ctypes.c_void_p, ctypes.POINTER(MBI), ctypes.c_size_t] k.VirtualQueryEx.restype = ctypes.c_size_t ap = argparse.ArgumentParser() ap.add_argument("pid", type=int) args = ap.parse_args() h = k.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, False, args.pid) if not h: print(f"OpenProcess({args.pid}) failed err={ctypes.get_last_error()}"); sys.exit(2) # Pass 1: enumerate all readable private RW regions; remember snapshots in memory # so we can back-scan WITHOUT extra remote reads. regions = [] # list of (base, data:bytes, region_size) mbi = MBI() addr = 0 while k.VirtualQueryEx(h, addr, ctypes.byref(mbi), ctypes.sizeof(mbi)): pr = mbi.Protect & 0xff if (mbi.State == MEM_COMMIT and mbi.Type == MEM_PRIVATE and pr in (0x04, 0x40)): buf = (ctypes.c_ubyte * mbi.RegionSize)() sz = ctypes.c_size_t(0) if k.ReadProcessMemory(h, mbi.BaseAddress, buf, mbi.RegionSize, ctypes.byref(sz)): regions.append((int(mbi.BaseAddress), bytes(buf[:sz.value]), int(mbi.RegionSize))) addr = (mbi.BaseAddress or 0) + mbi.RegionSize if addr >= 0x80000000: break total_hits = 0 offset_hist = Counter() # offset from inferred object head head_vt_hist = Counter() # vtable found at inferred object head region_size_hist = Counter() head_vt_samples = defaultdict(list) # vt -> list of (addr, first 64 bytes) # Bucket region sizes for histogram def bucket(sz): if sz < 1024: return "<1KB" if sz < 4*1024: return "1-4KB" if sz < 64*1024: return "4-64KB" if sz < 256*1024: return "64-256KB" if sz < 512*1024: return "256-512KB" if sz < 1024*1024: return "512KB-1MB" return ">=1MB" # For each hit, search backward for a vtable head. # Walk back up to 0x400 bytes (CObjCell-class size guess), aligned to 4. MAX_BACKSCAN = 0x400 for base, data, rsize in regions: end = (len(data) // 4) * 4 # Find all DWORD positions equal to TARGET # struct.iter_unpack is fast enough pos = 0 target_bytes = struct.pack("10} {c:>7}") print() print("=== Offset of marker from inferred object head (top 15) ===") for off, c in offset_hist.most_common(15): if off == -1: print(f" (no head found) {c:>7}") else: print(f" +0x{off:04x} {c:>7}") print() print("=== Head vtable histogram (top 15) ===") for vt, c in head_vt_hist.most_common(15): print(f" 0x{vt:08x} {c:>7}") print() print("=== Sample first-64-byte dumps for top 3 head vtables ===") for vt, _c in head_vt_hist.most_common(3): print(f"\n--- head vtable 0x{vt:08x} ---") for ha, snip in head_vt_samples[vt]: hexs = " ".join(f"{b:02x}" for b in snip) print(f" at 0x{ha:08x}: {hexs}")