package winpwn import ( "bytes" "encoding/binary" "testing" "unicode/utf16" ) // buildSyntheticMinidump assembles a minimal but structurally real // MINIDUMP_HEADER + MINIDUMP_DIRECTORY[2] + ModuleListStream (one module) // + ExceptionStream, laid out at arbitrary offsets the directory points to // (deliberately not in stream order, to exercise the Rva indirection // rather than relying on everything being contiguous). This is the payoff // of parsing the format natively instead of via dbghelp.dll: a synthetic // fixture like this lets the parser be tested without a real crash dump. func buildSyntheticMinidump(t *testing.T) []byte { t.Helper() const ( headerSize = 32 directorySize = 12 * 2 // two streams ) moduleName := "ntdll.dll" nameUTF16 := utf16.Encode([]rune(moduleName)) nameBytes := make([]byte, 2*len(nameUTF16)) for i, u := range nameUTF16 { binary.LittleEndian.PutUint16(nameBytes[i*2:], u) } // MINIDUMP_STRING: Length (byte count, no terminator) + UTF-16 buffer + NUL. moduleNameStream := make([]byte, 4+len(nameBytes)+2) binary.LittleEndian.PutUint32(moduleNameStream[0:4], uint32(len(nameBytes))) copy(moduleNameStream[4:], nameBytes) moduleNameOff := uint32(headerSize + directorySize) moduleListOff := moduleNameOff + uint32(len(moduleNameStream)) // MINIDUMP_MODULE_LIST: NumberOfModules(4) + one MINIDUMP_MODULE(108). moduleList := make([]byte, 4+sizeofMinidumpModule) binary.LittleEndian.PutUint32(moduleList[0:4], 1) mod := moduleList[4:] binary.LittleEndian.PutUint64(mod[0:8], 0x00007FFE12340000) // BaseOfImage binary.LittleEndian.PutUint32(mod[8:12], 0x00200000) // SizeOfImage binary.LittleEndian.PutUint32(mod[16:20], 0x5F000000) // TimeDateStamp binary.LittleEndian.PutUint32(mod[20:24], moduleNameOff) // ModuleNameRva exceptionOff := moduleListOff + uint32(len(moduleList)) exception := make([]byte, sizeofMinidumpExceptionStream) binary.LittleEndian.PutUint32(exception[0:4], 1337) // ThreadId exc := exception[8:] binary.LittleEndian.PutUint32(exc[0:4], 0xC0000005) // ExceptionCode (access violation) binary.LittleEndian.PutUint64(exc[16:24], 0x00007FFE12341234) // ExceptionAddress binary.LittleEndian.PutUint32(exc[24:28], 2) // NumberParameters binary.LittleEndian.PutUint64(exc[32:40], 1) // Parameters[0]: write access binary.LittleEndian.PutUint64(exc[40:48], 0xDEADBEEF) // Parameters[1]: faulting address total := int(exceptionOff) + len(exception) out := make([]byte, total) binary.LittleEndian.PutUint32(out[0:4], minidumpSignature) binary.LittleEndian.PutUint32(out[8:12], 2) // NumberOfStreams binary.LittleEndian.PutUint32(out[12:16], headerSize) // StreamDirectoryRva dir := out[headerSize:] binary.LittleEndian.PutUint32(dir[0:4], uint32(StreamModuleList)) binary.LittleEndian.PutUint32(dir[4:8], uint32(len(moduleList))) binary.LittleEndian.PutUint32(dir[8:12], moduleListOff) binary.LittleEndian.PutUint32(dir[12:16], uint32(StreamException)) binary.LittleEndian.PutUint32(dir[16:20], uint32(len(exception))) binary.LittleEndian.PutUint32(dir[20:24], exceptionOff) copy(out[moduleNameOff:], moduleNameStream) copy(out[moduleListOff:], moduleList) copy(out[exceptionOff:], exception) return out } func TestMinidumpModules(t *testing.T) { raw := buildSyntheticMinidump(t) m, err := newMinidump(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } defer m.Close() mods, err := m.Modules() if err != nil { t.Fatal(err) } if len(mods) != 1 { t.Fatalf("got %d modules, want 1", len(mods)) } if mods[0].Name != "ntdll.dll" { t.Errorf("module name = %q, want %q", mods[0].Name, "ntdll.dll") } if mods[0].BaseOfImage != 0x00007FFE12340000 { t.Errorf("BaseOfImage = 0x%X, want 0x7FFE12340000", mods[0].BaseOfImage) } if mods[0].SizeOfImage != 0x00200000 { t.Errorf("SizeOfImage = 0x%X, want 0x200000", mods[0].SizeOfImage) } } func TestMinidumpException(t *testing.T) { raw := buildSyntheticMinidump(t) m, err := newMinidump(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } defer m.Close() exc, err := m.Exception() if err != nil { t.Fatal(err) } if exc.ThreadID != 1337 { t.Errorf("ThreadID = %d, want 1337", exc.ThreadID) } if exc.ExceptionCode != 0xC0000005 { t.Errorf("ExceptionCode = 0x%X, want 0xC0000005", exc.ExceptionCode) } if exc.ExceptionAddress != 0x00007FFE12341234 { t.Errorf("ExceptionAddress = 0x%X, want 0x7FFE12341234", exc.ExceptionAddress) } if len(exc.Parameters) != 2 || exc.Parameters[0] != 1 || exc.Parameters[1] != 0xDEADBEEF { t.Errorf("Parameters = %v, want [1 0xDEADBEEF]", exc.Parameters) } } func TestMinidumpRejectsBadSignature(t *testing.T) { raw := buildSyntheticMinidump(t) binary.LittleEndian.PutUint32(raw[0:4], 0xDEADBEEF) if _, err := newMinidump(bytes.NewReader(raw)); err == nil { t.Error("expected an error for a bad minidump signature") } } func TestMinidumpRawStream(t *testing.T) { raw := buildSyntheticMinidump(t) m, err := newMinidump(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } defer m.Close() data, err := m.RawStream(StreamModuleList) if err != nil { t.Fatal(err) } if got := binary.LittleEndian.Uint32(data[0:4]); got != 1 { t.Errorf("RawStream(StreamModuleList) NumberOfModules = %d, want 1", got) } if _, err := m.RawStream(StreamSystemInfo); err == nil { t.Error("expected an error for an absent stream type") } }