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

160 lines
5.3 KiB
Go

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")
}
}