Files
go_pwner/heap_windows.go
T
2026-07-18 21:37:15 +03:00

69 lines
2.4 KiB
Go

//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
}