"""read_vtable_live.py [num_slots=16] Read vtable slot pointers from a live process via ReadProcessMemory. """ import argparse, ctypes, ctypes.wintypes as wt, struct, sys PROCESS_VM_READ = 0x10 PROCESS_QUERY_INFORMATION = 0x400 k = ctypes.windll.kernel32 k.OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD] k.OpenProcess.restype = wt.HANDLE k.CloseHandle.argtypes = [wt.HANDLE] k.CloseHandle.restype = wt.BOOL k.ReadProcessMemory.argtypes = [wt.HANDLE, wt.LPCVOID, wt.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] k.ReadProcessMemory.restype = wt.BOOL def read_bytes(h, addr, n): buf = (ctypes.c_ubyte * n)() sz = ctypes.c_size_t(0) if not k.ReadProcessMemory(h, addr, buf, n, ctypes.byref(sz)): return None return bytes(buf[:sz.value]) def main(): ap = argparse.ArgumentParser() ap.add_argument("pid", type=int) ap.add_argument("vtable", type=lambda x: int(x, 0)) ap.add_argument("slots", type=int, nargs="?", default=16) ap.add_argument("--peek", type=int, default=8, help="bytes to peek at each slot's target") args = ap.parse_args() h = k.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, False, args.pid) if not h: print(f"OpenProcess({args.pid}) err={ctypes.get_last_error()}") sys.exit(2) buf = read_bytes(h, args.vtable, args.slots * 4) if not buf: print(f"read vtable @ 0x{args.vtable:08x} failed") sys.exit(3) print(f"vtable @ 0x{args.vtable:08x}") for i in range(args.slots): slot = struct.unpack_from("