package verify import ( "encoding/binary" "strings" "unsafe" "golang.org/x/sys/windows" "ringer/extract" ) var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") procOpenFileMapping = modkernel32.NewProc("OpenFileMappingW") ) // openFileMapping opens an existing named file mapping. x/sys/windows has no // OpenFileMapping wrapper, so this calls the API directly. Unlike // CreateFileMapping(InvalidHandle, ...), it does not create a new section when // the name is absent — it returns ERROR_FILE_NOT_FOUND instead. func openFileMapping(access uint32, inheritHandle bool, name *uint16) (windows.Handle, error) { var inherit uintptr if inheritHandle { inherit = 1 } r0, _, lastErr := procOpenFileMapping.Call(uintptr(access), inherit, uintptr(unsafe.Pointer(name))) h := windows.Handle(r0) if h == 0 { return 0, lastErr } return h, nil } // SectionResult is the outcome of mapping and reading a shared-memory section. type SectionResult struct { Name string Success bool Error string RegionSize uintptr Data []byte // first N bytes of the mapped region (raw) FirstDword uint32 // first 4 bytes, often a ring-buffer write index } // SectionCandidates derives candidate user-mode section names from extracted // strings. Kernel section names (\BaseNamedObjects\X) map to Global\X in the // user-mode namespace; Global\X is also tried as-is. func SectionCandidates(strs []extract.StringInfo) []string { seen := map[string]bool{} var out []string add := func(p string) { if p == "" || seen[p] { return } seen[p] = true out = append(out, p) } for _, s := range strs { switch s.Category { case "section": if strings.HasPrefix(s.Value, `\BaseNamedObjects\`) { name := strings.TrimPrefix(s.Value, `\BaseNamedObjects\`) add(`Global\` + name) add(name) } else if strings.HasPrefix(s.Value, `Global\`) { add(s.Value) } } } return out } // VerifySections maps each candidate section and reads its contents. dumpSize // caps how many bytes are copied back for display. func VerifySections(names []string, dumpSize int) []SectionResult { if dumpSize <= 0 { dumpSize = 4096 } var results []SectionResult for _, name := range names { results = append(results, mapSection(name, dumpSize)) } return results } // mapSection opens a file mapping by name, maps it into the process, and reads // the first dumpSize bytes. // //go:nocheckptr func mapSection(name string, dumpSize int) SectionResult { res := SectionResult{Name: name} p, err := windows.UTF16PtrFromString(name) if err != nil { res.Error = err.Error() return res } h, err := openFileMapping(windows.FILE_MAP_READ, false, p) if err != nil { res.Error = err.Error() return res } defer windows.CloseHandle(h) addr, err := windows.MapViewOfFile(h, windows.FILE_MAP_READ, 0, 0, 0) if err != nil { res.Error = err.Error() return res } defer windows.UnmapViewOfFile(addr) var mbi windows.MemoryBasicInformation if err := windows.VirtualQuery(addr, &mbi, unsafe.Sizeof(mbi)); err != nil { res.Error = err.Error() return res } res.RegionSize = mbi.RegionSize read := mbi.RegionSize if read > uintptr(dumpSize) { read = uintptr(dumpSize) } // addr is a memory-mapped region, not a GC-managed object, so the // uintptr->pointer conversion is safe here (go vet flags this as a false // positive). The bytes are copied out immediately so the mapping can be // released. view := unsafe.Slice((*byte)(unsafe.Pointer(addr)), int(read)) res.Data = make([]byte, len(view)) copy(res.Data, view) if len(res.Data) >= 4 { res.FirstDword = binary.LittleEndian.Uint32(res.Data[:4]) } res.Success = true return res }