"""probe_260k_allocation_structure.py For each 260KB private RW region, dump VirtualQuery details (AllocationBase, Protect, AllocationProtect, RegionSize) plus VirtualQuery for the pages immediately AFTER the region — to detect if it's inside a larger reservation.""" import ctypes, ctypes.wintypes as wt, sys, struct from collections import Counter k = ctypes.windll.kernel32 k.OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]; k.OpenProcess.restype = wt.HANDLE k.VirtualQueryEx.argtypes = [wt.HANDLE, wt.LPCVOID, ctypes.c_void_p, ctypes.c_size_t] k.VirtualQueryEx.restype = ctypes.c_size_t class MBI(ctypes.Structure): _fields_ = [("BaseAddress", ctypes.c_void_p), ("AllocationBase", ctypes.c_void_p), ("AllocationProtect", wt.DWORD), ("RegionSize", ctypes.c_size_t), ("State", wt.DWORD), ("Protect", wt.DWORD), ("Type", wt.DWORD)] def vq(h, addr): mbi = MBI() if k.VirtualQueryEx(h, addr, ctypes.byref(mbi), ctypes.sizeof(mbi)): return mbi return None def prot_name(p): names = {0x01: "NA", 0x02: "RO", 0x04: "RW", 0x08: "WC", 0x10: "EX", 0x20: "ER", 0x40: "ERW", 0x80: "ERWC"} return names.get(p & 0xFF, f"0x{p:x}") pid = int(sys.argv[1]) h = k.OpenProcess(0x410, False, pid) if not h: print("OpenProcess fail"); sys.exit(2) candidates = [] addr = 0 while True: mbi = vq(h, addr) if not mbi: break base = mbi.BaseAddress or 0 if mbi.State == 0x1000 and mbi.RegionSize == 266240 and (mbi.Type & 0x20000): if (mbi.Protect & 0xFF) in (0x04, 0x40): candidates.append(mbi.BaseAddress) next_addr = base + mbi.RegionSize if next_addr <= addr: break addr = next_addr if addr >= 0x80000000: break print(f"Found {len(candidates)} candidate 260KB regions") # Statistics: base == alloc_base? self_alloc = 0 in_larger = 0 neighbor_stats = Counter() for b in candidates: mbi = vq(h, b) if not mbi: continue base = mbi.BaseAddress ab = mbi.AllocationBase if base == ab: self_alloc += 1 else: in_larger += 1 # Check neighbor immediately after the 260KB region after_addr = base + 266240 nbr = vq(h, after_addr) if nbr: same_alloc = (nbr.AllocationBase == ab) nbr_state = "COMMIT" if nbr.State == 0x1000 else ("RESERVE" if nbr.State == 0x2000 else "FREE") key = f"same_alloc={same_alloc} state={nbr_state}" neighbor_stats[key] += 1 print(f"\nself_alloc (base == AllocationBase): {self_alloc}") print(f"in_larger (base != AllocationBase): {in_larger}") print(f"\nNeighbor (page immediately after 260KB region):") for key, n in sorted(neighbor_stats.items(), key=lambda x: -x[1]): print(f" {key}: {n}") # Dump first 8 in detail print(f"\nDetailed dump of first 8 regions:") for b in candidates[:8]: mbi = vq(h, b) if not mbi: continue print(f"\n Region @0x{b:08x}:") print(f" BaseAddress = 0x{mbi.BaseAddress:08x}") print(f" AllocationBase = 0x{mbi.AllocationBase:08x} delta={(mbi.BaseAddress - mbi.AllocationBase):d}") print(f" RegionSize = {mbi.RegionSize}") print(f" Protect = {prot_name(mbi.Protect)}") print(f" AllocProtect = {prot_name(mbi.AllocationProtect)}") print(f" State = 0x{mbi.State:x}") print(f" Type = 0x{mbi.Type:x}") # Look at AllocationBase's full size if mbi.BaseAddress != mbi.AllocationBase: ab_mbi = vq(h, mbi.AllocationBase) if ab_mbi: print(f" --- AllocationBase region: ---") print(f" base=0x{ab_mbi.BaseAddress:08x} size={ab_mbi.RegionSize} prot={prot_name(ab_mbi.Protect)} type=0x{ab_mbi.Type:x}") # Look at neighbor after region nbr = vq(h, b + 266240) if nbr: same = (nbr.AllocationBase == mbi.AllocationBase) print(f" --- Neighbor after: 0x{nbr.BaseAddress:08x} size={nbr.RegionSize} prot={prot_name(nbr.Protect)} state=0x{nbr.State:x} same_alloc={same}") k.CloseHandle(h)