178 lines
4.8 KiB
Go
178 lines
4.8 KiB
Go
// Package binsnoop identifies and optionally extracts files embedded inside binary data.
|
|
//
|
|
// It is a Go port of the binwalk scanning engine, implementing all magic byte signatures,
|
|
// structure parsers, and the Aho-Corasick based multi-pattern matching algorithm.
|
|
//
|
|
// # Quick Start
|
|
//
|
|
// scanner := binsnoop.New()
|
|
// data, _ := os.ReadFile("firmware.bin")
|
|
// results := scanner.Scan(data)
|
|
// for _, r := range results {
|
|
// fmt.Printf("0x%08X %s\n", r.Offset, r.Description)
|
|
// }
|
|
//
|
|
// # Extraction
|
|
//
|
|
// // Extract a specific result
|
|
// err := binsnoop.CarveResult(data, results[0], "/tmp/output")
|
|
//
|
|
// # Filtering
|
|
//
|
|
// // Only scan for specific signatures
|
|
// scanner := binsnoop.NewWithOptions(Options{Include: []string{"gzip", "squashfs"}})
|
|
package binsnoop
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Options configures the Scanner.
|
|
type Options struct {
|
|
// Include restricts scanning to only these signature names.
|
|
Include []string
|
|
// Exclude removes these signature names from scanning.
|
|
Exclude []string
|
|
// FullSearch makes short signatures match at any offset, not just offset 0.
|
|
FullSearch bool
|
|
// AdditionalSignatures adds user-defined signatures to the scanner.
|
|
AdditionalSignatures []Signature
|
|
}
|
|
|
|
// New creates a Scanner with all default signatures enabled.
|
|
func New() *Scanner {
|
|
s, _ := NewWithOptions(Options{})
|
|
return s
|
|
}
|
|
|
|
// NewWithOptions creates a Scanner with the specified options.
|
|
func NewWithOptions(opts Options) (*Scanner, error) {
|
|
s := &Scanner{
|
|
patternSigTable: make(map[int]Signature),
|
|
extractorLookup: make(map[string]*Extractor),
|
|
}
|
|
|
|
allSigs := AllSignatures()
|
|
if opts.AdditionalSignatures != nil {
|
|
allSigs = append(allSigs, opts.AdditionalSignatures...)
|
|
}
|
|
|
|
for _, sig := range allSigs {
|
|
if !includeSig(&sig, opts.Include, opts.Exclude) {
|
|
continue
|
|
}
|
|
|
|
s.signatureCount++
|
|
s.patternCount += len(sig.Magic)
|
|
|
|
// Register extractor
|
|
var ext *Extractor
|
|
if sig.Extractor != nil {
|
|
extCopy := *sig.Extractor
|
|
ext = &extCopy
|
|
}
|
|
s.extractorLookup[sig.Name] = ext
|
|
|
|
for _, pattern := range sig.Magic {
|
|
if sig.Short && !opts.FullSearch {
|
|
s.shortSignatures = append(s.shortSignatures, sig)
|
|
break
|
|
}
|
|
s.patternSigTable[len(s.patterns)] = sig
|
|
s.patterns = append(s.patterns, pattern)
|
|
}
|
|
}
|
|
|
|
s.compile()
|
|
return s, nil
|
|
}
|
|
|
|
// ScanBytes scans raw bytes for signatures and returns all matches.
|
|
func (s *Scanner) ScanBytes(data []byte) []SignatureResult {
|
|
return s.Scan(data)
|
|
}
|
|
|
|
// ScanFile reads a file from disk and scans it for signatures.
|
|
func (s *Scanner) ScanFile(path string) ([]SignatureResult, error) {
|
|
data, err := ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("binsnoop: read file: %w", err)
|
|
}
|
|
return s.Scan(data), nil
|
|
}
|
|
|
|
// AnalyzeBytes scans and optionally extracts from raw bytes.
|
|
func (s *Scanner) AnalyzeBytes(data []byte, filePath string, extract bool) *AnalysisResults {
|
|
results := &AnalysisResults{
|
|
FilePath: filePath,
|
|
Extractions: make(map[string]*ExtractionResult),
|
|
}
|
|
|
|
results.FileMap = s.Scan(data)
|
|
|
|
if extract && len(results.FileMap) > 0 {
|
|
results.Extractions = s.Extract(data, filePath, results.FileMap)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// AnalyzeFile reads a file from disk, scans, and optionally extracts.
|
|
func (s *Scanner) AnalyzeFile(path string, extract bool) (*AnalysisResults, error) {
|
|
data, err := ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("binsnoop: read file: %w", err)
|
|
}
|
|
return s.AnalyzeBytes(data, path, extract), nil
|
|
}
|
|
|
|
// CarveResult extracts raw bytes for a specific signature result to a file.
|
|
func CarveResult(data []byte, result SignatureResult, outputDir string) error {
|
|
if result.Offset < 0 || result.Offset+result.Size > len(data) {
|
|
return fmt.Errorf("binsnoop: result offset/size exceeds data bounds")
|
|
}
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
return fmt.Errorf("binsnoop: mkdir: %w", err)
|
|
}
|
|
outPath := filepath.Join(outputDir, fmt.Sprintf("%x_%s.bin", result.Offset, result.Name))
|
|
return os.WriteFile(outPath, data[result.Offset:result.Offset+result.Size], 0644)
|
|
}
|
|
|
|
// SignatureCount returns the number of loaded signatures.
|
|
func (s *Scanner) SignatureCount() int { return s.signatureCount }
|
|
|
|
// PatternCount returns the total number of magic byte patterns.
|
|
func (s *Scanner) PatternCount() int { return s.patternCount }
|
|
|
|
// ListSignatures returns the names of all available signatures.
|
|
func ListSignatures() []string {
|
|
sigs := AllSignatures()
|
|
names := make([]string, len(sigs))
|
|
for i, s := range sigs {
|
|
names[i] = s.Name
|
|
}
|
|
return names
|
|
}
|
|
|
|
func includeSig(sig *Signature, include, exclude []string) bool {
|
|
if len(include) > 0 {
|
|
for _, inc := range include {
|
|
if strings.EqualFold(sig.Name, inc) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if len(exclude) > 0 {
|
|
for _, exc := range exclude {
|
|
if strings.EqualFold(sig.Name, exc) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|