"""classify_0x0079385c_v2.py V2 of the classifier. Two new approaches: 1. Test the "CObjCell shell at +0x30 and +0x54" hypothesis: for each hit, check if there's ALSO a 0x0079385c marker exactly 0x24 (36) bytes away (i.e. the OTHER offset of the same CObjCell). If yes → likely a real CObjCell shell pair. Count those. 2. Check immediate-neighborhood context. A "real leaked object" looks like: - Object head at some 8/16-byte-aligned address - First DWORD is a vtable pointer in .rdata range - Most of object is zeros or sensible field values A "compiler-baked constant" looks like: - Surrounded by code/anim data, not separable as an object - May appear right after a function pointer (in a vtable construction) or in a const-data array Approach: for each hit, look at the 16 bytes BEFORE the hit. If the preceding DWORDs contain ANY value in the executable-range 0x00400000-0x00700000 (which would be CODE pointer), this is likely an embedded constant in compiled data, not a runtime object field. Real heap object fields would not have code-range pointers RIGHT before the marker. """ import argparse, ctypes, ctypes.wintypes as wt, struct, sys from collections import Counter PROCESS_VM_READ = 0x10 PROCESS_QUERY_INFORMATION = 0x400 MEM_COMMIT = 0x1000 MEM_PRIVATE = 0x20000 TARGET = 0x0079385c # Code is in .text typically 0x00401000 - 0x006xxxxx CODE_LO = 0x00401000 CODE_HI = 0x006d0000 # Read-only data (.rdata) typically follows .text RDATA_LO = 0x006d0000 RDATA_HI = 0x008c0000 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) regions = [] 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 target_bytes = struct.pack("= 4: prev = struct.unpack_from("= 16: window = data[idx-16:idx] wd = struct.unpack("7}") print() print(f"Prior 16 bytes all zero (likely object field): {prev16_all_zero}") print(f"Prior 16 bytes has code-ptr (likely baked data): {prev16_has_code}") print() print("=== Top 15 regions by hit count ===") region_hit_counter.sort(key=lambda x: -x[2]) for base, rsize, c in region_hit_counter[:15]: print(f" base=0x{base:08x} size={rsize/1024:>8.1f}KB hits={c:>5} hits/KB={c*1024/rsize:.2f}")