163 lines
3.6 KiB
Go
163 lines
3.6 KiB
Go
package analysis
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
"unicode/utf16"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Inspect interprets bytes starting at `buf[0]` as many candidate
|
|
// types. It is resilient: types that don't have enough bytes simply
|
|
// return empty strings.
|
|
func Inspect(buf []byte, offset int64) map[string]any {
|
|
out := map[string]any{
|
|
"offset": offset,
|
|
"length": len(buf),
|
|
}
|
|
|
|
// 8-bit
|
|
if len(buf) >= 1 {
|
|
out["u8"] = int(buf[0])
|
|
out["i8"] = int(int8(buf[0]))
|
|
out["char"] = printable(buf[0])
|
|
out["bin8"] = fmt.Sprintf("%08b", buf[0])
|
|
}
|
|
|
|
// 16-bit
|
|
if len(buf) >= 2 {
|
|
le16 := binary.LittleEndian.Uint16(buf)
|
|
be16 := binary.BigEndian.Uint16(buf)
|
|
out["u16_le"] = int(le16)
|
|
out["u16_be"] = int(be16)
|
|
out["i16_le"] = int(int16(le16))
|
|
out["i16_be"] = int(int16(be16))
|
|
}
|
|
|
|
// 32-bit
|
|
if len(buf) >= 4 {
|
|
le32 := binary.LittleEndian.Uint32(buf)
|
|
be32 := binary.BigEndian.Uint32(buf)
|
|
out["u32_le"] = uint(le32)
|
|
out["u32_be"] = uint(be32)
|
|
out["i32_le"] = int(int32(le32))
|
|
out["i32_be"] = int(int32(be32))
|
|
out["f32_le"] = float64(math.Float32frombits(le32))
|
|
out["f32_be"] = float64(math.Float32frombits(be32))
|
|
}
|
|
|
|
// 64-bit
|
|
if len(buf) >= 8 {
|
|
le64 := binary.LittleEndian.Uint64(buf)
|
|
be64 := binary.BigEndian.Uint64(buf)
|
|
out["u64_le"] = float64(le64)
|
|
out["u64_be"] = float64(be64)
|
|
out["i64_le"] = float64(int64(le64))
|
|
out["i64_be"] = float64(int64(be64))
|
|
out["f64_le"] = math.Float64frombits(le64)
|
|
out["f64_be"] = math.Float64frombits(be64)
|
|
}
|
|
|
|
// Timestamp guesses
|
|
if len(buf) >= 4 {
|
|
le := binary.LittleEndian.Uint32(buf)
|
|
out["unix_le"] = formatUnix(int64(le))
|
|
}
|
|
if len(buf) >= 8 {
|
|
le := binary.LittleEndian.Uint64(buf)
|
|
out["filetime_le"] = formatFiletime(le)
|
|
out["webkit_le"] = formatWebkit(le)
|
|
out["oletime_le"] = formatOLEDate(math.Float64frombits(le))
|
|
}
|
|
|
|
// ASCII preview (up to 32 bytes)
|
|
n := len(buf)
|
|
if n > 32 {
|
|
n = 32
|
|
}
|
|
ascii := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
if buf[i] >= 0x20 && buf[i] <= 0x7e {
|
|
ascii[i] = buf[i]
|
|
} else {
|
|
ascii[i] = '.'
|
|
}
|
|
}
|
|
out["ascii"] = string(ascii)
|
|
|
|
// UTF-8 attempt
|
|
if utf8.Valid(buf[:n]) {
|
|
out["utf8"] = string(buf[:n])
|
|
}
|
|
|
|
// UTF-16 LE/BE preview
|
|
if len(buf) >= 2 {
|
|
lu := n / 2
|
|
if lu > 0 {
|
|
u := make([]uint16, lu)
|
|
for i := 0; i < lu; i++ {
|
|
u[i] = uint16(buf[i*2]) | uint16(buf[i*2+1])<<8
|
|
}
|
|
out["utf16le"] = string(utf16.Decode(u))
|
|
for i := 0; i < lu; i++ {
|
|
u[i] = uint16(buf[i*2])<<8 | uint16(buf[i*2+1])
|
|
}
|
|
out["utf16be"] = string(utf16.Decode(u))
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func printable(b byte) string {
|
|
if b >= 0x20 && b <= 0x7e {
|
|
return string([]byte{b})
|
|
}
|
|
return "."
|
|
}
|
|
|
|
func formatUnix(sec int64) string {
|
|
if sec < 0 || sec > 4102444800 { // past 2100
|
|
return ""
|
|
}
|
|
return time.Unix(sec, 0).UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
// Windows FILETIME epoch 1601-01-01 in 100ns ticks.
|
|
const filetimeEpochDelta = 11644473600
|
|
|
|
func formatFiletime(v uint64) string {
|
|
if v == 0 {
|
|
return ""
|
|
}
|
|
sec := int64(v/10_000_000) - filetimeEpochDelta
|
|
if sec < 0 || sec > 4102444800 {
|
|
return ""
|
|
}
|
|
return time.Unix(sec, 0).UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
// WebKit/Chrome epoch 1601-01-01 in microseconds.
|
|
func formatWebkit(v uint64) string {
|
|
if v == 0 {
|
|
return ""
|
|
}
|
|
sec := int64(v/1_000_000) - filetimeEpochDelta
|
|
if sec < 0 || sec > 4102444800 {
|
|
return ""
|
|
}
|
|
return time.Unix(sec, 0).UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
// OLE Automation date: days since 1899-12-30, as float64.
|
|
func formatOLEDate(days float64) string {
|
|
if days < 2 || days > 75000 {
|
|
return ""
|
|
}
|
|
base := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
|
|
sec := int64(days * 86400)
|
|
return base.Add(time.Duration(sec) * time.Second).Format(time.RFC3339)
|
|
}
|