mirror of
https://github.com/NomanNasirMinhas/Ringer
synced 2026-08-19 05:01:13 +00:00
140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
package extract
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf16"
|
|
|
|
"ringer/pefile"
|
|
)
|
|
|
|
// StringInfo is a single extracted string with its encoding, file offset, and
|
|
// semantic category.
|
|
type StringInfo struct {
|
|
Value string
|
|
Encoding string // "ascii", "utf16le", or "utf16be"
|
|
Offset uint32 // file offset of the first byte
|
|
Category string // "device", "symlink", "section", "ringbuffer", or "other"
|
|
}
|
|
|
|
// ExtractStrings scans the whole file for ASCII and UTF-16 strings of at least
|
|
// minLen characters and classifies each one.
|
|
func ExtractStrings(f *pefile.File, minLen int) []StringInfo {
|
|
if minLen < 1 {
|
|
minLen = 1
|
|
}
|
|
var out []StringInfo
|
|
out = append(out, extractASCII(f.Raw, minLen)...)
|
|
out = append(out, extractUTF16(f.Raw, minLen)...)
|
|
for i := range out {
|
|
out[i].Category = classify(out[i].Value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// IsSharedRingBuffer reports whether a string is a "Shared Ring Buffer" string:
|
|
// a named-object section name or a ring-buffer/shared-memory keyword.
|
|
func IsSharedRingBuffer(s StringInfo) bool {
|
|
return s.Category == "section" || s.Category == "ringbuffer"
|
|
}
|
|
|
|
// extractASCII finds runs of printable ASCII (0x20-0x7E).
|
|
func extractASCII(data []byte, minLen int) []StringInfo {
|
|
var out []StringInfo
|
|
var cur []byte
|
|
var start int
|
|
flush := func(end int) {
|
|
if len(cur) >= minLen {
|
|
out = append(out, StringInfo{Value: string(cur), Encoding: "ascii", Offset: uint32(start)})
|
|
}
|
|
cur = nil
|
|
}
|
|
for i := 0; i < len(data); i++ {
|
|
b := data[i]
|
|
if b >= 0x20 && b <= 0x7E {
|
|
if cur == nil {
|
|
start = i
|
|
}
|
|
cur = append(cur, b)
|
|
} else {
|
|
flush(i)
|
|
}
|
|
}
|
|
flush(len(data))
|
|
return out
|
|
}
|
|
|
|
// extractUTF16 finds runs of printable UTF-16LE code units (ASCII range only,
|
|
// to avoid CJK false positives). Windows drivers are overwhelmingly UTF-16LE;
|
|
// UTF-16BE is not scanned because it is essentially nonexistent in PE images
|
|
// and produces misaligned false positives.
|
|
func extractUTF16(data []byte, minLen int) []StringInfo {
|
|
var out []StringInfo
|
|
var cur []uint16
|
|
var start int
|
|
flush := func() {
|
|
if len(cur) >= minLen {
|
|
out = append(out, StringInfo{Value: string(utf16.Decode(cur)), Encoding: "utf16le", Offset: uint32(start)})
|
|
}
|
|
cur = nil
|
|
}
|
|
for i := 0; i+1 < len(data); {
|
|
u := uint16(data[i]) | uint16(data[i+1])<<8
|
|
if u >= 0x20 && u <= 0x7E {
|
|
if cur == nil {
|
|
start = i
|
|
}
|
|
cur = append(cur, u)
|
|
i += 2
|
|
} else {
|
|
flush()
|
|
i++
|
|
}
|
|
}
|
|
flush()
|
|
return out
|
|
}
|
|
|
|
// classify assigns a semantic category to a string.
|
|
func classify(s string) string {
|
|
// A printf-style format string (e.g. "\??\%ls", "\Device\%ls") is a template
|
|
// the driver fills in at runtime, not a literal device/symlink/section name.
|
|
// Reporting it as a concrete name would be wrong, so it is left as "other".
|
|
if isFormatString(s) {
|
|
return "other"
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(s, `\Device\`):
|
|
return "device"
|
|
case strings.HasPrefix(s, `\DosDevices\`), strings.HasPrefix(s, `\??\`), strings.HasPrefix(s, `\\.\`):
|
|
return "symlink"
|
|
case strings.HasPrefix(s, `\BaseNamedObjects\`), strings.HasPrefix(s, `\REGISTRY\`),
|
|
strings.HasPrefix(s, `Global\`), strings.HasPrefix(s, `\KnownDlls\`):
|
|
return "section"
|
|
}
|
|
lower := strings.ToLower(s)
|
|
// Match ring-buffer / shared-memory phrases specifically. "ring" alone
|
|
// would false-positive on "string", and "buffer"/"log"/"map" are too broad.
|
|
for _, kw := range []string{"ring buffer", "ringbuffer", "ring_buffer", "ringbuf", "circular", "shared"} {
|
|
if strings.Contains(lower, kw) {
|
|
return "ringbuffer"
|
|
}
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
// isFormatString reports whether s contains a printf-style format specifier
|
|
// (e.g. "%ls", "%S", "%d", "%08X"). "%%" is a literal percent and is ignored.
|
|
func isFormatString(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] != '%' {
|
|
continue
|
|
}
|
|
if i+1 < len(s) && s[i+1] == '%' {
|
|
i++
|
|
continue
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|