// Package report formats extraction and verification results as text or JSON. package report import ( "encoding/hex" "encoding/json" "fmt" "io" "strings" "ringer/extract" "ringer/verify" ) // Report aggregates everything the tool produces for one driver. type Report struct { File string Machine string Is64 bool Entropy float64 Ioctls []extract.IoctlCode Strings []extract.StringInfo IoctlResults []verify.IoctlResult SectionResults []verify.SectionResult RuntimeDevices []string // live device names discovered from the object manager RuntimeSections []string // live shared-section names discovered from the object manager } // SharedRingBufferStrings returns the extracted strings classified as shared // ring buffer strings (section names + ring-buffer keywords), deduplicated. func (r *Report) SharedRingBufferStrings() []extract.StringInfo { seen := map[string]bool{} var out []extract.StringInfo for _, s := range r.Strings { if !extract.IsSharedRingBuffer(s) { continue } key := s.Encoding + ":" + s.Value if seen[key] { continue } seen[key] = true out = append(out, s) } return out } // DeviceStrings returns device names and symbolic links, deduplicated. func (r *Report) DeviceStrings() []extract.StringInfo { seen := map[string]bool{} var out []extract.StringInfo for _, s := range r.Strings { if s.Category != "device" && s.Category != "symlink" { continue } key := s.Encoding + ":" + s.Value if seen[key] { continue } seen[key] = true out = append(out, s) } return out } // WriteText renders a human-readable report. func (r *Report) WriteText(w io.Writer) { fmt.Fprintf(w, "Ringer - driver IOCTL & shared ring buffer extractor\n") fmt.Fprintf(w, "====================================================\n") fmt.Fprintf(w, "File: %s\n", r.File) fmt.Fprintf(w, "Machine: %s\n", r.Machine) packed := "" if r.Entropy > 7.2 { packed = " [WARNING: high entropy, may be packed/encrypted - results unreliable]" } fmt.Fprintf(w, "Entropy: %.2f bits/byte%s\n", r.Entropy, packed) fmt.Fprintf(w, "\n--- IOCTL Codes (%d found) ---\n", len(r.Ioctls)) if len(r.Ioctls) == 0 { fmt.Fprintf(w, " (none)\n") } else { fmt.Fprintf(w, "%-12s %-24s %-6s %-18s %-28s %-6s %-8s %s\n", "Code", "DeviceType", "Func", "Method", "Access", "Conf", "Source", "RVA") for _, c := range r.Ioctls { dt := extract.DeviceTypeName(c.DeviceType) if dt == "" { dt = fmt.Sprintf("0x%04X", c.DeviceType) } note := "" if c.Note != "" { note = " [" + c.Note + "]" } fmt.Fprintf(w, "0x%08X %-24s 0x%03X %-18s %-28s %-6s %-8s 0x%X%s\n", c.Code, dt, c.Function, extract.MethodName(c.Method), extract.AccessName(c.Access), c.Confidence, c.Source, c.RVA, note) } } rb := r.SharedRingBufferStrings() fmt.Fprintf(w, "\n--- Shared Ring Buffer Strings (%d found) ---\n", len(rb)) if len(rb) == 0 { fmt.Fprintf(w, " (none)\n") } else { for _, s := range rb { fmt.Fprintf(w, " [%-10s] %-40s (%s @ 0x%X)\n", s.Category, s.Value, s.Encoding, s.Offset) } } dev := r.DeviceStrings() fmt.Fprintf(w, "\n--- Device Names / Symbolic Links (%d found) ---\n", len(dev)) if len(dev) == 0 { fmt.Fprintf(w, " (none)\n") } else { for _, s := range dev { fmt.Fprintf(w, " [%-8s] %s (0x%X)\n", s.Category, s.Value, s.Offset) } } if len(r.RuntimeDevices) > 0 || len(r.RuntimeSections) > 0 { fmt.Fprintf(w, "\n--- Runtime Discovery (live object manager) ---\n") if len(r.RuntimeDevices) > 0 { fmt.Fprintf(w, " Devices (%d):\n", len(r.RuntimeDevices)) for _, d := range r.RuntimeDevices { fmt.Fprintf(w, " %s\n", d) } } if len(r.RuntimeSections) > 0 { fmt.Fprintf(w, " Shared sections (%d):\n", len(r.RuntimeSections)) for _, s := range r.RuntimeSections { fmt.Fprintf(w, " %s\n", s) } } } if len(r.IoctlResults) > 0 { fmt.Fprintf(w, "\n--- Live Verification: IOCTL ---\n") writeIoctlResults(w, r.IoctlResults) } if len(r.SectionResults) > 0 { fmt.Fprintf(w, "\n--- Live Verification: Shared Sections ---\n") writeSectionResults(w, r.SectionResults) } } func writeIoctlResults(w io.Writer, results []verify.IoctlResult) { cur := "" for _, res := range results { if res.Device != cur { cur = res.Device fmt.Fprintf(w, "Device %s:\n", cur) } if res.Code == 0 { // Device-open failure placeholder. fmt.Fprintf(w, " open failed: %s (%d)\n", res.Error, res.ErrorCode) continue } if res.TimedOut { fmt.Fprintf(w, " 0x%08X TIMEOUT\n", res.Code) continue } if res.Success { fmt.Fprintf(w, " 0x%08X SUCCESS (%d bytes)\n", res.Code, res.BytesReturned) if len(res.Output) > 0 { fmt.Fprintf(w, " %s\n", hexDump(res.Output, 64)) } } else { fmt.Fprintf(w, " 0x%08X %s (%d)\n", res.Code, res.Error, res.ErrorCode) } } } func writeSectionResults(w io.Writer, results []verify.SectionResult) { for _, res := range results { if !res.Success { fmt.Fprintf(w, "%s: open/map failed: %s\n", res.Name, res.Error) continue } fmt.Fprintf(w, "%s: mapped %d bytes, first dword (possible write index) = %d\n", res.Name, res.RegionSize, res.FirstDword) if len(res.Data) > 0 { fmt.Fprintf(w, "%s\n", hexDump(res.Data, 256)) } } } // hexDump renders up to maxLen bytes as offset + hex + ASCII. func hexDump(b []byte, maxLen int) string { if len(b) > maxLen { b = b[:maxLen] } var sb strings.Builder for off := 0; off < len(b); off += 16 { end := off + 16 if end > len(b) { end = len(b) } row := b[off:end] sb.WriteString(fmt.Sprintf(" %08X ", off)) hexPart := make([]string, 0, 16) asciiPart := make([]byte, 0, 16) for _, c := range row { hexPart = append(hexPart, fmt.Sprintf("%02X", c)) if c >= 0x20 && c <= 0x7E { asciiPart = append(asciiPart, c) } else { asciiPart = append(asciiPart, '.') } } sb.WriteString(strings.Join(hexPart, " ")) if len(row) < 16 { sb.WriteString(strings.Repeat(" ", 16-len(row))) } sb.WriteString(" |") sb.Write(asciiPart) sb.WriteString("|\n") } return strings.TrimRight(sb.String(), "\n") } // WriteJSON renders a machine-readable report. func (r *Report) WriteJSON(w io.Writer) error { type ioctlJSON struct { Code uint32 `json:"code"` DeviceType uint16 `json:"device_type"` DeviceTypeName string `json:"device_type_name"` Function uint16 `json:"function"` Method uint8 `json:"method"` MethodName string `json:"method_name"` Access uint8 `json:"access"` AccessName string `json:"access_name"` Confidence string `json:"confidence"` Source string `json:"source"` RVA uint32 `json:"rva"` Note string `json:"note,omitempty"` } type stringJSON struct { Value string `json:"value"` Encoding string `json:"encoding"` Offset uint32 `json:"offset"` Category string `json:"category"` } type ioctlResultJSON struct { Code uint32 `json:"code"` Device string `json:"device"` Success bool `json:"success"` ErrorCode uint32 `json:"error_code,omitempty"` Error string `json:"error,omitempty"` BytesReturned uint32 `json:"bytes_returned,omitempty"` Output string `json:"output_hex,omitempty"` TimedOut bool `json:"timed_out,omitempty"` } type sectionResultJSON struct { Name string `json:"name"` Success bool `json:"success"` Error string `json:"error,omitempty"` RegionSize uint64 `json:"region_size,omitempty"` FirstDword uint32 `json:"first_dword,omitempty"` DataHex string `json:"data_hex,omitempty"` } out := struct { File string `json:"file"` Machine string `json:"machine"` Is64 bool `json:"is_64bit"` Entropy float64 `json:"entropy"` Ioctls []ioctlJSON `json:"ioctls"` RingBuffer []stringJSON `json:"shared_ring_buffer_strings"` Devices []stringJSON `json:"device_strings"` RuntimeDevices []string `json:"runtime_devices"` RuntimeSections []string `json:"runtime_sections"` IoctlResults []ioctlResultJSON `json:"ioctl_verification"` SectionResults []sectionResultJSON `json:"section_verification"` }{ File: r.File, Machine: r.Machine, Is64: r.Is64, Entropy: r.Entropy, RuntimeDevices: r.RuntimeDevices, RuntimeSections: r.RuntimeSections, } for _, c := range r.Ioctls { dt := extract.DeviceTypeName(c.DeviceType) if dt == "" { dt = fmt.Sprintf("0x%04X", c.DeviceType) } out.Ioctls = append(out.Ioctls, ioctlJSON{ Code: c.Code, DeviceType: c.DeviceType, DeviceTypeName: dt, Function: c.Function, Method: c.Method, MethodName: extract.MethodName(c.Method), Access: c.Access, AccessName: extract.AccessName(c.Access), Confidence: c.Confidence, Source: c.Source, RVA: c.RVA, Note: c.Note, }) } for _, s := range r.SharedRingBufferStrings() { out.RingBuffer = append(out.RingBuffer, stringJSON{s.Value, s.Encoding, s.Offset, s.Category}) } for _, s := range r.DeviceStrings() { out.Devices = append(out.Devices, stringJSON{s.Value, s.Encoding, s.Offset, s.Category}) } for _, res := range r.IoctlResults { out.IoctlResults = append(out.IoctlResults, ioctlResultJSON{ Code: res.Code, Device: res.Device, Success: res.Success, ErrorCode: res.ErrorCode, Error: res.Error, BytesReturned: res.BytesReturned, Output: hex.EncodeToString(res.Output), TimedOut: res.TimedOut, }) } for _, res := range r.SectionResults { out.SectionResults = append(out.SectionResults, sectionResultJSON{ Name: res.Name, Success: res.Success, Error: res.Error, RegionSize: uint64(res.RegionSize), FirstDword: res.FirstDword, DataHex: hex.EncodeToString(res.Data), }) } enc := json.NewEncoder(w) enc.SetIndent("", " ") return enc.Encode(out) }