2183 lines
65 KiB
Go
2183 lines
65 KiB
Go
// Package parsers provides lightweight structure parsers for common
|
|
// binary formats used in Leviathan's structures / templates panel.
|
|
// Each parser returns a tree of Field values anchored at absolute
|
|
// offsets into the document.
|
|
package parsers
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf16"
|
|
"unicode/utf8"
|
|
|
|
"github.com/andydixon/nexus/internal/leviathan/document"
|
|
)
|
|
|
|
// Field is one node of the parsed structure.
|
|
type Field struct {
|
|
Name string
|
|
Type string
|
|
Offset int64
|
|
Length int64
|
|
Value string
|
|
Note string
|
|
Children []*Field
|
|
}
|
|
|
|
// ToMap serialises a Field tree to JS-friendly maps.
|
|
func (f *Field) ToMap() map[string]any {
|
|
m := map[string]any{
|
|
"name": f.Name,
|
|
"type": f.Type,
|
|
"offset": f.Offset,
|
|
"length": f.Length,
|
|
"value": f.Value,
|
|
}
|
|
if f.Note != "" {
|
|
m["note"] = f.Note
|
|
}
|
|
if len(f.Children) > 0 {
|
|
kids := make([]map[string]any, len(f.Children))
|
|
for i, c := range f.Children {
|
|
kids[i] = c.ToMap()
|
|
}
|
|
m["children"] = kids
|
|
}
|
|
return m
|
|
}
|
|
|
|
// DetectAndParse picks the first matching parser for the document.
|
|
// It returns the parser name used and the tree.
|
|
func DetectAndParse(d *document.Document) (string, *Field, error) {
|
|
// Read enough to cover the tar ustar magic at offset 257.
|
|
head, err := d.Range(0, min64(265, d.Size()))
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
switch {
|
|
case len(head) >= 8 && string(head[:8]) == "\x89PNG\r\n\x1a\n":
|
|
f, err := ParsePNG(d)
|
|
return "png", f, err
|
|
case len(head) >= 3 && head[0] == 0xff && head[1] == 0xd8 && head[2] == 0xff:
|
|
f, err := ParseJPEG(d)
|
|
return "jpeg", f, err
|
|
case len(head) >= 6 && (string(head[:6]) == "GIF87a" || string(head[:6]) == "GIF89a"):
|
|
f, err := ParseGIF(d)
|
|
return "gif", f, err
|
|
case len(head) >= 2 && string(head[:2]) == "BM":
|
|
f, err := ParseBMP(d)
|
|
return "bmp", f, err
|
|
case len(head) >= 4 && string(head[:4]) == "PK\x03\x04":
|
|
f, err := ParseZIP(d)
|
|
return "zip", f, err
|
|
case len(head) >= 4 && string(head[:4]) == "\x7fELF":
|
|
f, err := ParseELF(d)
|
|
return "elf", f, err
|
|
case len(head) >= 2 && string(head[:2]) == "MZ":
|
|
f, err := ParsePE(d)
|
|
return "pe", f, err
|
|
case len(head) >= 4 && string(head[:4]) == "RIFF":
|
|
f, err := ParseRIFF(d)
|
|
return "riff", f, err
|
|
case len(head) >= 16 && string(head[:16]) == "SQLite format 3\x00":
|
|
f, err := ParseSQLite(d)
|
|
return "sqlite", f, err
|
|
case len(head) >= 8 && string(head[:8]) == string(cfbMagic):
|
|
f, err := ParseCFB(d)
|
|
return "cfb", f, err
|
|
case len(head) >= 3 && head[0] == 0x1f && head[1] == 0x8b && head[2] == 8:
|
|
f, err := ParseGZIP(d)
|
|
return "gzip", f, err
|
|
case len(head) >= 4 && string(head[:4]) == "\x0a\x0d\x0d\x0a":
|
|
f, err := ParsePCAPNG(d)
|
|
return "pcapng", f, err
|
|
case len(head) >= 4 && isPCAPMagic(head):
|
|
f, err := ParsePCAP(d)
|
|
return "pcap", f, err
|
|
case len(head) >= 4 && (string(head[:4]) == "II\x2a\x00" || string(head[:4]) == "MM\x00\x2a"):
|
|
f, err := ParseTIFF(d)
|
|
return "tiff", f, err
|
|
case len(head) >= 265 && (string(head[257:262]) == "ustar"):
|
|
f, err := ParseTAR(d)
|
|
return "tar", f, err
|
|
}
|
|
// Mach-O (including fat/universal) — detection needs more than 8 bytes.
|
|
if IsMachO(head, d.Size()) {
|
|
f, err := ParseMachO(d)
|
|
return "macho", f, err
|
|
}
|
|
return "", nil, errors.New("no parser matched")
|
|
}
|
|
|
|
func isPCAPMagic(head []byte) bool {
|
|
m := binary.LittleEndian.Uint32(head[:4])
|
|
return m == 0xa1b2c3d4 || m == 0xd4c3b2a1 || m == 0xa1b23c4d || m == 0x4d3cb2a1
|
|
}
|
|
|
|
// ParsePNG walks chunks sequentially and returns a Field tree.
|
|
func ParsePNG(d *document.Document) (*Field, error) {
|
|
root := &Field{Name: "PNG", Type: "container", Offset: 0}
|
|
hdr, err := d.Range(0, 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
root.Children = append(root.Children, &Field{
|
|
Name: "signature", Type: "bytes", Offset: 0, Length: 8,
|
|
Value: fmt.Sprintf("% x", hdr),
|
|
})
|
|
off := int64(8)
|
|
size := d.Size()
|
|
for off+8 <= size {
|
|
hdr, err := d.Range(off, 8)
|
|
if err != nil {
|
|
return root, err
|
|
}
|
|
ln := int64(binary.BigEndian.Uint32(hdr[0:4]))
|
|
typ := string(hdr[4:8])
|
|
chunk := &Field{
|
|
Name: typ + "@" + fmt.Sprintf("0x%x", off),
|
|
Type: "chunk",
|
|
Offset: off,
|
|
Length: 12 + ln,
|
|
Value: fmt.Sprintf("length=%d", ln),
|
|
}
|
|
chunk.Children = []*Field{
|
|
{Name: "length", Type: "u32be", Offset: off, Length: 4, Value: fmt.Sprintf("%d", ln)},
|
|
{Name: "type", Type: "ascii", Offset: off + 4, Length: 4, Value: typ},
|
|
{Name: "data", Type: "bytes", Offset: off + 8, Length: ln, Value: fmt.Sprintf("%d bytes", ln)},
|
|
{Name: "crc", Type: "u32be", Offset: off + 8 + ln, Length: 4, Value: "crc32"},
|
|
}
|
|
root.Children = append(root.Children, chunk)
|
|
off += 12 + ln
|
|
if typ == "IEND" {
|
|
break
|
|
}
|
|
}
|
|
root.Length = off
|
|
return root, nil
|
|
}
|
|
|
|
// ParseJPEG walks marker segments.
|
|
func ParseJPEG(d *document.Document) (*Field, error) {
|
|
root := &Field{Name: "JPEG", Type: "container", Offset: 0}
|
|
off := int64(0)
|
|
size := d.Size()
|
|
for off < size {
|
|
b, err := d.Range(off, 2)
|
|
if err != nil || len(b) < 2 {
|
|
break
|
|
}
|
|
if b[0] != 0xff {
|
|
return root, errors.New("lost sync")
|
|
}
|
|
marker := b[1]
|
|
mark := &Field{
|
|
Name: jpegMarkerName(marker),
|
|
Type: "marker",
|
|
Offset: off,
|
|
Value: fmt.Sprintf("0x%02x", marker),
|
|
}
|
|
off += 2
|
|
if marker == 0xd8 || marker == 0xd9 || marker == 0x00 || (marker >= 0xd0 && marker <= 0xd7) {
|
|
mark.Length = 2
|
|
root.Children = append(root.Children, mark)
|
|
if marker == 0xd9 {
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
lb, err := d.Range(off, 2)
|
|
if err != nil || len(lb) < 2 {
|
|
break
|
|
}
|
|
ln := int64(binary.BigEndian.Uint16(lb))
|
|
mark.Length = 2 + ln
|
|
mark.Children = []*Field{
|
|
{Name: "length", Type: "u16be", Offset: off, Length: 2, Value: fmt.Sprintf("%d", ln)},
|
|
{Name: "data", Type: "bytes", Offset: off + 2, Length: ln - 2, Value: fmt.Sprintf("%d bytes", ln-2)},
|
|
}
|
|
root.Children = append(root.Children, mark)
|
|
off += ln
|
|
if marker == 0xda { // SOS
|
|
// scan until next marker
|
|
for off < size {
|
|
bb, err := d.Range(off, 2)
|
|
if err != nil || len(bb) < 2 {
|
|
break
|
|
}
|
|
if bb[0] == 0xff && bb[1] != 0x00 && !(bb[1] >= 0xd0 && bb[1] <= 0xd7) {
|
|
break
|
|
}
|
|
off++
|
|
}
|
|
}
|
|
}
|
|
root.Length = off
|
|
return root, nil
|
|
}
|
|
|
|
func jpegMarkerName(m byte) string {
|
|
switch m {
|
|
case 0xd8:
|
|
return "SOI"
|
|
case 0xd9:
|
|
return "EOI"
|
|
case 0xda:
|
|
return "SOS"
|
|
case 0xdb:
|
|
return "DQT"
|
|
case 0xc4:
|
|
return "DHT"
|
|
case 0xe0:
|
|
return "APP0"
|
|
case 0xe1:
|
|
return "APP1"
|
|
case 0xfe:
|
|
return "COM"
|
|
}
|
|
if m >= 0xc0 && m <= 0xcf {
|
|
return fmt.Sprintf("SOF%d", int(m-0xc0))
|
|
}
|
|
if m >= 0xe0 && m <= 0xef {
|
|
return fmt.Sprintf("APP%d", int(m-0xe0))
|
|
}
|
|
return fmt.Sprintf("MRK%02x", m)
|
|
}
|
|
|
|
// ParseGIF walks the logical screen descriptor, global colour table,
|
|
// extension blocks, image descriptors and trailer.
|
|
func ParseGIF(d *document.Document) (*Field, error) {
|
|
size := d.Size()
|
|
if size < 13 {
|
|
return nil, errors.New("gif too short")
|
|
}
|
|
hdr, err := d.Range(0, 13)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
version := string(hdr[0:6])
|
|
if version != "GIF87a" && version != "GIF89a" {
|
|
return nil, errors.New("bad gif signature")
|
|
}
|
|
width := binary.LittleEndian.Uint16(hdr[6:8])
|
|
height := binary.LittleEndian.Uint16(hdr[8:10])
|
|
packed := hdr[10]
|
|
gctFlag := packed&0x80 != 0
|
|
colorRes := ((packed >> 4) & 0x07) + 1
|
|
sortFlag := packed&0x08 != 0
|
|
gctEntries := 0
|
|
if gctFlag {
|
|
gctEntries = 1 << ((packed & 0x07) + 1)
|
|
}
|
|
root := &Field{
|
|
Name: "GIF",
|
|
Type: "container",
|
|
Offset: 0,
|
|
Value: fmt.Sprintf("%s %dx%d", version, width, height),
|
|
Children: []*Field{
|
|
{Name: "signature", Type: "ascii", Offset: 0, Length: 6, Value: version},
|
|
{Name: "width", Type: "u16le", Offset: 6, Length: 2, Value: fmt.Sprintf("%d", width)},
|
|
{Name: "height", Type: "u16le", Offset: 8, Length: 2, Value: fmt.Sprintf("%d", height)},
|
|
{Name: "packed", Type: "u8", Offset: 10, Length: 1, Value: fmt.Sprintf("gct=%v colorRes=%d sort=%v entries=%d", gctFlag, colorRes, sortFlag, gctEntries)},
|
|
{Name: "backgroundColorIndex", Type: "u8", Offset: 11, Length: 1, Value: fmt.Sprintf("%d", hdr[11])},
|
|
{Name: "pixelAspectRatio", Type: "u8", Offset: 12, Length: 1, Value: fmt.Sprintf("%d", hdr[12])},
|
|
},
|
|
}
|
|
off := int64(13)
|
|
if gctEntries > 0 {
|
|
ln := int64(gctEntries * 3)
|
|
if off+ln > size {
|
|
return root, errors.New("gif global colour table exceeds file")
|
|
}
|
|
root.Children = append(root.Children, &Field{Name: "globalColorTable", Type: "palette", Offset: off, Length: ln, Value: fmt.Sprintf("%d RGB entries", gctEntries)})
|
|
off += ln
|
|
}
|
|
for off < size {
|
|
tag, err := d.Range(off, 1)
|
|
if err != nil || len(tag) < 1 {
|
|
break
|
|
}
|
|
switch tag[0] {
|
|
case 0x3b:
|
|
root.Children = append(root.Children, &Field{Name: "trailer", Type: "byte", Offset: off, Length: 1, Value: "0x3b"})
|
|
off++
|
|
root.Length = off
|
|
return root, nil
|
|
case 0x21:
|
|
block, next, err := parseGIFExtension(d, off, size)
|
|
root.Children = append(root.Children, block)
|
|
if err != nil {
|
|
root.Length = next
|
|
return root, err
|
|
}
|
|
off = next
|
|
case 0x2c:
|
|
block, next, err := parseGIFImage(d, off, size)
|
|
root.Children = append(root.Children, block)
|
|
if err != nil {
|
|
root.Length = next
|
|
return root, err
|
|
}
|
|
off = next
|
|
default:
|
|
root.Children = append(root.Children, &Field{Name: "unknown", Type: "byte", Offset: off, Length: 1, Value: fmt.Sprintf("0x%02x", tag[0])})
|
|
root.Length = off + 1
|
|
return root, fmt.Errorf("unknown gif block 0x%02x at 0x%x", tag[0], off)
|
|
}
|
|
}
|
|
root.Length = off
|
|
return root, nil
|
|
}
|
|
|
|
func parseGIFExtension(d *document.Document, off, size int64) (*Field, int64, error) {
|
|
hdr, err := d.Range(off, 2)
|
|
if err != nil || len(hdr) < 2 {
|
|
return &Field{Name: "extension", Type: "block", Offset: off}, off, errors.New("short gif extension")
|
|
}
|
|
label := hdr[1]
|
|
name := gifExtensionName(label)
|
|
next, subBlocks, err := skipGIFSubBlocks(d, off+2, size)
|
|
f := &Field{
|
|
Name: name,
|
|
Type: "extension",
|
|
Offset: off,
|
|
Length: next - off,
|
|
Value: fmt.Sprintf("label=0x%02x subBlocks=%d", label, subBlocks),
|
|
Children: []*Field{
|
|
{Name: "introducer", Type: "u8", Offset: off, Length: 1, Value: "0x21"},
|
|
{Name: "label", Type: "u8", Offset: off + 1, Length: 1, Value: fmt.Sprintf("0x%02x", label)},
|
|
},
|
|
}
|
|
return f, next, err
|
|
}
|
|
|
|
func parseGIFImage(d *document.Document, off, size int64) (*Field, int64, error) {
|
|
hdr, err := d.Range(off, 10)
|
|
if err != nil || len(hdr) < 10 {
|
|
return &Field{Name: "image", Type: "image", Offset: off}, off, errors.New("short gif image descriptor")
|
|
}
|
|
left := binary.LittleEndian.Uint16(hdr[1:3])
|
|
top := binary.LittleEndian.Uint16(hdr[3:5])
|
|
width := binary.LittleEndian.Uint16(hdr[5:7])
|
|
height := binary.LittleEndian.Uint16(hdr[7:9])
|
|
packed := hdr[9]
|
|
lctEntries := 0
|
|
if packed&0x80 != 0 {
|
|
lctEntries = 1 << ((packed & 0x07) + 1)
|
|
}
|
|
next := off + 10 + int64(lctEntries*3)
|
|
if next >= size {
|
|
return nil, next, errors.New("gif image data missing")
|
|
}
|
|
lzw, err := d.Range(next, 1)
|
|
if err != nil || len(lzw) < 1 {
|
|
return nil, next, errors.New("gif lzw minimum code size missing")
|
|
}
|
|
dataStart := next + 1
|
|
next, subBlocks, err := skipGIFSubBlocks(d, dataStart, size)
|
|
f := &Field{
|
|
Name: fmt.Sprintf("image@0x%x", off),
|
|
Type: "image",
|
|
Offset: off,
|
|
Length: next - off,
|
|
Value: fmt.Sprintf("%dx%d at %d,%d subBlocks=%d", width, height, left, top, subBlocks),
|
|
Children: []*Field{
|
|
{Name: "left", Type: "u16le", Offset: off + 1, Length: 2, Value: fmt.Sprintf("%d", left)},
|
|
{Name: "top", Type: "u16le", Offset: off + 3, Length: 2, Value: fmt.Sprintf("%d", top)},
|
|
{Name: "width", Type: "u16le", Offset: off + 5, Length: 2, Value: fmt.Sprintf("%d", width)},
|
|
{Name: "height", Type: "u16le", Offset: off + 7, Length: 2, Value: fmt.Sprintf("%d", height)},
|
|
{Name: "packed", Type: "u8", Offset: off + 9, Length: 1, Value: fmt.Sprintf("lctEntries=%d interlace=%v", lctEntries, packed&0x40 != 0)},
|
|
{Name: "lzwMinimumCodeSize", Type: "u8", Offset: dataStart - 1, Length: 1, Value: fmt.Sprintf("%d", lzw[0])},
|
|
{Name: "imageData", Type: "subblocks", Offset: dataStart, Length: next - dataStart, Value: fmt.Sprintf("%d sub-blocks", subBlocks)},
|
|
},
|
|
}
|
|
if lctEntries > 0 {
|
|
f.Children = append(f.Children, &Field{Name: "localColorTable", Type: "palette", Offset: off + 10, Length: int64(lctEntries * 3), Value: fmt.Sprintf("%d RGB entries", lctEntries)})
|
|
}
|
|
return f, next, err
|
|
}
|
|
|
|
func skipGIFSubBlocks(d *document.Document, off, size int64) (int64, int, error) {
|
|
count := 0
|
|
for off < size {
|
|
b, err := d.Range(off, 1)
|
|
if err != nil || len(b) < 1 {
|
|
return off, count, errors.New("short gif sub-block")
|
|
}
|
|
off++
|
|
ln := int64(b[0])
|
|
if ln == 0 {
|
|
return off, count, nil
|
|
}
|
|
if off+ln > size {
|
|
return off, count, errors.New("gif sub-block exceeds file")
|
|
}
|
|
off += ln
|
|
count++
|
|
}
|
|
return off, count, errors.New("unterminated gif sub-block chain")
|
|
}
|
|
|
|
func gifExtensionName(label byte) string {
|
|
switch label {
|
|
case 0xf9:
|
|
return "graphicControlExtension"
|
|
case 0xfe:
|
|
return "commentExtension"
|
|
case 0xff:
|
|
return "applicationExtension"
|
|
case 0x01:
|
|
return "plainTextExtension"
|
|
}
|
|
return fmt.Sprintf("extension%02x", label)
|
|
}
|
|
|
|
// ParseBMP reads the BMP file header and DIB header metadata.
|
|
func ParseBMP(d *document.Document) (*Field, error) {
|
|
size := d.Size()
|
|
if size < 26 {
|
|
return nil, errors.New("bmp too short")
|
|
}
|
|
hdr, err := d.Range(0, min64(size, 138))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(hdr) < 26 || string(hdr[0:2]) != "BM" {
|
|
return nil, errors.New("bad bmp signature")
|
|
}
|
|
fileSize := binary.LittleEndian.Uint32(hdr[2:6])
|
|
pixelOff := binary.LittleEndian.Uint32(hdr[10:14])
|
|
dibSize := binary.LittleEndian.Uint32(hdr[14:18])
|
|
root := &Field{
|
|
Name: "BMP",
|
|
Type: "container",
|
|
Offset: 0,
|
|
Length: int64(fileSize),
|
|
Value: fmt.Sprintf("fileSize=%d pixelOffset=0x%x dib=%s", fileSize, pixelOff, bmpDIBName(dibSize)),
|
|
Children: []*Field{
|
|
{Name: "signature", Type: "ascii", Offset: 0, Length: 2, Value: "BM"},
|
|
{Name: "fileSize", Type: "u32le", Offset: 2, Length: 4, Value: fmt.Sprintf("%d", fileSize)},
|
|
{Name: "pixelDataOffset", Type: "u32le", Offset: 10, Length: 4, Value: fmt.Sprintf("0x%x", pixelOff)},
|
|
{Name: "dibHeaderSize", Type: "u32le", Offset: 14, Length: 4, Value: fmt.Sprintf("%d (%s)", dibSize, bmpDIBName(dibSize))},
|
|
},
|
|
}
|
|
if dibSize == 12 {
|
|
if len(hdr) < 26 {
|
|
return root, nil
|
|
}
|
|
width := binary.LittleEndian.Uint16(hdr[18:20])
|
|
height := binary.LittleEndian.Uint16(hdr[20:22])
|
|
planes := binary.LittleEndian.Uint16(hdr[22:24])
|
|
bpp := binary.LittleEndian.Uint16(hdr[24:26])
|
|
root.Children = append(root.Children,
|
|
&Field{Name: "width", Type: "u16le", Offset: 18, Length: 2, Value: fmt.Sprintf("%d", width)},
|
|
&Field{Name: "height", Type: "u16le", Offset: 20, Length: 2, Value: fmt.Sprintf("%d", height)},
|
|
&Field{Name: "planes", Type: "u16le", Offset: 22, Length: 2, Value: fmt.Sprintf("%d", planes)},
|
|
&Field{Name: "bitsPerPixel", Type: "u16le", Offset: 24, Length: 2, Value: fmt.Sprintf("%d", bpp)},
|
|
)
|
|
} else if dibSize >= 40 && len(hdr) >= 54 {
|
|
width := int32(binary.LittleEndian.Uint32(hdr[18:22]))
|
|
height := int32(binary.LittleEndian.Uint32(hdr[22:26]))
|
|
planes := binary.LittleEndian.Uint16(hdr[26:28])
|
|
bpp := binary.LittleEndian.Uint16(hdr[28:30])
|
|
compression := binary.LittleEndian.Uint32(hdr[30:34])
|
|
imageSize := binary.LittleEndian.Uint32(hdr[34:38])
|
|
xppm := int32(binary.LittleEndian.Uint32(hdr[38:42]))
|
|
yppm := int32(binary.LittleEndian.Uint32(hdr[42:46]))
|
|
coloursUsed := binary.LittleEndian.Uint32(hdr[46:50])
|
|
root.Children = append(root.Children,
|
|
&Field{Name: "width", Type: "i32le", Offset: 18, Length: 4, Value: fmt.Sprintf("%d", width)},
|
|
&Field{Name: "height", Type: "i32le", Offset: 22, Length: 4, Value: fmt.Sprintf("%d", height)},
|
|
&Field{Name: "planes", Type: "u16le", Offset: 26, Length: 2, Value: fmt.Sprintf("%d", planes)},
|
|
&Field{Name: "bitsPerPixel", Type: "u16le", Offset: 28, Length: 2, Value: fmt.Sprintf("%d", bpp)},
|
|
&Field{Name: "compression", Type: "u32le", Offset: 30, Length: 4, Value: fmt.Sprintf("%d (%s)", compression, bmpCompressionName(compression))},
|
|
&Field{Name: "imageSize", Type: "u32le", Offset: 34, Length: 4, Value: fmt.Sprintf("%d", imageSize)},
|
|
&Field{Name: "xPixelsPerMeter", Type: "i32le", Offset: 38, Length: 4, Value: fmt.Sprintf("%d", xppm)},
|
|
&Field{Name: "yPixelsPerMeter", Type: "i32le", Offset: 42, Length: 4, Value: fmt.Sprintf("%d", yppm)},
|
|
&Field{Name: "coloursUsed", Type: "u32le", Offset: 46, Length: 4, Value: fmt.Sprintf("%d", coloursUsed)},
|
|
)
|
|
}
|
|
if int64(pixelOff) < size {
|
|
root.Children = append(root.Children, &Field{Name: "pixelData", Type: "bytes", Offset: int64(pixelOff), Length: size - int64(pixelOff), Value: fmt.Sprintf("%d bytes", size-int64(pixelOff))})
|
|
}
|
|
return root, nil
|
|
}
|
|
|
|
func bmpDIBName(size uint32) string {
|
|
switch size {
|
|
case 12:
|
|
return "BITMAPCOREHEADER"
|
|
case 40:
|
|
return "BITMAPINFOHEADER"
|
|
case 52:
|
|
return "BITMAPV2INFOHEADER"
|
|
case 56:
|
|
return "BITMAPV3INFOHEADER"
|
|
case 108:
|
|
return "BITMAPV4HEADER"
|
|
case 124:
|
|
return "BITMAPV5HEADER"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func bmpCompressionName(v uint32) string {
|
|
switch v {
|
|
case 0:
|
|
return "BI_RGB"
|
|
case 1:
|
|
return "BI_RLE8"
|
|
case 2:
|
|
return "BI_RLE4"
|
|
case 3:
|
|
return "BI_BITFIELDS"
|
|
case 4:
|
|
return "BI_JPEG"
|
|
case 5:
|
|
return "BI_PNG"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
// ParseZIP walks local file headers by seeking forward.
|
|
func ParseZIP(d *document.Document) (*Field, error) {
|
|
root := &Field{Name: "ZIP", Type: "container", Offset: 0, Length: d.Size()}
|
|
size := d.Size()
|
|
|
|
// 1) Local file headers, walked from the front.
|
|
off := int64(0)
|
|
entries := 0
|
|
for off+30 <= size && entries < 5000 {
|
|
sig, err := d.Range(off, 4)
|
|
if err != nil || string(sig) != "PK\x03\x04" {
|
|
break // reached the central directory / EOCD region
|
|
}
|
|
hdr, err := d.Range(off, 30)
|
|
if err != nil {
|
|
break
|
|
}
|
|
flags := binary.LittleEndian.Uint16(hdr[6:8])
|
|
method := binary.LittleEndian.Uint16(hdr[8:10])
|
|
dosTime := binary.LittleEndian.Uint16(hdr[10:12])
|
|
dosDate := binary.LittleEndian.Uint16(hdr[12:14])
|
|
crc := binary.LittleEndian.Uint32(hdr[14:18])
|
|
compSize := int64(binary.LittleEndian.Uint32(hdr[18:22]))
|
|
uncompSize := int64(binary.LittleEndian.Uint32(hdr[22:26]))
|
|
nameLen := int64(binary.LittleEndian.Uint16(hdr[26:28]))
|
|
extraLen := int64(binary.LittleEndian.Uint16(hdr[28:30]))
|
|
nameB, _ := d.Range(off+30, nameLen)
|
|
e := &Field{
|
|
Name: string(nameB),
|
|
Type: "entry",
|
|
Offset: off,
|
|
Length: 30 + nameLen + extraLen + compSize,
|
|
Value: fmt.Sprintf("%s comp=%d uncomp=%d", zipMethod(method), compSize, uncompSize),
|
|
Children: []*Field{
|
|
{Name: "signature", Type: "bytes", Offset: off, Length: 4, Value: "PK\\x03\\x04"},
|
|
{Name: "version_needed", Type: "u16le", Offset: off + 4, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(hdr[4:6]))},
|
|
{Name: "flags", Type: "u16le", Offset: off + 6, Length: 2, Value: zipFlags(flags)},
|
|
{Name: "method", Type: "u16le", Offset: off + 8, Length: 2, Value: zipMethod(method)},
|
|
{Name: "mod_time", Type: "dostime", Offset: off + 10, Length: 4, Value: zipDOSDateTime(dosDate, dosTime)},
|
|
{Name: "crc32", Type: "u32le", Offset: off + 14, Length: 4, Value: fmt.Sprintf("0x%08x", crc)},
|
|
{Name: "compressed_size", Type: "u32le", Offset: off + 18, Length: 4, Value: fmt.Sprintf("%d", compSize)},
|
|
{Name: "uncompressed_size", Type: "u32le", Offset: off + 22, Length: 4, Value: fmt.Sprintf("%d", uncompSize)},
|
|
{Name: "name", Type: "ascii", Offset: off + 30, Length: nameLen, Value: string(nameB)},
|
|
{Name: "data", Type: "bytes", Offset: off + 30 + nameLen + extraLen, Length: compSize, Value: fmt.Sprintf("%d bytes", compSize)},
|
|
},
|
|
}
|
|
root.Children = append(root.Children, e)
|
|
off += 30 + nameLen + extraLen + compSize
|
|
entries++
|
|
}
|
|
|
|
// 2) Central directory + end-of-central-directory, located from the back.
|
|
if cd := zipCentralDirectory(d); cd != nil {
|
|
root.Children = append(root.Children, cd...)
|
|
}
|
|
return root, nil
|
|
}
|
|
|
|
// zipCentralDirectory finds the EOCD record, decodes it, and walks the central
|
|
// directory it points at. Returns the EOCD field plus a "central_directory"
|
|
// container, or nil if no EOCD is present.
|
|
func zipCentralDirectory(d *document.Document) []*Field {
|
|
size := d.Size()
|
|
// EOCD is at most 22 + 65535 bytes from the end; scan a bounded tail.
|
|
scan := int64(22 + 65535)
|
|
if scan > size {
|
|
scan = size
|
|
}
|
|
tail, err := d.Range(size-scan, scan)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
eocdRel := int64(-1)
|
|
for i := len(tail) - 22; i >= 0; i-- {
|
|
if tail[i] == 'P' && tail[i+1] == 'K' && tail[i+2] == 0x05 && tail[i+3] == 0x06 {
|
|
eocdRel = int64(i)
|
|
break
|
|
}
|
|
}
|
|
if eocdRel < 0 {
|
|
return nil
|
|
}
|
|
eocdOff := size - scan + eocdRel
|
|
e, _ := d.Range(eocdOff, 22)
|
|
if len(e) < 22 {
|
|
return nil
|
|
}
|
|
total := binary.LittleEndian.Uint16(e[10:12])
|
|
cdSize := int64(binary.LittleEndian.Uint32(e[12:16]))
|
|
cdOff := int64(binary.LittleEndian.Uint32(e[16:20]))
|
|
commentLen := int64(binary.LittleEndian.Uint16(e[20:22]))
|
|
eocd := &Field{
|
|
Name: "end_of_central_directory", Type: "eocd", Offset: eocdOff, Length: 22 + commentLen,
|
|
Value: fmt.Sprintf("%d entries, cd@0x%x size=%d", total, cdOff, cdSize),
|
|
Children: []*Field{
|
|
{Name: "total_entries", Type: "u16le", Offset: eocdOff + 10, Length: 2, Value: fmt.Sprintf("%d", total)},
|
|
{Name: "cd_size", Type: "u32le", Offset: eocdOff + 12, Length: 4, Value: fmt.Sprintf("%d", cdSize)},
|
|
{Name: "cd_offset", Type: "u32le", Offset: eocdOff + 16, Length: 4, Value: fmt.Sprintf("0x%x", cdOff)},
|
|
{Name: "comment_length", Type: "u16le", Offset: eocdOff + 20, Length: 2, Value: fmt.Sprintf("%d", commentLen)},
|
|
},
|
|
}
|
|
out := []*Field{}
|
|
|
|
// ZIP64: a 0xffffffff offset or 0xffff count signals a ZIP64 EOCD locator.
|
|
if cdOff == 0xffffffff || total == 0xffff {
|
|
if eocdOff-20 >= 0 {
|
|
loc, _ := d.Range(eocdOff-20, 20)
|
|
if len(loc) == 20 && string(loc[0:4]) == "PK\x06\x07" {
|
|
z64Off := int64(binary.LittleEndian.Uint64(loc[8:16]))
|
|
eocd.Children = append(eocd.Children, &Field{Name: "zip64_locator", Type: "bytes", Offset: eocdOff - 20, Length: 20, Value: fmt.Sprintf("zip64 EOCD @0x%x", z64Off)})
|
|
if z64, _ := d.Range(z64Off, 56); len(z64) == 56 && string(z64[0:4]) == "PK\x06\x06" {
|
|
cdOff = int64(binary.LittleEndian.Uint64(z64[48:56]))
|
|
cdSize = int64(binary.LittleEndian.Uint64(z64[40:48]))
|
|
out = append(out, &Field{Name: "zip64_eocd", Type: "zip64", Offset: z64Off, Length: 56, Value: fmt.Sprintf("entries=%d cd@0x%x", binary.LittleEndian.Uint64(z64[32:40]), cdOff)})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk central-directory file headers.
|
|
if cdOff >= 0 && cdOff < size {
|
|
cd := &Field{Name: "central_directory", Type: "directory", Offset: cdOff, Length: cdSize}
|
|
off := cdOff
|
|
end := size
|
|
if cdSize > 0 && cdOff+cdSize <= size {
|
|
end = cdOff + cdSize
|
|
}
|
|
for n := 0; off+46 <= end && n < 5000; n++ {
|
|
h, err := d.Range(off, 46)
|
|
if err != nil || string(h[0:4]) != "PK\x01\x02" {
|
|
break
|
|
}
|
|
method := binary.LittleEndian.Uint16(h[10:12])
|
|
crc := binary.LittleEndian.Uint32(h[16:20])
|
|
compSize := int64(binary.LittleEndian.Uint32(h[20:24]))
|
|
nameLen := int64(binary.LittleEndian.Uint16(h[28:30]))
|
|
extraLen := int64(binary.LittleEndian.Uint16(h[30:32]))
|
|
commLen := int64(binary.LittleEndian.Uint16(h[32:34]))
|
|
lho := binary.LittleEndian.Uint32(h[42:46])
|
|
nameB, _ := d.Range(off+46, nameLen)
|
|
cd.Children = append(cd.Children, &Field{
|
|
Name: string(nameB), Type: "cd_entry", Offset: off, Length: 46 + nameLen + extraLen + commLen,
|
|
Value: fmt.Sprintf("%s crc=0x%08x comp=%d lfh@0x%x", zipMethod(method), crc, compSize, lho),
|
|
})
|
|
off += 46 + nameLen + extraLen + commLen
|
|
}
|
|
out = append(out, cd)
|
|
}
|
|
out = append(out, eocd)
|
|
return out
|
|
}
|
|
|
|
func zipMethod(m uint16) string {
|
|
names := map[uint16]string{0: "store", 1: "shrink", 6: "implode", 8: "deflate", 9: "deflate64", 12: "bzip2", 14: "lzma", 95: "xz", 96: "jpeg", 97: "wavpack", 98: "ppmd", 99: "aes"}
|
|
if n, ok := names[m]; ok {
|
|
return fmt.Sprintf("%d (%s)", m, n)
|
|
}
|
|
return fmt.Sprintf("%d", m)
|
|
}
|
|
|
|
func zipFlags(f uint16) string {
|
|
var on []string
|
|
if f&0x0001 != 0 {
|
|
on = append(on, "encrypted")
|
|
}
|
|
if f&0x0008 != 0 {
|
|
on = append(on, "data-descriptor")
|
|
}
|
|
if f&0x0800 != 0 {
|
|
on = append(on, "utf8")
|
|
}
|
|
if len(on) == 0 {
|
|
return fmt.Sprintf("0x%04x", f)
|
|
}
|
|
return fmt.Sprintf("0x%04x (%s)", f, strings.Join(on, "|"))
|
|
}
|
|
|
|
// zipDOSDateTime decodes the MS-DOS packed date/time used by ZIP entries.
|
|
func zipDOSDateTime(date, t uint16) string {
|
|
if date == 0 && t == 0 {
|
|
return "0"
|
|
}
|
|
year := 1980 + int(date>>9)
|
|
month := int(date>>5) & 0x0f
|
|
day := int(date) & 0x1f
|
|
hour := int(t >> 11)
|
|
minute := int(t>>5) & 0x3f
|
|
second := (int(t) & 0x1f) * 2
|
|
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
|
|
}
|
|
|
|
// ParseELF returns header, program header, and section header fields.
|
|
func ParseELF(d *document.Document) (*Field, error) {
|
|
hdr, err := d.Range(0, 64)
|
|
if err != nil || len(hdr) < 52 {
|
|
return nil, errors.New("elf too short")
|
|
}
|
|
class := hdr[4]
|
|
data := hdr[5]
|
|
kind := "32-bit"
|
|
if class == 2 {
|
|
kind = "64-bit"
|
|
}
|
|
endian := "little-endian"
|
|
if data == 2 {
|
|
endian = "big-endian"
|
|
}
|
|
root := &Field{
|
|
Name: "ELF",
|
|
Type: "container",
|
|
Offset: 0,
|
|
Length: d.Size(),
|
|
Value: kind + " " + endian,
|
|
Children: []*Field{
|
|
{Name: "ei_mag", Type: "bytes", Offset: 0, Length: 4, Value: "\\x7fELF"},
|
|
{Name: "ei_class", Type: "u8", Offset: 4, Length: 1, Value: kind},
|
|
{Name: "ei_data", Type: "u8", Offset: 5, Length: 1, Value: endian},
|
|
{Name: "ei_version", Type: "u8", Offset: 6, Length: 1, Value: fmt.Sprintf("%d", hdr[6])},
|
|
{Name: "ei_osabi", Type: "u8", Offset: 7, Length: 1, Value: fmt.Sprintf("%d", hdr[7])},
|
|
},
|
|
}
|
|
var e_type, e_machine uint16
|
|
var e_entry uint64
|
|
var e_phoff, e_shoff uint64
|
|
var e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx uint16
|
|
bo := elfByteOrder(data)
|
|
if class == 2 {
|
|
e_type = bo.Uint16(hdr[16:18])
|
|
e_machine = bo.Uint16(hdr[18:20])
|
|
e_entry = bo.Uint64(hdr[24:32])
|
|
e_phoff = bo.Uint64(hdr[32:40])
|
|
e_shoff = bo.Uint64(hdr[40:48])
|
|
e_phentsize = bo.Uint16(hdr[54:56])
|
|
e_phnum = bo.Uint16(hdr[56:58])
|
|
e_shentsize = bo.Uint16(hdr[58:60])
|
|
e_shnum = bo.Uint16(hdr[60:62])
|
|
e_shstrndx = bo.Uint16(hdr[62:64])
|
|
} else {
|
|
e_type = bo.Uint16(hdr[16:18])
|
|
e_machine = bo.Uint16(hdr[18:20])
|
|
e_entry = uint64(bo.Uint32(hdr[24:28]))
|
|
e_phoff = uint64(bo.Uint32(hdr[28:32]))
|
|
e_shoff = uint64(bo.Uint32(hdr[32:36]))
|
|
e_phentsize = bo.Uint16(hdr[42:44])
|
|
e_phnum = bo.Uint16(hdr[44:46])
|
|
e_shentsize = bo.Uint16(hdr[46:48])
|
|
e_shnum = bo.Uint16(hdr[48:50])
|
|
e_shstrndx = bo.Uint16(hdr[50:52])
|
|
}
|
|
root.Children = append(root.Children,
|
|
&Field{Name: "e_type", Value: fmt.Sprintf("%d (%s)", e_type, elfType(e_type))},
|
|
&Field{Name: "e_machine", Value: fmt.Sprintf("%d (%s)", e_machine, elfMachine(e_machine))},
|
|
&Field{Name: "e_entry", Value: fmt.Sprintf("0x%x", e_entry)},
|
|
&Field{Name: "e_phoff", Value: fmt.Sprintf("0x%x", e_phoff)},
|
|
&Field{Name: "e_shoff", Value: fmt.Sprintf("0x%x", e_shoff)},
|
|
&Field{Name: "e_phentsize", Value: fmt.Sprintf("%d", e_phentsize)},
|
|
&Field{Name: "e_phnum", Value: fmt.Sprintf("%d", e_phnum)},
|
|
&Field{Name: "e_shentsize", Value: fmt.Sprintf("%d", e_shentsize)},
|
|
&Field{Name: "e_shnum", Value: fmt.Sprintf("%d", e_shnum)},
|
|
&Field{Name: "e_shstrndx", Value: fmt.Sprintf("%d", e_shstrndx)},
|
|
)
|
|
root.Children = append(root.Children, elfProgramHeaders(d, bo, class, e_phoff, e_phentsize, e_phnum)...)
|
|
root.Children = append(root.Children, elfSectionHeaders(d, bo, class, e_shoff, e_shentsize, e_shnum, e_shstrndx)...)
|
|
return root, nil
|
|
}
|
|
|
|
func elfByteOrder(data byte) binary.ByteOrder {
|
|
if data == 2 {
|
|
return binary.BigEndian
|
|
}
|
|
return binary.LittleEndian
|
|
}
|
|
|
|
func elfProgramHeaders(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entsize, count uint16) []*Field {
|
|
if off == 0 || entsize == 0 || count == 0 {
|
|
return nil
|
|
}
|
|
size := d.Size()
|
|
out := make([]*Field, 0, count)
|
|
for i := uint16(0); i < count; i++ {
|
|
at := int64(off) + int64(i)*int64(entsize)
|
|
if at < 0 || at+int64(entsize) > size {
|
|
break
|
|
}
|
|
buf, err := d.Range(at, int64(entsize))
|
|
if err != nil {
|
|
break
|
|
}
|
|
var typ, flags uint32
|
|
var pOffset, vaddr, filesz, memsz, align uint64
|
|
if class == 2 {
|
|
if len(buf) < 56 {
|
|
break
|
|
}
|
|
typ = bo.Uint32(buf[0:4])
|
|
flags = bo.Uint32(buf[4:8])
|
|
pOffset = bo.Uint64(buf[8:16])
|
|
vaddr = bo.Uint64(buf[16:24])
|
|
filesz = bo.Uint64(buf[32:40])
|
|
memsz = bo.Uint64(buf[40:48])
|
|
align = bo.Uint64(buf[48:56])
|
|
} else {
|
|
if len(buf) < 32 {
|
|
break
|
|
}
|
|
typ = bo.Uint32(buf[0:4])
|
|
pOffset = uint64(bo.Uint32(buf[4:8]))
|
|
vaddr = uint64(bo.Uint32(buf[8:12]))
|
|
filesz = uint64(bo.Uint32(buf[16:20]))
|
|
memsz = uint64(bo.Uint32(buf[20:24]))
|
|
flags = bo.Uint32(buf[24:28])
|
|
align = uint64(bo.Uint32(buf[28:32]))
|
|
}
|
|
out = append(out, &Field{
|
|
Name: fmt.Sprintf("program[%d]", i),
|
|
Type: "program_header",
|
|
Offset: at,
|
|
Length: int64(entsize),
|
|
Value: fmt.Sprintf("type=%s offset=0x%x vaddr=0x%x filesz=%d memsz=%d flags=%s align=0x%x", elfProgramType(typ), pOffset, vaddr, filesz, memsz, elfProgramFlags(flags), align),
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
type elfSectionInfo struct {
|
|
nameOff uint32
|
|
typ uint32
|
|
flags, addr uint64
|
|
fileOff, secSize uint64
|
|
link, info uint32
|
|
align, entrySize uint64
|
|
at int64
|
|
length int64
|
|
}
|
|
|
|
func elfSectionHeaders(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entsize, count, strIndex uint16) []*Field {
|
|
if off == 0 || entsize == 0 || count == 0 {
|
|
return nil
|
|
}
|
|
size := d.Size()
|
|
sections := make([]elfSectionInfo, 0, count)
|
|
for i := uint16(0); i < count; i++ {
|
|
at := int64(off) + int64(i)*int64(entsize)
|
|
if at < 0 || at+int64(entsize) > size {
|
|
break
|
|
}
|
|
buf, err := d.Range(at, int64(entsize))
|
|
if err != nil {
|
|
break
|
|
}
|
|
var s elfSectionInfo
|
|
s.at = at
|
|
s.length = int64(entsize)
|
|
if class == 2 {
|
|
if len(buf) < 64 {
|
|
break
|
|
}
|
|
s.nameOff = bo.Uint32(buf[0:4])
|
|
s.typ = bo.Uint32(buf[4:8])
|
|
s.flags = bo.Uint64(buf[8:16])
|
|
s.addr = bo.Uint64(buf[16:24])
|
|
s.fileOff = bo.Uint64(buf[24:32])
|
|
s.secSize = bo.Uint64(buf[32:40])
|
|
s.link = bo.Uint32(buf[40:44])
|
|
s.info = bo.Uint32(buf[44:48])
|
|
s.align = bo.Uint64(buf[48:56])
|
|
s.entrySize = bo.Uint64(buf[56:64])
|
|
} else {
|
|
if len(buf) < 40 {
|
|
break
|
|
}
|
|
s.nameOff = bo.Uint32(buf[0:4])
|
|
s.typ = bo.Uint32(buf[4:8])
|
|
s.flags = uint64(bo.Uint32(buf[8:12]))
|
|
s.addr = uint64(bo.Uint32(buf[12:16]))
|
|
s.fileOff = uint64(bo.Uint32(buf[16:20]))
|
|
s.secSize = uint64(bo.Uint32(buf[20:24]))
|
|
s.link = bo.Uint32(buf[24:28])
|
|
s.info = bo.Uint32(buf[28:32])
|
|
s.align = uint64(bo.Uint32(buf[32:36]))
|
|
s.entrySize = uint64(bo.Uint32(buf[36:40]))
|
|
}
|
|
sections = append(sections, s)
|
|
}
|
|
var names []byte
|
|
if int(strIndex) < len(sections) {
|
|
str := sections[strIndex]
|
|
if str.fileOff <= uint64(size) && str.fileOff+str.secSize <= uint64(size) && str.secSize <= 1<<20 {
|
|
names, _ = d.Range(int64(str.fileOff), int64(str.secSize))
|
|
}
|
|
}
|
|
sectionNames := make([]string, len(sections))
|
|
for i, s := range sections {
|
|
name := elfStringAt(names, s.nameOff)
|
|
if name == "" {
|
|
name = fmt.Sprintf("section[%d]", i)
|
|
}
|
|
sectionNames[i] = name
|
|
}
|
|
out := make([]*Field, 0, len(sections))
|
|
for i, s := range sections {
|
|
name := sectionNames[i]
|
|
children := []*Field{}
|
|
if s.typ == 6 {
|
|
children = elfDynamicEntries(d, bo, class, sections, s)
|
|
} else if s.typ == 2 || s.typ == 11 {
|
|
children = elfSymbolEntries(d, bo, class, sections, s)
|
|
}
|
|
out = append(out, &Field{
|
|
Name: name,
|
|
Type: "section_header",
|
|
Offset: s.at,
|
|
Length: s.length,
|
|
Value: fmt.Sprintf("type=%s offset=0x%x size=%d addr=0x%x flags=%s link=%d info=%d align=0x%x entsize=%d", elfSectionType(s.typ), s.fileOff, s.secSize, s.addr, elfSectionFlags(s.flags), s.link, s.info, s.align, s.entrySize),
|
|
Children: children,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func elfSymbolEntries(d *document.Document, bo binary.ByteOrder, class byte, sections []elfSectionInfo, symtab elfSectionInfo) []*Field {
|
|
entrySize := symtab.entrySize
|
|
if entrySize == 0 {
|
|
if class == 2 {
|
|
entrySize = 24
|
|
} else {
|
|
entrySize = 16
|
|
}
|
|
}
|
|
if entrySize == 0 || symtab.secSize == 0 || symtab.fileOff+symtab.secSize > uint64(d.Size()) {
|
|
return nil
|
|
}
|
|
var strtab []byte
|
|
if int(symtab.link) < len(sections) {
|
|
linked := sections[symtab.link]
|
|
if linked.fileOff+linked.secSize <= uint64(d.Size()) && linked.secSize <= 8<<20 {
|
|
strtab, _ = d.Range(int64(linked.fileOff), int64(linked.secSize))
|
|
}
|
|
}
|
|
count := int(symtab.secSize / entrySize)
|
|
if count > 4096 {
|
|
count = 4096
|
|
}
|
|
out := []*Field{}
|
|
for i := 0; i < count; i++ {
|
|
at := int64(symtab.fileOff + uint64(i)*entrySize)
|
|
buf, err := d.Range(at, int64(entrySize))
|
|
if err != nil || len(buf) < int(entrySize) {
|
|
break
|
|
}
|
|
var nameOff uint32
|
|
var info, other byte
|
|
var shndx uint16
|
|
var value, size uint64
|
|
if class == 2 {
|
|
nameOff = bo.Uint32(buf[0:4])
|
|
info = buf[4]
|
|
other = buf[5]
|
|
shndx = bo.Uint16(buf[6:8])
|
|
value = bo.Uint64(buf[8:16])
|
|
size = bo.Uint64(buf[16:24])
|
|
} else {
|
|
nameOff = bo.Uint32(buf[0:4])
|
|
value = uint64(bo.Uint32(buf[4:8]))
|
|
size = uint64(bo.Uint32(buf[8:12]))
|
|
info = buf[12]
|
|
other = buf[13]
|
|
shndx = bo.Uint16(buf[14:16])
|
|
}
|
|
name := elfStringAt(strtab, nameOff)
|
|
if name == "" {
|
|
name = fmt.Sprintf("<unnamed:%d>", i)
|
|
}
|
|
out = append(out, &Field{
|
|
Name: fmt.Sprintf("symbol[%d] %s", i, name),
|
|
Type: "symbol",
|
|
Offset: at,
|
|
Length: int64(entrySize),
|
|
Value: fmt.Sprintf("name=%s bind=%s type=%s vis=%s shndx=%s value=0x%x size=%d",
|
|
name, elfSymbolBind(info>>4), elfSymbolType(info&0xf), elfSymbolVisibility(other&0x3), elfSymbolSection(shndx), value, size),
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func elfDynamicEntries(d *document.Document, bo binary.ByteOrder, class byte, sections []elfSectionInfo, dyn elfSectionInfo) []*Field {
|
|
entrySize := dyn.entrySize
|
|
if entrySize == 0 {
|
|
if class == 2 {
|
|
entrySize = 16
|
|
} else {
|
|
entrySize = 8
|
|
}
|
|
}
|
|
if entrySize == 0 || dyn.secSize == 0 || dyn.fileOff+dyn.secSize > uint64(d.Size()) {
|
|
return nil
|
|
}
|
|
var strtab []byte
|
|
if int(dyn.link) < len(sections) {
|
|
linked := sections[dyn.link]
|
|
if linked.fileOff+linked.secSize <= uint64(d.Size()) && linked.secSize <= 8<<20 {
|
|
strtab, _ = d.Range(int64(linked.fileOff), int64(linked.secSize))
|
|
}
|
|
}
|
|
out := []*Field{}
|
|
count := int(dyn.secSize / entrySize)
|
|
if count > 4096 {
|
|
count = 4096
|
|
}
|
|
for i := 0; i < count; i++ {
|
|
at := int64(dyn.fileOff + uint64(i)*entrySize)
|
|
buf, err := d.Range(at, int64(entrySize))
|
|
if err != nil || len(buf) < int(entrySize) {
|
|
break
|
|
}
|
|
var tag int64
|
|
var val uint64
|
|
if class == 2 {
|
|
tag = int64(bo.Uint64(buf[0:8]))
|
|
val = bo.Uint64(buf[8:16])
|
|
} else {
|
|
tag = int64(int32(bo.Uint32(buf[0:4])))
|
|
val = uint64(bo.Uint32(buf[4:8]))
|
|
}
|
|
value := fmt.Sprintf("%s=0x%x", elfDynamicTag(tag), val)
|
|
if tag == 1 && len(strtab) > 0 {
|
|
if s := elfStringAt(strtab, uint32(val)); s != "" {
|
|
value = "NEEDED=" + s
|
|
}
|
|
}
|
|
out = append(out, &Field{
|
|
Name: fmt.Sprintf("dynamic[%d]", i),
|
|
Type: "dynamic_entry",
|
|
Offset: at,
|
|
Length: int64(entrySize),
|
|
Value: value,
|
|
})
|
|
if tag == 0 {
|
|
break
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func elfSymbolBind(bind byte) string {
|
|
switch bind {
|
|
case 0:
|
|
return "LOCAL"
|
|
case 1:
|
|
return "GLOBAL"
|
|
case 2:
|
|
return "WEAK"
|
|
}
|
|
return fmt.Sprintf("0x%x", bind)
|
|
}
|
|
|
|
func elfSymbolType(typ byte) string {
|
|
switch typ {
|
|
case 0:
|
|
return "NOTYPE"
|
|
case 1:
|
|
return "OBJECT"
|
|
case 2:
|
|
return "FUNC"
|
|
case 3:
|
|
return "SECTION"
|
|
case 4:
|
|
return "FILE"
|
|
case 5:
|
|
return "COMMON"
|
|
case 6:
|
|
return "TLS"
|
|
}
|
|
return fmt.Sprintf("0x%x", typ)
|
|
}
|
|
|
|
func elfSymbolVisibility(vis byte) string {
|
|
switch vis {
|
|
case 0:
|
|
return "DEFAULT"
|
|
case 1:
|
|
return "INTERNAL"
|
|
case 2:
|
|
return "HIDDEN"
|
|
case 3:
|
|
return "PROTECTED"
|
|
}
|
|
return fmt.Sprintf("0x%x", vis)
|
|
}
|
|
|
|
func elfSymbolSection(idx uint16) string {
|
|
switch idx {
|
|
case 0:
|
|
return "UND"
|
|
case 0xfff1:
|
|
return "ABS"
|
|
case 0xfff2:
|
|
return "COMMON"
|
|
}
|
|
return fmt.Sprintf("%d", idx)
|
|
}
|
|
|
|
func elfDynamicTag(tag int64) string {
|
|
switch tag {
|
|
case 0:
|
|
return "NULL"
|
|
case 1:
|
|
return "NEEDED"
|
|
case 2:
|
|
return "PLTRELSZ"
|
|
case 3:
|
|
return "PLTGOT"
|
|
case 4:
|
|
return "HASH"
|
|
case 5:
|
|
return "STRTAB"
|
|
case 6:
|
|
return "SYMTAB"
|
|
case 7:
|
|
return "RELA"
|
|
case 8:
|
|
return "RELASZ"
|
|
case 9:
|
|
return "RELAENT"
|
|
case 10:
|
|
return "STRSZ"
|
|
case 11:
|
|
return "SYMENT"
|
|
case 12:
|
|
return "INIT"
|
|
case 13:
|
|
return "FINI"
|
|
case 14:
|
|
return "SONAME"
|
|
case 15:
|
|
return "RPATH"
|
|
case 16:
|
|
return "SYMBOLIC"
|
|
case 17:
|
|
return "REL"
|
|
case 18:
|
|
return "RELSZ"
|
|
case 19:
|
|
return "RELENT"
|
|
case 20:
|
|
return "PLTREL"
|
|
case 21:
|
|
return "DEBUG"
|
|
case 22:
|
|
return "TEXTREL"
|
|
case 23:
|
|
return "JMPREL"
|
|
case 29:
|
|
return "RUNPATH"
|
|
}
|
|
return fmt.Sprintf("0x%x", tag)
|
|
}
|
|
|
|
func elfStringAt(buf []byte, off uint32) string {
|
|
if int(off) >= len(buf) {
|
|
return ""
|
|
}
|
|
end := int(off)
|
|
for end < len(buf) && buf[end] != 0 {
|
|
end++
|
|
}
|
|
return string(buf[off:end])
|
|
}
|
|
|
|
func elfProgramType(t uint32) string {
|
|
switch t {
|
|
case 0:
|
|
return "NULL"
|
|
case 1:
|
|
return "LOAD"
|
|
case 2:
|
|
return "DYNAMIC"
|
|
case 3:
|
|
return "INTERP"
|
|
case 4:
|
|
return "NOTE"
|
|
case 5:
|
|
return "SHLIB"
|
|
case 6:
|
|
return "PHDR"
|
|
case 7:
|
|
return "TLS"
|
|
}
|
|
return fmt.Sprintf("0x%x", t)
|
|
}
|
|
|
|
func elfProgramFlags(flags uint32) string {
|
|
var out string
|
|
if flags&4 != 0 {
|
|
out += "R"
|
|
}
|
|
if flags&2 != 0 {
|
|
out += "W"
|
|
}
|
|
if flags&1 != 0 {
|
|
out += "X"
|
|
}
|
|
if out == "" {
|
|
return "-"
|
|
}
|
|
return out
|
|
}
|
|
|
|
func elfSectionType(t uint32) string {
|
|
switch t {
|
|
case 0:
|
|
return "NULL"
|
|
case 1:
|
|
return "PROGBITS"
|
|
case 2:
|
|
return "SYMTAB"
|
|
case 3:
|
|
return "STRTAB"
|
|
case 4:
|
|
return "RELA"
|
|
case 5:
|
|
return "HASH"
|
|
case 6:
|
|
return "DYNAMIC"
|
|
case 7:
|
|
return "NOTE"
|
|
case 8:
|
|
return "NOBITS"
|
|
case 9:
|
|
return "REL"
|
|
case 11:
|
|
return "DYNSYM"
|
|
case 14:
|
|
return "INIT_ARRAY"
|
|
case 15:
|
|
return "FINI_ARRAY"
|
|
case 17:
|
|
return "GROUP"
|
|
case 18:
|
|
return "SYMTAB_SHNDX"
|
|
}
|
|
return fmt.Sprintf("0x%x", t)
|
|
}
|
|
|
|
func elfSectionFlags(flags uint64) string {
|
|
var out string
|
|
if flags&0x1 != 0 {
|
|
out += "W"
|
|
}
|
|
if flags&0x2 != 0 {
|
|
out += "A"
|
|
}
|
|
if flags&0x4 != 0 {
|
|
out += "X"
|
|
}
|
|
if flags&0x10 != 0 {
|
|
out += "M"
|
|
}
|
|
if flags&0x20 != 0 {
|
|
out += "S"
|
|
}
|
|
if flags&0x40 != 0 {
|
|
out += "I"
|
|
}
|
|
if flags&0x400 != 0 {
|
|
out += "T"
|
|
}
|
|
if out == "" {
|
|
return "-"
|
|
}
|
|
return out
|
|
}
|
|
|
|
func elfType(t uint16) string {
|
|
switch t {
|
|
case 1:
|
|
return "REL"
|
|
case 2:
|
|
return "EXEC"
|
|
case 3:
|
|
return "DYN"
|
|
case 4:
|
|
return "CORE"
|
|
}
|
|
return "UNKNOWN"
|
|
}
|
|
|
|
func elfMachine(m uint16) string {
|
|
switch m {
|
|
case 0x03:
|
|
return "x86"
|
|
case 0x28:
|
|
return "ARM"
|
|
case 0x3e:
|
|
return "x86_64"
|
|
case 0xb7:
|
|
return "AArch64"
|
|
case 0x08:
|
|
return "MIPS"
|
|
case 0xf3:
|
|
return "RISC-V"
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
// ParsePE walks the DOS header, PE header, and section headers.
|
|
func ParsePE(d *document.Document) (*Field, error) {
|
|
hdr, err := d.Range(0, 64)
|
|
if err != nil || len(hdr) < 64 {
|
|
return nil, errors.New("too short")
|
|
}
|
|
lfanew := int64(binary.LittleEndian.Uint32(hdr[0x3c:0x40]))
|
|
root := &Field{Name: "PE", Type: "container", Offset: 0}
|
|
root.Children = append(root.Children,
|
|
&Field{Name: "e_magic", Offset: 0, Length: 2, Value: "MZ"},
|
|
&Field{Name: "e_lfanew", Offset: 0x3c, Length: 4, Value: fmt.Sprintf("0x%x", lfanew)},
|
|
)
|
|
peHdr, err := d.Range(lfanew, 24)
|
|
if err != nil || len(peHdr) < 24 || string(peHdr[:4]) != "PE\x00\x00" {
|
|
return root, errors.New("no PE signature")
|
|
}
|
|
machine := binary.LittleEndian.Uint16(peHdr[4:6])
|
|
numSections := binary.LittleEndian.Uint16(peHdr[6:8])
|
|
timestamp := binary.LittleEndian.Uint32(peHdr[8:12])
|
|
sizeOptHdr := binary.LittleEndian.Uint16(peHdr[20:22])
|
|
characteristics := binary.LittleEndian.Uint16(peHdr[22:24])
|
|
root.Children = append(root.Children,
|
|
&Field{Name: "Signature", Offset: lfanew, Length: 4, Value: "PE\\x00\\x00"},
|
|
&Field{Name: "Machine", Offset: lfanew + 4, Length: 2, Value: fmt.Sprintf("0x%04x (%s)", machine, peMachine(machine))},
|
|
&Field{Name: "NumberOfSections", Offset: lfanew + 6, Length: 2, Value: fmt.Sprintf("%d", numSections)},
|
|
&Field{Name: "TimeDateStamp", Offset: lfanew + 8, Length: 4, Value: fmt.Sprintf("%d", timestamp)},
|
|
&Field{Name: "SizeOfOptionalHeader", Offset: lfanew + 20, Length: 2, Value: fmt.Sprintf("%d", sizeOptHdr)},
|
|
&Field{Name: "Characteristics", Offset: lfanew + 22, Length: 2, Value: fmt.Sprintf("0x%04x", characteristics)},
|
|
)
|
|
sections := make([]peSectionInfo, 0, numSections)
|
|
optOff := lfanew + 24
|
|
var dataDirs []peDataDirectory
|
|
peIs64 := false
|
|
if sizeOptHdr > 0 {
|
|
opt, err := d.Range(optOff, int64(sizeOptHdr))
|
|
if err == nil && len(opt) >= 2 {
|
|
optional, dirs := parsePEOptionalHeader(opt, optOff)
|
|
root.Children = append(root.Children, optional)
|
|
dataDirs = dirs
|
|
peIs64 = binary.LittleEndian.Uint16(opt[0:2]) == 0x20b
|
|
}
|
|
}
|
|
sectOff := lfanew + 24 + int64(sizeOptHdr)
|
|
for i := 0; i < int(numSections); i++ {
|
|
sh, err := d.Range(sectOff, 40)
|
|
if err != nil || len(sh) < 40 {
|
|
break
|
|
}
|
|
name := string(trimZero(sh[0:8]))
|
|
vsize := binary.LittleEndian.Uint32(sh[8:12])
|
|
vaddr := binary.LittleEndian.Uint32(sh[12:16])
|
|
rsize := binary.LittleEndian.Uint32(sh[16:20])
|
|
raddr := binary.LittleEndian.Uint32(sh[20:24])
|
|
flags := binary.LittleEndian.Uint32(sh[36:40])
|
|
sec := &Field{
|
|
Name: name, Type: "section", Offset: sectOff, Length: 40,
|
|
Value: fmt.Sprintf("vaddr=0x%x vsize=%d raw=0x%x rsize=%d flags=0x%x", vaddr, vsize, raddr, rsize, flags),
|
|
}
|
|
root.Children = append(root.Children, sec)
|
|
sections = append(sections, peSectionInfo{Name: name, VirtualAddress: vaddr, VirtualSize: vsize, RawOffset: raddr, RawSize: rsize, Characteristics: flags})
|
|
sectOff += 40
|
|
}
|
|
if len(dataDirs) > 0 && dataDirs[0].RVA != 0 && dataDirs[0].Size != 0 {
|
|
root.Children = append(root.Children, parsePEExports(d, sections, dataDirs[0])...)
|
|
}
|
|
if len(dataDirs) > 1 && dataDirs[1].RVA != 0 && dataDirs[1].Size != 0 {
|
|
root.Children = append(root.Children, parsePEImports(d, sections, dataDirs[1], peIs64)...)
|
|
}
|
|
if len(dataDirs) > 2 && dataDirs[2].RVA != 0 && dataDirs[2].Size != 0 {
|
|
root.Children = append(root.Children, parsePEResources(d, sections, dataDirs[2])...)
|
|
}
|
|
return root, nil
|
|
}
|
|
|
|
type peDataDirectory struct {
|
|
Name string
|
|
RVA uint32
|
|
Size uint32
|
|
}
|
|
|
|
type peSectionInfo struct {
|
|
Name string
|
|
VirtualAddress uint32
|
|
VirtualSize uint32
|
|
RawOffset uint32
|
|
RawSize uint32
|
|
Characteristics uint32
|
|
}
|
|
|
|
func parsePEOptionalHeader(opt []byte, off int64) (*Field, []peDataDirectory) {
|
|
magic := binary.LittleEndian.Uint16(opt[0:2])
|
|
is64 := magic == 0x20b
|
|
name := "PE32"
|
|
if is64 {
|
|
name = "PE32+"
|
|
}
|
|
field := &Field{Name: "optional_header", Type: "optional_header", Offset: off, Length: int64(len(opt)), Value: name}
|
|
if len(opt) >= 28 {
|
|
field.Children = append(field.Children,
|
|
&Field{Name: "Magic", Type: "u16le", Offset: off, Length: 2, Value: fmt.Sprintf("0x%04x (%s)", magic, name)},
|
|
&Field{Name: "AddressOfEntryPoint", Type: "u32le", Offset: off + 16, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[16:20]))},
|
|
&Field{Name: "BaseOfCode", Type: "u32le", Offset: off + 20, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[20:24]))},
|
|
)
|
|
}
|
|
if is64 && len(opt) >= 112 {
|
|
field.Children = append(field.Children,
|
|
&Field{Name: "ImageBase", Type: "u64le", Offset: off + 24, Length: 8, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint64(opt[24:32]))},
|
|
&Field{Name: "SectionAlignment", Type: "u32le", Offset: off + 32, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[32:36]))},
|
|
&Field{Name: "FileAlignment", Type: "u32le", Offset: off + 36, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[36:40]))},
|
|
&Field{Name: "SizeOfImage", Type: "u32le", Offset: off + 56, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[56:60]))},
|
|
&Field{Name: "Subsystem", Type: "u16le", Offset: off + 68, Length: 2, Value: peSubsystem(binary.LittleEndian.Uint16(opt[68:70]))},
|
|
)
|
|
return field, peDataDirectories(opt, off, 112)
|
|
}
|
|
if !is64 && len(opt) >= 96 {
|
|
field.Children = append(field.Children,
|
|
&Field{Name: "ImageBase", Type: "u32le", Offset: off + 28, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[28:32]))},
|
|
&Field{Name: "SectionAlignment", Type: "u32le", Offset: off + 32, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[32:36]))},
|
|
&Field{Name: "FileAlignment", Type: "u32le", Offset: off + 36, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[36:40]))},
|
|
&Field{Name: "SizeOfImage", Type: "u32le", Offset: off + 56, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[56:60]))},
|
|
&Field{Name: "Subsystem", Type: "u16le", Offset: off + 68, Length: 2, Value: peSubsystem(binary.LittleEndian.Uint16(opt[68:70]))},
|
|
)
|
|
return field, peDataDirectories(opt, off, 96)
|
|
}
|
|
return field, nil
|
|
}
|
|
|
|
func peDataDirectories(opt []byte, optOff int64, dirOff int) []peDataDirectory {
|
|
if len(opt) < dirOff {
|
|
return nil
|
|
}
|
|
names := []string{"export", "import", "resource", "exception", "certificate", "base_reloc", "debug", "architecture", "global_ptr", "tls", "load_config", "bound_import", "iat", "delay_import", "clr_runtime", "reserved"}
|
|
count := (len(opt) - dirOff) / 8
|
|
if count > len(names) {
|
|
count = len(names)
|
|
}
|
|
out := make([]peDataDirectory, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
at := dirOff + i*8
|
|
rva := binary.LittleEndian.Uint32(opt[at : at+4])
|
|
size := binary.LittleEndian.Uint32(opt[at+4 : at+8])
|
|
out = append(out, peDataDirectory{Name: names[i], RVA: rva, Size: size})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parsePEImports(d *document.Document, sections []peSectionInfo, dir peDataDirectory, is64 bool) []*Field {
|
|
off, ok := peRVAToOffset(sections, dir.RVA)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out := []*Field{}
|
|
for i := 0; i < 256; i++ {
|
|
at := int64(off) + int64(i*20)
|
|
buf, err := d.Range(at, 20)
|
|
if err != nil || len(buf) < 20 {
|
|
break
|
|
}
|
|
oft := binary.LittleEndian.Uint32(buf[0:4])
|
|
timestamp := binary.LittleEndian.Uint32(buf[4:8])
|
|
forwarder := binary.LittleEndian.Uint32(buf[8:12])
|
|
nameRVA := binary.LittleEndian.Uint32(buf[12:16])
|
|
firstThunk := binary.LittleEndian.Uint32(buf[16:20])
|
|
if oft == 0 && timestamp == 0 && forwarder == 0 && nameRVA == 0 && firstThunk == 0 {
|
|
break
|
|
}
|
|
name := ""
|
|
if nameOff, ok := peRVAToOffset(sections, nameRVA); ok {
|
|
name = readCString(d, int64(nameOff), 256)
|
|
}
|
|
if name == "" {
|
|
name = fmt.Sprintf("rva_0x%x", nameRVA)
|
|
}
|
|
out = append(out, &Field{
|
|
Name: fmt.Sprintf("import[%d]", i),
|
|
Type: "import_descriptor",
|
|
Offset: at,
|
|
Length: 20,
|
|
Value: fmt.Sprintf("dll=%s oft=0x%x first_thunk=0x%x timestamp=0x%x", name, oft, firstThunk, timestamp),
|
|
Children: parsePEImportThunks(d, sections, oft, firstThunk, is64),
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parsePEExports(d *document.Document, sections []peSectionInfo, dir peDataDirectory) []*Field {
|
|
off, ok := peRVAToOffset(sections, dir.RVA)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
buf, err := d.Range(int64(off), 40)
|
|
if err != nil || len(buf) < 40 {
|
|
return nil
|
|
}
|
|
nameRVA := binary.LittleEndian.Uint32(buf[12:16])
|
|
ordinalBase := binary.LittleEndian.Uint32(buf[16:20])
|
|
numberOfFunctions := binary.LittleEndian.Uint32(buf[20:24])
|
|
numberOfNames := binary.LittleEndian.Uint32(buf[24:28])
|
|
functionsRVA := binary.LittleEndian.Uint32(buf[28:32])
|
|
namesRVA := binary.LittleEndian.Uint32(buf[32:36])
|
|
ordinalsRVA := binary.LittleEndian.Uint32(buf[36:40])
|
|
module := ""
|
|
if nameOff, ok := peRVAToOffset(sections, nameRVA); ok {
|
|
module = readCString(d, int64(nameOff), 512)
|
|
}
|
|
if module == "" {
|
|
module = fmt.Sprintf("rva_0x%x", nameRVA)
|
|
}
|
|
root := &Field{
|
|
Name: "exports",
|
|
Type: "export_directory",
|
|
Offset: int64(off),
|
|
Length: 40,
|
|
Value: fmt.Sprintf("module=%s ordinal_base=%d functions=%d names=%d", module, ordinalBase, numberOfFunctions, numberOfNames),
|
|
}
|
|
if numberOfNames > 4096 {
|
|
numberOfNames = 4096
|
|
}
|
|
namesOff, namesOK := peRVAToOffset(sections, namesRVA)
|
|
ordinalsOff, ordOK := peRVAToOffset(sections, ordinalsRVA)
|
|
functionsOff, funcsOK := peRVAToOffset(sections, functionsRVA)
|
|
if namesOK && ordOK && funcsOK {
|
|
for i := uint32(0); i < numberOfNames; i++ {
|
|
namePtrAt := int64(namesOff) + int64(i)*4
|
|
namePtr, err := d.Range(namePtrAt, 4)
|
|
if err != nil || len(namePtr) < 4 {
|
|
break
|
|
}
|
|
symNameRVA := binary.LittleEndian.Uint32(namePtr)
|
|
symName := ""
|
|
if symNameOff, ok := peRVAToOffset(sections, symNameRVA); ok {
|
|
symName = readCString(d, int64(symNameOff), 512)
|
|
}
|
|
if symName == "" {
|
|
symName = fmt.Sprintf("rva_0x%x", symNameRVA)
|
|
}
|
|
ordAt := int64(ordinalsOff) + int64(i)*2
|
|
ordBuf, err := d.Range(ordAt, 2)
|
|
if err != nil || len(ordBuf) < 2 {
|
|
break
|
|
}
|
|
index := uint32(binary.LittleEndian.Uint16(ordBuf))
|
|
if index >= numberOfFunctions {
|
|
continue
|
|
}
|
|
funcAt := int64(functionsOff) + int64(index)*4
|
|
funcBuf, err := d.Range(funcAt, 4)
|
|
if err != nil || len(funcBuf) < 4 {
|
|
break
|
|
}
|
|
funcRVA := binary.LittleEndian.Uint32(funcBuf)
|
|
value := fmt.Sprintf("name=%s ordinal=%d rva=0x%x", symName, ordinalBase+index, funcRVA)
|
|
if funcOff, ok := peRVAToOffset(sections, funcRVA); ok {
|
|
value += fmt.Sprintf(" file=0x%x", funcOff)
|
|
}
|
|
root.Children = append(root.Children, &Field{
|
|
Name: fmt.Sprintf("export[%d] %s", i, symName),
|
|
Type: "export",
|
|
Offset: namePtrAt,
|
|
Length: 4,
|
|
Value: value,
|
|
})
|
|
}
|
|
}
|
|
return []*Field{root}
|
|
}
|
|
|
|
func parsePEResources(d *document.Document, sections []peSectionInfo, dir peDataDirectory) []*Field {
|
|
baseOff, ok := peRVAToOffset(sections, dir.RVA)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
root := &Field{
|
|
Name: "resources",
|
|
Type: "resource_directory",
|
|
Offset: int64(baseOff),
|
|
Length: int64(dir.Size),
|
|
Value: fmt.Sprintf("rva=0x%x size=%d", dir.RVA, dir.Size),
|
|
}
|
|
seen := map[uint32]bool{}
|
|
root.Children = parsePEResourceDirectory(d, sections, dir.RVA, baseOff, 0, 0, seen, "")
|
|
return []*Field{root}
|
|
}
|
|
|
|
func parsePEResourceDirectory(d *document.Document, sections []peSectionInfo, baseRVA, baseOff, rel uint32, depth int, seen map[uint32]bool, path string) []*Field {
|
|
if depth > 8 || seen[rel] {
|
|
return nil
|
|
}
|
|
seen[rel] = true
|
|
defer delete(seen, rel)
|
|
|
|
at := int64(baseOff + rel)
|
|
buf, err := d.Range(at, 16)
|
|
if err != nil || len(buf) < 16 {
|
|
return nil
|
|
}
|
|
named := binary.LittleEndian.Uint16(buf[12:14])
|
|
ids := binary.LittleEndian.Uint16(buf[14:16])
|
|
count := int(named) + int(ids)
|
|
if count > 1024 {
|
|
count = 1024
|
|
}
|
|
header := &Field{
|
|
Name: fmt.Sprintf("directory depth=%d", depth),
|
|
Type: "resource_directory_header",
|
|
Offset: at,
|
|
Length: 16,
|
|
Value: fmt.Sprintf("named=%d ids=%d timestamp=0x%x version=%d.%d", named, ids, binary.LittleEndian.Uint32(buf[4:8]), binary.LittleEndian.Uint16(buf[10:12]), binary.LittleEndian.Uint16(buf[8:10])),
|
|
}
|
|
out := []*Field{header}
|
|
for i := 0; i < count; i++ {
|
|
entryAt := at + 16 + int64(i)*8
|
|
ent, err := d.Range(entryAt, 8)
|
|
if err != nil || len(ent) < 8 {
|
|
break
|
|
}
|
|
nameRaw := binary.LittleEndian.Uint32(ent[0:4])
|
|
targetRaw := binary.LittleEndian.Uint32(ent[4:8])
|
|
name := peResourceName(d, baseOff, nameRaw, depth)
|
|
targetRel := targetRaw & 0x7fffffff
|
|
nextPath := path
|
|
if nextPath == "" {
|
|
nextPath = name
|
|
} else {
|
|
nextPath += "/" + name
|
|
}
|
|
entry := &Field{
|
|
Name: fmt.Sprintf("entry[%d] %s", i, name),
|
|
Type: "resource_entry",
|
|
Offset: entryAt,
|
|
Length: 8,
|
|
Value: fmt.Sprintf("name=0x%x target=0x%x path=%s", nameRaw, targetRaw, nextPath),
|
|
}
|
|
if targetRaw&0x80000000 != 0 {
|
|
entry.Children = parsePEResourceDirectory(d, sections, baseRVA, baseOff, targetRel, depth+1, seen, nextPath)
|
|
} else {
|
|
entry.Children = parsePEResourceDataEntry(d, sections, baseOff, targetRel, nextPath)
|
|
}
|
|
out = append(out, entry)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parsePEResourceDataEntry(d *document.Document, sections []peSectionInfo, baseOff, rel uint32, path string) []*Field {
|
|
at := int64(baseOff + rel)
|
|
buf, err := d.Range(at, 16)
|
|
if err != nil || len(buf) < 16 {
|
|
return nil
|
|
}
|
|
dataRVA := binary.LittleEndian.Uint32(buf[0:4])
|
|
size := binary.LittleEndian.Uint32(buf[4:8])
|
|
codePage := binary.LittleEndian.Uint32(buf[8:12])
|
|
dataOff, mapped := peRVAToOffset(sections, dataRVA)
|
|
value := fmt.Sprintf("data_rva=0x%x size=%d codepage=%d", dataRVA, size, codePage)
|
|
if mapped {
|
|
value += fmt.Sprintf(" file=0x%x", dataOff)
|
|
}
|
|
entry := &Field{Name: "data", Type: "resource_data", Offset: at, Length: 16, Value: value}
|
|
if mapped && size > 0 {
|
|
entry.Children = append(entry.Children, peResourcePayloadFields(d, int64(dataOff), size, path, codePage)...)
|
|
}
|
|
return []*Field{entry}
|
|
}
|
|
|
|
func peResourcePayloadFields(d *document.Document, off int64, size uint32, path string, codePage uint32) []*Field {
|
|
if size > 16<<20 {
|
|
return []*Field{{Name: "payload", Type: "resource_payload", Offset: off, Length: int64(size), Value: "too large to decode"}}
|
|
}
|
|
buf, err := d.Range(off, int64(size))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
out := []*Field{{Name: "payload", Type: "resource_payload", Offset: off, Length: int64(size), Value: fmt.Sprintf("%d bytes", size)}}
|
|
parts := strings.Split(path, "/")
|
|
typ := ""
|
|
if len(parts) > 0 {
|
|
typ = parts[0]
|
|
}
|
|
switch {
|
|
case strings.HasPrefix(typ, "RT_VERSION"):
|
|
out = append(out, decodePEVersionResource(buf, off)...)
|
|
case strings.HasPrefix(typ, "RT_STRING"):
|
|
out = append(out, decodePEStringTable(buf, off)...)
|
|
case strings.HasPrefix(typ, "RT_MANIFEST"):
|
|
out = append(out, &Field{Name: "manifest", Type: "manifest", Offset: off, Length: int64(size), Value: peResourceTextPreview(buf, codePage)})
|
|
default:
|
|
if txt := peResourceTextPreview(buf, codePage); txt != "" {
|
|
out = append(out, &Field{Name: "text_preview", Type: "text", Offset: off, Length: int64(size), Value: txt})
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func peResourceName(d *document.Document, baseOff, raw uint32, depth int) string {
|
|
if raw&0x80000000 != 0 {
|
|
rel := raw & 0x7fffffff
|
|
buf, err := d.Range(int64(baseOff+rel), 2)
|
|
if err != nil || len(buf) < 2 {
|
|
return fmt.Sprintf("name@0x%x", rel)
|
|
}
|
|
n := int(binary.LittleEndian.Uint16(buf))
|
|
if n <= 0 || n > 512 {
|
|
return fmt.Sprintf("name@0x%x", rel)
|
|
}
|
|
rawName, err := d.Range(int64(baseOff+rel+2), int64(n*2))
|
|
if err != nil || len(rawName) < n*2 {
|
|
return fmt.Sprintf("name@0x%x", rel)
|
|
}
|
|
return decodeUTF16LE(rawName)
|
|
}
|
|
id := raw & 0xffff
|
|
if depth == 0 {
|
|
return peResourceTypeName(id)
|
|
}
|
|
if depth == 2 {
|
|
return peLanguageName(id)
|
|
}
|
|
return fmt.Sprintf("#%d", id)
|
|
}
|
|
|
|
func peResourceTypeName(id uint32) string {
|
|
names := map[uint32]string{
|
|
1: "RT_CURSOR", 2: "RT_BITMAP", 3: "RT_ICON", 4: "RT_MENU", 5: "RT_DIALOG", 6: "RT_STRING",
|
|
7: "RT_FONTDIR", 8: "RT_FONT", 9: "RT_ACCELERATOR", 10: "RT_RCDATA", 11: "RT_MESSAGETABLE",
|
|
12: "RT_GROUP_CURSOR", 14: "RT_GROUP_ICON", 16: "RT_VERSION", 17: "RT_DLGINCLUDE",
|
|
19: "RT_PLUGPLAY", 20: "RT_VXD", 21: "RT_ANICURSOR", 22: "RT_ANIICON", 23: "RT_HTML", 24: "RT_MANIFEST",
|
|
}
|
|
if name, ok := names[id]; ok {
|
|
return fmt.Sprintf("%s(%d)", name, id)
|
|
}
|
|
return fmt.Sprintf("#%d", id)
|
|
}
|
|
|
|
func peLanguageName(id uint32) string {
|
|
switch id {
|
|
case 0:
|
|
return "LANG_NEUTRAL"
|
|
case 0x0409:
|
|
return "en-US"
|
|
case 0x0809:
|
|
return "en-GB"
|
|
}
|
|
return fmt.Sprintf("lang-0x%04x", id)
|
|
}
|
|
|
|
func decodePEStringTable(buf []byte, off int64) []*Field {
|
|
out := []*Field{}
|
|
pos := 0
|
|
for i := 0; i < 16 && pos+2 <= len(buf); i++ {
|
|
n := int(binary.LittleEndian.Uint16(buf[pos : pos+2]))
|
|
pos += 2
|
|
if pos+n*2 > len(buf) {
|
|
break
|
|
}
|
|
if n > 0 {
|
|
out = append(out, &Field{Name: fmt.Sprintf("string[%d]", i), Type: "resource_string", Offset: off + int64(pos), Length: int64(n * 2), Value: decodeUTF16LE(buf[pos : pos+n*2])})
|
|
}
|
|
pos += n * 2
|
|
}
|
|
return out
|
|
}
|
|
|
|
func decodePEVersionResource(buf []byte, off int64) []*Field {
|
|
if len(buf) < 6 {
|
|
return nil
|
|
}
|
|
key, keyEnd := readPEVersionKey(buf, 6)
|
|
out := []*Field{{Name: "version_info", Type: "version_info", Offset: off, Length: int64(len(buf)), Value: fmt.Sprintf("key=%s length=%d valueLength=%d type=%d", key, binary.LittleEndian.Uint16(buf[0:2]), binary.LittleEndian.Uint16(buf[2:4]), binary.LittleEndian.Uint16(buf[4:6]))}}
|
|
valueOff := align4(keyEnd)
|
|
if key == "VS_VERSION_INFO" && valueOff+52 <= len(buf) {
|
|
fixed := buf[valueOff : valueOff+52]
|
|
if binary.LittleEndian.Uint32(fixed[0:4]) == 0xfeef04bd {
|
|
fileMS := binary.LittleEndian.Uint32(fixed[8:12])
|
|
fileLS := binary.LittleEndian.Uint32(fixed[12:16])
|
|
prodMS := binary.LittleEndian.Uint32(fixed[16:20])
|
|
prodLS := binary.LittleEndian.Uint32(fixed[20:24])
|
|
out = append(out, &Field{Name: "fixed_file_info", Type: "version_fixed", Offset: off + int64(valueOff), Length: 52, Value: fmt.Sprintf("file=%d.%d.%d.%d product=%d.%d.%d.%d flags=0x%x type=0x%x subtype=0x%x",
|
|
fileMS>>16, fileMS&0xffff, fileLS>>16, fileLS&0xffff,
|
|
prodMS>>16, prodMS&0xffff, prodLS>>16, prodLS&0xffff,
|
|
binary.LittleEndian.Uint32(fixed[28:32]), binary.LittleEndian.Uint32(fixed[36:40]), binary.LittleEndian.Uint32(fixed[40:44]))})
|
|
}
|
|
}
|
|
if strings.Contains(string(buf), "StringFileInfo") || strings.Contains(string(buf), "VarFileInfo") {
|
|
text := decodeUTF16LEASCII(buf)
|
|
for _, label := range []string{"CompanyName", "FileDescription", "FileVersion", "ProductName", "ProductVersion", "OriginalFilename"} {
|
|
if strings.Contains(text, label) {
|
|
out = append(out, &Field{Name: label, Type: "version_string_hint", Offset: off, Length: int64(len(buf)), Value: label})
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func readPEVersionKey(buf []byte, pos int) (string, int) {
|
|
start := pos
|
|
for pos+1 < len(buf) {
|
|
if buf[pos] == 0 && buf[pos+1] == 0 {
|
|
return decodeUTF16LE(buf[start:pos]), pos + 2
|
|
}
|
|
pos += 2
|
|
}
|
|
return "", pos
|
|
}
|
|
|
|
func align4(n int) int {
|
|
return (n + 3) &^ 3
|
|
}
|
|
|
|
func peResourceTextPreview(buf []byte, codePage uint32) string {
|
|
if len(buf) == 0 {
|
|
return ""
|
|
}
|
|
if len(buf) >= 2 {
|
|
if buf[0] == 0xff && buf[1] == 0xfe {
|
|
return cleanResourcePreview(decodeUTF16LE(buf[2:]))
|
|
}
|
|
if buf[0] == 0xfe && buf[1] == 0xff {
|
|
return cleanResourcePreview(decodeUTF16BE(buf[2:]))
|
|
}
|
|
}
|
|
if codePage == 1200 {
|
|
return cleanResourcePreview(decodeUTF16LE(buf))
|
|
}
|
|
if utf8.Valid(buf) {
|
|
return cleanResourcePreview(string(buf))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func cleanResourcePreview(s string) string {
|
|
s = strings.TrimSpace(strings.ReplaceAll(s, "\x00", ""))
|
|
if len(s) > 512 {
|
|
s = s[:512] + "..."
|
|
}
|
|
if strings.IndexFunc(s, func(r rune) bool { return r < 0x09 }) >= 0 {
|
|
return ""
|
|
}
|
|
return s
|
|
}
|
|
|
|
func decodeUTF16LE(buf []byte) string {
|
|
u := make([]uint16, 0, len(buf)/2)
|
|
for i := 0; i+1 < len(buf); i += 2 {
|
|
u = append(u, binary.LittleEndian.Uint16(buf[i:i+2]))
|
|
}
|
|
return string(utf16.Decode(u))
|
|
}
|
|
|
|
func decodeUTF16BE(buf []byte) string {
|
|
u := make([]uint16, 0, len(buf)/2)
|
|
for i := 0; i+1 < len(buf); i += 2 {
|
|
u = append(u, binary.BigEndian.Uint16(buf[i:i+2]))
|
|
}
|
|
return string(utf16.Decode(u))
|
|
}
|
|
|
|
func decodeUTF16LEASCII(buf []byte) string {
|
|
var b strings.Builder
|
|
for i := 0; i+1 < len(buf); i += 2 {
|
|
r := binary.LittleEndian.Uint16(buf[i : i+2])
|
|
if r >= 0x20 && r < 0x7f {
|
|
b.WriteByte(byte(r))
|
|
} else {
|
|
b.WriteByte(' ')
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func parsePEImportThunks(d *document.Document, sections []peSectionInfo, oft, firstThunk uint32, is64 bool) []*Field {
|
|
thunkRVA := oft
|
|
if thunkRVA == 0 {
|
|
thunkRVA = firstThunk
|
|
}
|
|
thunkOff, ok := peRVAToOffset(sections, thunkRVA)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
width := int64(4)
|
|
ordinalMask := uint64(0x80000000)
|
|
if is64 {
|
|
width = 8
|
|
ordinalMask = 0x8000000000000000
|
|
}
|
|
out := []*Field{}
|
|
for i := 0; i < 4096; i++ {
|
|
at := int64(thunkOff) + int64(i)*width
|
|
buf, err := d.Range(at, width)
|
|
if err != nil || int64(len(buf)) < width {
|
|
break
|
|
}
|
|
var thunk uint64
|
|
if is64 {
|
|
thunk = binary.LittleEndian.Uint64(buf)
|
|
} else {
|
|
thunk = uint64(binary.LittleEndian.Uint32(buf))
|
|
}
|
|
if thunk == 0 {
|
|
break
|
|
}
|
|
value := ""
|
|
if thunk&ordinalMask != 0 {
|
|
value = fmt.Sprintf("ordinal=%d", thunk&0xffff)
|
|
} else if nameOff, ok := peRVAToOffset(sections, uint32(thunk)); ok {
|
|
name := readImportByName(d, int64(nameOff))
|
|
if name != "" {
|
|
value = "name=" + name
|
|
} else {
|
|
value = fmt.Sprintf("name_rva=0x%x", thunk)
|
|
}
|
|
} else {
|
|
value = fmt.Sprintf("thunk=0x%x", thunk)
|
|
}
|
|
out = append(out, &Field{
|
|
Name: fmt.Sprintf("thunk[%d]", i),
|
|
Type: "import_thunk",
|
|
Offset: at,
|
|
Length: width,
|
|
Value: value,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func readImportByName(d *document.Document, off int64) string {
|
|
buf, err := d.Range(off, 258)
|
|
if err != nil || len(buf) < 3 {
|
|
return ""
|
|
}
|
|
return readCStringFrom(buf[2:])
|
|
}
|
|
|
|
func peRVAToOffset(sections []peSectionInfo, rva uint32) (uint32, bool) {
|
|
for _, s := range sections {
|
|
span := s.VirtualSize
|
|
if s.RawSize > span {
|
|
span = s.RawSize
|
|
}
|
|
if span == 0 {
|
|
continue
|
|
}
|
|
if rva >= s.VirtualAddress && rva < s.VirtualAddress+span {
|
|
return s.RawOffset + (rva - s.VirtualAddress), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func readCString(d *document.Document, off int64, max int64) string {
|
|
buf, err := d.Range(off, max)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return readCStringFrom(buf)
|
|
}
|
|
|
|
func readCStringFrom(buf []byte) string {
|
|
for i, b := range buf {
|
|
if b == 0 {
|
|
return string(buf[:i])
|
|
}
|
|
}
|
|
return string(buf)
|
|
}
|
|
|
|
func peSubsystem(v uint16) string {
|
|
switch v {
|
|
case 1:
|
|
return "native"
|
|
case 2:
|
|
return "windows_gui"
|
|
case 3:
|
|
return "windows_cui"
|
|
case 7:
|
|
return "posix_cui"
|
|
case 9:
|
|
return "windows_ce_gui"
|
|
case 10:
|
|
return "efi_application"
|
|
case 11:
|
|
return "efi_boot_service_driver"
|
|
case 12:
|
|
return "efi_runtime_driver"
|
|
}
|
|
return fmt.Sprintf("0x%x", v)
|
|
}
|
|
|
|
func peMachine(m uint16) string {
|
|
switch m {
|
|
case 0x14c:
|
|
return "i386"
|
|
case 0x8664:
|
|
return "x86_64"
|
|
case 0x1c0:
|
|
return "ARM"
|
|
case 0xaa64:
|
|
return "ARM64"
|
|
case 0x200:
|
|
return "IA64"
|
|
}
|
|
return "?"
|
|
}
|
|
|
|
func trimZero(b []byte) []byte {
|
|
for i, c := range b {
|
|
if c == 0 {
|
|
return b[:i]
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
// ParseRIFF walks RIFF chunks.
|
|
func ParseRIFF(d *document.Document) (*Field, error) {
|
|
size := d.Size()
|
|
if size < 12 {
|
|
return nil, errors.New("too short")
|
|
}
|
|
hdr, err := d.Range(0, 12)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
total := int64(binary.LittleEndian.Uint32(hdr[4:8]))
|
|
form := string(hdr[8:12])
|
|
end := total + 8
|
|
if end > size {
|
|
end = size
|
|
}
|
|
root := &Field{
|
|
Name: "RIFF", Type: "container", Offset: 0, Length: total + 8,
|
|
Value: fmt.Sprintf("form=%s (%s)", form, riffFormName(form)),
|
|
}
|
|
root.Children = riffChunks(d, 12, end, 0)
|
|
return root, nil
|
|
}
|
|
|
|
// riffChunks walks the chunks in [off,end), recursing into LIST containers and
|
|
// decoding the common WAV/AVI chunks.
|
|
func riffChunks(d *document.Document, off, end int64, depth int) []*Field {
|
|
var out []*Field
|
|
for off+8 <= end && depth < 16 {
|
|
ch, err := d.Range(off, 8)
|
|
if err != nil || len(ch) < 8 {
|
|
break
|
|
}
|
|
id := string(ch[0:4])
|
|
ln := int64(binary.LittleEndian.Uint32(ch[4:8]))
|
|
body := off + 8
|
|
chunkEnd := body + ln
|
|
if chunkEnd > end {
|
|
chunkEnd = end
|
|
}
|
|
f := &Field{Name: riffChunkLabel(id), Type: "chunk", Offset: off, Length: 8 + ln, Value: fmt.Sprintf("length=%d", ln)}
|
|
switch id {
|
|
case "LIST", "RIFF":
|
|
if ln >= 4 {
|
|
lb, _ := d.Range(body, 4)
|
|
lt := string(lb)
|
|
f.Type = "list"
|
|
f.Value = fmt.Sprintf("type=%s (%s)", lt, riffFormName(lt))
|
|
f.Children = riffChunks(d, body+4, chunkEnd, depth+1)
|
|
}
|
|
case "fmt ":
|
|
if fb, e := d.Range(body, min64(16, ln)); e == nil && len(fb) >= 16 {
|
|
f.Children = []*Field{
|
|
{Name: "audio_format", Type: "u16le", Offset: body, Length: 2, Value: wavFormat(binary.LittleEndian.Uint16(fb[0:2]))},
|
|
{Name: "channels", Type: "u16le", Offset: body + 2, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[2:4]))},
|
|
{Name: "sample_rate", Type: "u32le", Offset: body + 4, Length: 4, Value: fmt.Sprintf("%d Hz", binary.LittleEndian.Uint32(fb[4:8]))},
|
|
{Name: "byte_rate", Type: "u32le", Offset: body + 8, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(fb[8:12]))},
|
|
{Name: "block_align", Type: "u16le", Offset: body + 12, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[12:14]))},
|
|
{Name: "bits_per_sample", Type: "u16le", Offset: body + 14, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[14:16]))},
|
|
}
|
|
}
|
|
case "avih":
|
|
if ab, e := d.Range(body, min64(40, ln)); e == nil && len(ab) >= 16 {
|
|
f.Children = []*Field{
|
|
{Name: "microsec_per_frame", Type: "u32le", Offset: body, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[0:4]))},
|
|
{Name: "max_bytes_per_sec", Type: "u32le", Offset: body + 4, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[4:8]))},
|
|
{Name: "total_frames", Type: "u32le", Offset: body + 16, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[16:20]))},
|
|
}
|
|
}
|
|
}
|
|
out = append(out, f)
|
|
off = chunkEnd
|
|
if ln%2 != 0 {
|
|
off++ // chunks are word-aligned
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func riffFormName(f string) string {
|
|
switch f {
|
|
case "WAVE":
|
|
return "audio/wav"
|
|
case "AVI ":
|
|
return "video/avi"
|
|
case "WEBP":
|
|
return "image/webp"
|
|
case "ANI ":
|
|
return "animated cursor"
|
|
case "RMID":
|
|
return "MIDI"
|
|
case "INFO":
|
|
return "metadata"
|
|
case "movi":
|
|
return "movie data"
|
|
case "hdrl":
|
|
return "header list"
|
|
case "strl":
|
|
return "stream list"
|
|
default:
|
|
return "?"
|
|
}
|
|
}
|
|
|
|
func riffChunkLabel(id string) string {
|
|
return strings.TrimRight(id, " ")
|
|
}
|
|
|
|
func wavFormat(f uint16) string {
|
|
names := map[uint16]string{1: "PCM", 2: "ADPCM", 3: "IEEE float", 6: "A-law", 7: "mu-law", 0xfffe: "extensible"}
|
|
if n, ok := names[f]; ok {
|
|
return fmt.Sprintf("%d (%s)", f, n)
|
|
}
|
|
return fmt.Sprintf("%d", f)
|
|
}
|
|
|
|
func min64(a, b int64) int64 {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|