mirror of
https://github.com/NomanNasirMinhas/Ringer
synced 2026-08-19 05:01:13 +00:00
335 lines
10 KiB
Go
335 lines
10 KiB
Go
// 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)
|
|
}
|