160 lines
5.3 KiB
Go
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")
|
|
}
|
|
}
|