"""compare_mesh_templates.py Compare the byte signature (+0x04 to +0x18) of orphan vs held meshes to figure out what +0x04 means and whether orphans share a template. """ import ctypes, ctypes.wintypes as wt, struct, sys from collections import Counter VTABLE = 0x007ed3b0 PROCESS_VM_READ=0x10; PROCESS_QUERY_INFORMATION=0x400 MEM_COMMIT=0x1000; MEM_PRIVATE=0x20000 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 pid = int(sys.argv[1]) h = k.OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, False, pid) rw_regions = [] mbi=MBI(); addr=0 while k.VirtualQueryEx(h, addr, ctypes.byref(mbi), ctypes.sizeof(mbi)): if mbi.State==MEM_COMMIT and mbi.Type==MEM_PRIVATE and (mbi.Protect&0xff) 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)): rw_regions.append((mbi.BaseAddress, bytes(buf[:sz.value]))) addr=(mbi.BaseAddress or 0)+mbi.RegionSize if addr>=0x80000000: break mesh_data = {} for base, data in rw_regions: end = (len(data)//4)*4 for off in range(0, end-0x40, 4): if struct.unpack_from(' 0] print(f'orphans: {len(orphans)} held: {len(held)}') # Histogram +0x04 for orphans vs held def hist_off(addrs, offset, top_n=10): c = Counter() for a in addrs: v = struct.unpack_from(' 0: for i in range(0x40): byte_set = set(mesh_data[a][i] for a in orphans) if len(byte_set) > 1: print(f' +0x{i:02x}: varies — sample values: {sorted(byte_set)[:5]}') # Pick an orphan and read its +0x2c field — that's the buffer pointer per earlier analysis print() print('=== Buffer pointer (+0x2c) of orphans ===') buf_addrs = [] for a in orphans: bp = struct.unpack_from('