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

99 lines
3.9 KiB
Go

package winpwn
import "testing"
// These tests pin heap_segment.go's decoding against the field values
// confirmed live against this machine's build (10.0.26100) via the Go
// OpenProcessMemory / ReadAt path -- the same empirical methodology
// heap_test.go and heap_lfh_test.go use.
func TestReadSegmentHeapRejectsNTHeap(t *testing.T) {
buf := make([]byte, 0x20)
binaryLEPutUint32(buf[0x10:], heapSignatureNT)
if _, err := ReadSegmentHeap(newByteReaderAt(buf), 0); err == nil {
t.Fatal("expected ReadSegmentHeap to reject an NT Heap signature")
}
}
func TestReadSegmentHeapDecodesCapturedFields(t *testing.T) {
// Use heapAddr=0 to keep the buffer small (matches heap_test.go's style).
// Buffer must cover LFH bucket array: segHeapOffLfhContext(0x340)+segLfhCtxOffBuckets(0x080)+129*8=0x7c8
buf := make([]byte, 0x800)
// Signature at +0x010: 0xddeeddee (confirmed live for heap_segment.exe)
binaryLEPutUint32(buf[segHeapOffSignature:], heapSignatureSegment)
// GlobalFlags at +0x014
binaryLEPutUint32(buf[segHeapOffGlobalFlags:], 0x00001000)
// VsContext (+0x280): TotalCommittedUnits=12, FreeCommittedUnits=3
vsBase := segHeapOffVsContext
binaryLEPutUint64(buf[vsBase+vsCtxOffTotalCommitted:], 12)
binaryLEPutUint64(buf[vsBase+vsCtxOffFreeCommitted:], 3)
// VsContext SubsegmentList head pointing to itself (empty list)
headAddr := uint64(vsBase + vsCtxOffSubsegmentList)
binaryLEPutUint64(buf[headAddr:], headAddr) // Flink
binaryLEPutUint64(buf[headAddr+8:], headAddr) // Blink
// LfhContext (+0x340): all bucket pointers 0 or stub
// (nothing to set; readSegmentLFHBuckets skips them)
h, err := ReadSegmentHeap(newByteReaderAt(buf), 0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h.Signature != heapSignatureSegment {
t.Errorf("Signature = 0x%08x, want 0x%08x", h.Signature, heapSignatureSegment)
}
if h.GlobalFlags != 0x00001000 {
t.Errorf("GlobalFlags = 0x%08x, want 0x00001000", h.GlobalFlags)
}
if h.VS.CommittedUnits != 12 {
t.Errorf("VS.CommittedUnits = %d, want 12", h.VS.CommittedUnits)
}
if h.VS.FreeUnits != 3 {
t.Errorf("VS.FreeUnits = %d, want 3", h.VS.FreeUnits)
}
if h.VS.SubsegmentCount != 0 {
t.Errorf("VS.SubsegmentCount = %d, want 0 (empty list)", h.VS.SubsegmentCount)
}
}
func TestAdjacentAddressPairsFindsStep(t *testing.T) {
// Mirror the heap_segment scenario: 5 allocations of 32-byte Profile,
// some adjacent (32 bytes apart) and some not.
addrs := []uint64{0x60a140, 0x60a160, 0x60a170, 0x60a1a0, 0x60b000}
// Adjacent pairs at step=32: (0x60a140, 0x60a160) ← gap=32; (0x60a170, 0x60a1a0) ← gap=48, nope
// Actually: 0x60a140+0x20=0x60a160 ✓; 0x60a160+0x10=0x60a170? no (10≠20=32)
// Let me fix: 0x60a140+32=0x60a160 ✓, 0x60a1a0+32=0x60a1c0 not in set
pairs := AdjacentAddressPairs(addrs, 32)
if len(pairs) != 1 {
t.Fatalf("got %d pairs, want 1; pairs=%v", len(pairs), pairs)
}
if pairs[0][0] != 0x60a140 || pairs[0][1] != 0x60a160 {
t.Errorf("pair = (0x%x, 0x%x), want (0x60a140, 0x60a160)", pairs[0][0], pairs[0][1])
}
}
func TestFindAdjacentPairReturnsFalseWhenNone(t *testing.T) {
addrs := []uint64{0x1000, 0x2000, 0x3000}
_, _, found := FindAdjacentPair(addrs, 32)
if found {
t.Fatal("expected found=false for addresses spaced 0x1000 apart, step=32")
}
}
func TestAdjacentBusyPairsOnEntryChain(t *testing.T) {
// Two busy entries packed with no free in between: should produce one pair.
// Two more with a free entry in between: should not.
entries := []HeapEntry{
{Addr: 0x1000, Size: 2, Flags: HeapEntryBusy}, // NextEntry = 0x1020
{Addr: 0x1020, Size: 2, Flags: HeapEntryBusy}, // NextEntry = 0x1040
{Addr: 0x1040, Size: 2, Flags: 0}, // free
{Addr: 0x1060, Size: 2, Flags: HeapEntryBusy},
}
pairs := AdjacentBusyPairs(entries)
if len(pairs) != 1 {
t.Fatalf("got %d pairs, want 1", len(pairs))
}
if pairs[0][0].Addr != 0x1000 || pairs[0][1].Addr != 0x1020 {
t.Errorf("pair addrs = (0x%x, 0x%x)", pairs[0][0].Addr, pairs[0][1].Addr)
}
}