//go:build windows package winpwn import ( "fmt" "unsafe" "golang.org/x/sys/windows" ) // Offsets within ntdll's x64 _PEB, confirmed via `dt ntdll!_PEB` the same // way heap.go's _HEAP offsets were -- see heap.go's top comment. const ( pebOffNumberOfHeaps = 0x0e8 pebOffProcessHeaps = 0x0f0 // PVOID*, an array of NumberOfHeaps heap addresses ) // ListProcessHeaps enumerates every heap that exists in pid's address // space by walking PEB.ProcessHeaps from outside the process -- the exact // same array GetProcessHeaps() reads from inside one, just reached via // ReadProcessMemory the way ResolveModuleBase (procmem_windows.go) reads // PEB.Ldr for the loaded-module list instead of needing a leak. The // default process heap (PEB.ProcessHeap) is always included, since // HeapCreate registers every heap -- including the default one ntdll // creates before main() even runs -- into this same array. // // This is the natural companion to DetectHeapKind/ReadHeap: once you have // a PID and nothing else, ListProcessHeaps is how you find an address // worth handing to either of them, instead of needing a leaked heap handle // from the target's own output first. func ListProcessHeaps(pid uint32) ([]uint64, error) { mem, err := OpenProcessMemory(pid, 0) if err != nil { return nil, err } defer mem.Close() var pbi windows.PROCESS_BASIC_INFORMATION var retLen uint32 if err := windows.NtQueryInformationProcess(mem.Handle, windows.ProcessBasicInformation, unsafe.Pointer(&pbi), uint32(unsafe.Sizeof(pbi)), &retLen); err != nil { return nil, fmt.Errorf("NtQueryInformationProcess(ProcessBasicInformation): %w", err) } pebAddr := uint64(uintptr(unsafe.Pointer(pbi.PebBaseAddress))) if pebAddr == 0 { return nil, fmt.Errorf("PEB address for pid %d is null", pid) } numHeaps, err := readUint32AtValue(mem, int64(pebAddr)+pebOffNumberOfHeaps) if err != nil { return nil, fmt.Errorf("reading PEB.NumberOfHeaps: %w", err) } arrayAddr, err := readUint64At(mem, int64(pebAddr)+pebOffProcessHeaps) if err != nil { return nil, fmt.Errorf("reading PEB.ProcessHeaps: %w", err) } heaps := make([]uint64, 0, numHeaps) for i := uint32(0); i < numHeaps; i++ { addr, err := readUint64At(mem, int64(arrayAddr)+int64(i)*8) if err != nil { return heaps, fmt.Errorf("reading ProcessHeaps[%d] (of %d): %w", i, numHeaps, err) } heaps = append(heaps, addr) } return heaps, nil }