Files
nexus/internal/leviathan/binsnoop/sig_firmware.go
T
2026-06-16 11:51:07 +01:00

1334 lines
43 KiB
Go

package binsnoop
import (
"encoding/hex"
"fmt"
)
// ============================================================================
// uImage
// ============================================================================
const uimageDescription = "uImage firmware image"
func uimageMagic() [][]byte {
return [][]byte{
{0x27, 0x05, 0x19, 0x56},
[]byte("OKLI"),
}
}
func uimageParser(data []byte, offset int) (*SignatureResult, error) {
if offset+64 > len(data) {
return nil, fmt.Errorf("insufficient data for uImage header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_crc", "u32"}, {"timestamp", "u32"},
{"data_size", "u32"}, {"load_address", "u32"}, {"entry_point", "u32"},
{"data_crc", "u32"}, {"os_type", "u8"}, {"cpu_type", "u8"},
{"image_type", "u8"}, {"compression_type", "u8"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := 64
dataSize := parsed["data_size"]
if dataSize == 0 {
return nil, fmt.Errorf("uImage data size is 0")
}
totalSize := headerSize + dataSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("uImage extends beyond EOF")
}
// Validate header CRC: zero out the header_crc field, calc CRC32 over 64 bytes
headerCopy := make([]byte, headerSize)
copy(headerCopy, hdr[:headerSize])
headerCopy[4] = 0
headerCopy[5] = 0
headerCopy[6] = 0
headerCopy[7] = 0
calcCRC := CRC32(headerCopy)
headerCRCValid := calcCRC == uint32(parsed["header_crc"])
osMap := map[int]string{0: "OpenBSD", 1: "NetBSD", 2: "FreeBSD", 3: "4.4BSD", 4: "Linux", 5: "SVR4", 6: "Esix", 7: "Solaris", 8: "Irix", 9: "SCO", 10: "Dell", 11: "NCR", 12: "LynxOS", 13: "VxWorks", 14: "pSOS", 15: "QNX", 16: "U-Boot", 17: "RTEMS", 18: "ARTOS", 19: "Unity OS", 20: "INTEGRITY", 21: "OSE", 22: "Plan 9", 23: "OpenRTOS", 24: "ARM Trusted Firmware", 25: "Trusted Execution Environment", 26: "RISC-V OpenSBI", 27: "EFI Firmware"}
cpuMap := map[int]string{0: "Invalid", 1: "Alpha", 2: "ARM", 3: "Intel x86", 4: "IA64", 5: "MIPS", 6: "MIPS 64-bit", 7: "PowerPC", 8: "IBM S390", 9: "SuperH", 10: "Sparc", 11: "Sparc 64-bit", 12: "M68K", 13: "Nios-32", 14: "MicroBlaze", 15: "Nios-II", 16: "Blackfin", 17: "AVR32", 18: "ST200", 19: "Sandbox", 20: "NDS32", 21: "OpenRISC 1000", 22: "ARM64", 23: "ARC", 24: "x86_64", 25: "Xtensa", 26: "RISC-V"}
compMap := map[int]string{0: "none", 1: "gzip", 2: "bzip2", 3: "lzma", 4: "lzo", 5: "lz4", 6: "zstd"}
imgMap := map[int]string{0: "Invalid", 1: "Standalone", 2: "Kernel", 3: "RAMDisk", 4: "Multi-File", 5: "Firmware", 6: "Script", 7: "Filesystem", 8: "Flat DT", 9: "Kwb", 10: "IMX", 11: "AIS", 12: "Kernel (no load)", 13: "PBL", 14: "MXS", 15: "GP Header", 16: "ATMEL", 17: "IMX (Silicon Labs)", 18: "TEE", 19: "FW IVT", 20: "PMC", 21: "STM32", 22: "Renesas SPKG", 23: "ST SunXI", 24: "FDT Legacy"}
osType := lookupMap(osMap, parsed["os_type"], "Unknown")
cpuType := lookupMap(cpuMap, parsed["cpu_type"], "Unknown")
compType := lookupMap(compMap, parsed["compression_type"], "Unknown")
imageType := lookupMap(imgMap, parsed["image_type"], "Unknown")
name := GetCString(hdr[32:64])
confidence := ConfidenceHigh
desc := fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes, compression: %s, CPU: %s, OS: %s, image type: %s, load address: 0x%X, entry point: 0x%X, creation time: %s, image name: \"%s\"",
uimageDescription, headerSize, dataSize, compType, cpuType, osType, imageType,
parsed["load_address"], parsed["entry_point"],
EpochToString(uint32(parsed["timestamp"])), name)
if !headerCRCValid {
if offset == 0 {
confidence = ConfidenceMedium
} else {
confidence = ConfidenceLow
}
desc += ", invalid checksum"
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: desc,
Confidence: confidence,
ExtractionDeclined: !headerCRCValid || dataSize == 0,
}, nil
}
// ============================================================================
// U-Boot version string
// ============================================================================
const ubootDescription = "U-Boot version string"
func ubootMagic() [][]byte {
return [][]byte{[]byte("U-Boot ")}
}
func ubootParser(data []byte, offset int) (*SignatureResult, error) {
numOff := offset + 7
if numOff >= len(data) {
return nil, fmt.Errorf("insufficient data")
}
if !IsASCIINumber(data[numOff]) {
return nil, fmt.Errorf("expected version number")
}
ver := GetCString(data[numOff:])
if len(ver) == 0 {
return nil, fmt.Errorf("empty version string")
}
if len(ver) > 100 {
ver = ver[:100]
}
return &SignatureResult{
Offset: offset,
Size: len(ver),
Description: fmt.Sprintf("%s: %s", ubootDescription, ver),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// TRX
// ============================================================================
const trxDescription = "TRX firmware image"
func trxMagic() [][]byte {
return [][]byte{[]byte("HDR0")}
}
func trxParser(data []byte, offset int) (*SignatureResult, error) {
if offset+28 > len(data) {
return nil, fmt.Errorf("insufficient data for TRX header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"total_size", "u32"}, {"header_crc", "u32"},
{"version", "u16"}, {"flags", "u16"},
{"partition1", "u32"}, {"partition2", "u32"}, {"partition3", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
totalSize := parsed["total_size"]
if totalSize <= 28 || offset+totalSize > len(data) {
return nil, fmt.Errorf("invalid TRX size")
}
// CRC32 validation (over data after the CRC field)
if offset+12 < len(data) {
calcCRC := CRC32(hdr[12:totalSize])
if int(calcCRC) != parsed["header_crc"] {
return nil, fmt.Errorf("TRX CRC mismatch")
}
}
partCount := 0
for _, p := range []string{"partition1", "partition2", "partition3"} {
if parsed[p] > 0 {
partCount++
}
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, version %d, partition count: %d, header size: 28 bytes, total size: %d bytes", trxDescription, parsed["version"], partCount, totalSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// TP-Link firmware header
// ============================================================================
const tplinkDescription = "TP-Link firmware header"
func tplinkMagic() [][]byte {
return [][]byte{
{0x01, 0x00, 0x00, 0x00, 0x54, 0x50, 0x2D, 0x4C, 0x49, 0x4E, 0x4B, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6E, 0x6F, 0x6C, 0x6F, 0x67, 0x69, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x76, 0x65, 0x72, 0x2E, 0x20, 0x31, 0x2E, 0x30},
}
}
func tplinkParser(data []byte, offset int) (*SignatureResult, error) {
if offset+0x200 > len(data) {
return nil, fmt.Errorf("insufficient data for TP-Link header")
}
hdr := data[offset:]
fields := []StructField{
{"version", "u32"}, {"fw_version", "u32"}, {"hw_id", "u32"}, {"hw_rev", "u32"},
{"unk1", "u32"}, {"kernel_data_offset", "u32"}, {"kernel_load_address", "u32"},
{"kernel_entry_point", "u32"}, {"kernel_data_length", "u32"},
}
parsed, err := Parse(hdr[0x40:], fields, "big")
if err != nil {
return nil, err
}
headerSize := 0x200
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, kernel load address: 0x%X, kernel entry point: 0x%X, header size: %d bytes", tplinkDescription, parsed["kernel_load_address"], parsed["kernel_entry_point"], headerSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// TP-Link RTOS firmware
// ============================================================================
const tplinkRTOSDescription = "TP-Link RTOS firmware"
func tplinkRTOSMagic() [][]byte {
return [][]byte{{0x00, 0x14, 0x2F, 0xC0}}
}
func tplinkRTOSParser(data []byte, offset int) (*SignatureResult, error) {
if offset+0x200 > len(data) {
return nil, fmt.Errorf("insufficient data for TP-Link RTOS header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"total_size", "u32"},
{"model_number", "u32"}, {"hw_rev_major", "u32"}, {"hw_rev_minor", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
return &SignatureResult{
Offset: offset,
Size: parsed["header_size"],
Description: fmt.Sprintf("%s, model number: %X, hardware version: %X.%X, header size: %d bytes, total size: %d bytes", tplinkRTOSDescription, parsed["model_number"], parsed["hw_rev_major"], parsed["hw_rev_minor"], parsed["header_size"], parsed["total_size"]),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// DLOB firmware header
// ============================================================================
const dlobDescription = "DLOB firmware header"
func dlobMagic() [][]byte {
return [][]byte{{0x5e, 0xa3, 0xa4, 0x17}}
}
func dlobParser(data []byte, offset int) (*SignatureResult, error) {
if offset+16 > len(data) {
return nil, fmt.Errorf("insufficient data for DLOB header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
availableData := len(data) - offset
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
if availableData < headerSize+dataSize {
return nil, fmt.Errorf("DLOB size exceeds available data")
}
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d", dlobDescription, headerSize, dataSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// Arcadyan obfuscated LZMA
// ============================================================================
const arcadyanDescription = "Arcadyan obfuscated LZMA"
func arcadyanMagic() [][]byte {
return [][]byte{{0x00, 0xD5, 0x08, 0x00}}
}
func arcadyanParser(data []byte, offset int) (*SignatureResult, error) {
const magicOffset = 0x68
if offset < magicOffset {
return nil, fmt.Errorf("arcadyan magic offset too small")
}
startOffset := offset - magicOffset
return &SignatureResult{
Offset: startOffset,
Description: arcadyanDescription,
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// CHK firmware header (Netgear)
// ============================================================================
const chkDescription = "CHK firmware header"
func chkMagic() [][]byte {
return [][]byte{{0x2A, 0x23, 0x24, 0x5E}}
}
func chkParser(data []byte, offset int) (*SignatureResult, error) {
if offset+40 > len(data) {
return nil, fmt.Errorf("insufficient data for CHK header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"reserved", "u32"},
{"kernel_checksum", "u32"}, {"rootfs_checksum", "u32"},
{"kernel_size", "u32"}, {"rootfs_size", "u32"},
{"image_checksum", "u32"}, {"header_checksum", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
kernelSize := parsed["kernel_size"]
rootfsSize := parsed["rootfs_size"]
totalSize := headerSize + kernelSize + rootfsSize
availableData := len(data) - offset
if availableData < totalSize || totalSize <= headerSize {
return nil, fmt.Errorf("invalid CHK sizes")
}
boardID := GetCString(hdr[40:])
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, board ID: %s, header size: %d bytes, data size: %d bytes", chkDescription, boardID, headerSize, kernelSize+rootfsSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// SEAMA firmware header
// ============================================================================
const seamaDescription = "SEAMA firmware header"
func seamaMagic() [][]byte {
return [][]byte{
{0x5E, 0xA3, 0xA4, 0x17, 0x00, 0x00},
{0x17, 0xA4, 0xA3, 0x5E, 0x00, 0x00},
}
}
func seamaParser(data []byte, offset int) (*SignatureResult, error) {
if offset+12 > len(data) {
return nil, fmt.Errorf("insufficient data for SEAMA header")
}
hdr := data[offset:]
endianness := "big"
if hdr[0] == 0x17 {
endianness = "little"
}
fields := []StructField{
{"magic", "u32"}, {"meta_size", "u16"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr, fields, endianness)
if err != nil {
return nil, err
}
headerSize := 12 + parsed["meta_size"]
dataSize := parsed["data_size"]
totalSize := headerSize + dataSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("SEAMA size exceeds data")
}
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", seamaDescription, headerSize, dataSize),
Confidence: ConfidenceLow,
}, nil
}
// ============================================================================
// CFE bootloader
// ============================================================================
const cfeDescription = "CFE bootloader"
func cfeMagic() [][]byte {
return [][]byte{[]byte("CFE1CFE1")}
}
func cfeParser(_ []byte, offset int) (*SignatureResult, error) {
const cfeMagicOffset = 28
if offset < cfeMagicOffset {
return nil, fmt.Errorf("CFE magic offset too small")
}
confidence := ConfidenceLow
startOffset := offset - cfeMagicOffset
if startOffset == 0 {
confidence = ConfidenceMedium
}
return &SignatureResult{
Offset: startOffset,
Description: cfeDescription,
Confidence: confidence,
}, nil
}
// ============================================================================
// JBOOT ARM firmware
// ============================================================================
const jbootARMDescription = "JBOOT firmware header"
func jbootARMMagic() [][]byte {
return [][]byte{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x48},
}
}
func jbootARMParser(data []byte, offset int) (*SignatureResult, error) {
const magicOff = 48
if offset < magicOff {
return nil, fmt.Errorf("JBOOT ARM magic offset too small")
}
headerStart := offset - magicOff
if headerStart+80 > len(data) {
return nil, fmt.Errorf("insufficient data for JBOOT ARM header")
}
hdr := data[headerStart:]
fields := []StructField{
{"rom_id_size", "u32"}, {"rom_id", "u32"}, {"product_id", "u32"},
{"func_id", "u32"}, {"erase_offset", "u32"}, {"erase_size", "u32"},
{"data_offset", "u32"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr[20:], fields, "big")
if err != nil {
return nil, err
}
headerSize := 80
return &SignatureResult{
Offset: headerStart,
Size: headerSize,
Description: fmt.Sprintf("%s, header size: %d bytes, ROM ID: %d, erase offset: 0x%X, erase size: 0x%X, data flash offset: 0x%X, data size: 0x%X", jbootARMDescription, headerSize, parsed["rom_id"], parsed["erase_offset"], parsed["erase_size"], parsed["data_offset"], parsed["data_size"]),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// JBOOT STAG header
// ============================================================================
const jbootSTAGDescription = "JBOOT STAG header"
func jbootSTAGMagic() [][]byte {
return [][]byte{
{0x04, 0x04, 0x24, 0x2B},
{0xFF, 0x04, 0x24, 0x2B},
}
}
func jbootSTAGParser(data []byte, offset int) (*SignatureResult, error) {
if offset+16 > len(data) {
return nil, fmt.Errorf("insufficient data for JBOOT STAG header")
}
hdr := data[offset:]
fields := []StructField{
{"tag", "u32"}, {"header_size", "u16"}, {"flags", "u16"}, {"image_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
imageSize := parsed["image_size"]
if offset+headerSize+imageSize >= len(data) {
return nil, fmt.Errorf("JBOOT STAG size exceeds data")
}
imageType := "factory image"
if parsed["flags"]&0x01 != 0 {
imageType = "system upgrade image"
}
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, %s, header size: %d bytes, kernel data size: %d bytes", jbootSTAGDescription, imageType, headerSize, imageSize),
Confidence: ConfidenceLow,
}, nil
}
// ============================================================================
// JBOOT SCH2 header
// ============================================================================
const jbootSCH2Description = "JBOOT SCH2 header"
func jbootSCH2Magic() [][]byte {
return [][]byte{
{0x24, 0x21, 0x00, 0x02},
{0x24, 0x21, 0x01, 0x02},
{0x24, 0x21, 0x02, 0x02},
{0x24, 0x21, 0x03, 0x02},
}
}
func jbootSCH2Parser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for JBOOT SCH2 header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"kernel_size", "u32"},
{"kernel_checksum", "u32"}, {"kernel_entry_point", "u32"},
{"compression", "u8"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
compTypes := map[int]string{0: "none", 1: "gzip", 2: "bzip2", 3: "lzma"}
comp := lookupMap(compTypes, parsed["compression"], "unknown")
headerSize := parsed["header_size"]
kernelSize := parsed["kernel_size"]
totalSize := headerSize + kernelSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("JBOOT SCH2 extends beyond EOF")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, header size: %d bytes, kernel size: %d bytes, kernel compression: %s, kernel entry point: 0x%X", jbootSCH2Description, headerSize, kernelSize, comp, parsed["kernel_entry_point"]),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// PackImg firmware header
// ============================================================================
const packimgDescription = "PackImg firmware header"
func packimgMagic() [][]byte {
return [][]byte{[]byte("--PaCkImGs--")}
}
func packimgParser(data []byte, offset int) (*SignatureResult, error) {
if offset+24 > len(data) {
return nil, fmt.Errorf("insufficient data for PackImg header")
}
hdr := data[offset:]
fields := []StructField{
{"magic1", "u32"}, {"magic2", "u32"}, {"magic3", "u32"},
{"data_size", "u32"}, {"header_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
if len(data)-offset < headerSize+dataSize {
return nil, fmt.Errorf("PackImg size exceeds data")
}
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", packimgDescription, headerSize, dataSize),
Confidence: ConfidenceLow,
}, nil
}
// ============================================================================
// BIN firmware header
// ============================================================================
const binhdrDescription = "BIN firmware header"
func binhdrMagic() [][]byte {
return [][]byte{[]byte("U2ND")}
}
func binhdrParser(data []byte, offset int) (*SignatureResult, error) {
const magicOff = 14
if offset < magicOff {
return nil, fmt.Errorf("BIN header magic offset too small")
}
startOffset := offset - magicOff
if startOffset+24 > len(data) {
return nil, fmt.Errorf("insufficient data for BIN header")
}
hdr := data[startOffset:]
boardID := GetCString(hdr[:14])
fields := []StructField{
{"magic", "u32"}, {"hw_rev", "u16"}, {"fw_major", "u8"}, {"fw_minor", "u8"},
}
parsed, err := Parse(hdr[14:], fields, "big")
if err != nil {
return nil, err
}
return &SignatureResult{
Offset: startOffset,
Description: fmt.Sprintf("%s, board ID: %s, hardware revision: %d, firmware version: %d.%d", binhdrDescription, boardID, parsed["hw_rev"], parsed["fw_major"], parsed["fw_minor"]),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// Autel obfuscated firmware
// ============================================================================
const autelDescription = "Autel obfuscated firmware"
func autelMagic() [][]byte {
return [][]byte{[]byte("ECC0101\x00")}
}
func autelParser(data []byte, offset int) (*SignatureResult, error) {
if offset+16 > len(data) {
return nil, fmt.Errorf("insufficient data for Autel header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u64"}, {"data_size", "u32"}, {"header_size", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
totalSize := headerSize + dataSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("Autel size exceeds data")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d, total size: %d", autelDescription, headerSize, dataSize, totalSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// D-Link TLV firmware
// ============================================================================
const dlinkTLVDescription = "D-Link TLV firmware"
func dlinkTLVMagic() [][]byte {
return [][]byte{{0x64, 0x80, 0x19, 0x40}}
}
func dlinkTLVParser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for D-Link TLV header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
totalSize := headerSize + dataSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("D-Link TLV size exceeds data")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", dlinkTLVDescription, headerSize, dataSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// DLKE encrypted firmware
// ============================================================================
const dlkeDescription = "DLK encrypted firmware"
func dlkeMagic() [][]byte {
return [][]byte{
[]byte("DLK6E8202001"),
[]byte("DLK6E6110002"),
}
}
func dlkeParser(data []byte, offset int) (*SignatureResult, error) {
if offset+80 > len(data) {
return nil, fmt.Errorf("insufficient data for DLKE header")
}
// Parse first JBOOT-like header
hdr1 := data[offset:]
fields := []StructField{
{"rom_id_size", "u32"}, {"rom_id", "u32"}, {"product_id", "u32"},
{"func_id", "u32"}, {"erase_offset", "u32"}, {"erase_size", "u32"},
{"data_offset", "u32"}, {"data_size", "u32"},
}
parsed1, err := Parse(hdr1[20:], fields, "big")
if err != nil {
return nil, err
}
hdr1Size := 80
sig1DataSize := parsed1["data_size"]
nextOffset := offset + hdr1Size + sig1DataSize
if nextOffset+80 > len(data) {
return nil, fmt.Errorf("DLKE second header beyond EOF")
}
hdr2 := data[nextOffset:]
parsed2, err := Parse(hdr2[20:], fields, "big")
if err != nil {
return nil, err
}
hdr2Size := 80
cryptDataSize := parsed2["data_size"]
totalSize := hdr1Size + sig1DataSize + hdr2Size + cryptDataSize
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, signature size: %d bytes, encrypted data size: %d bytes", dlkeDescription, sig1DataSize, cryptDataSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// SHRS encrypted firmware
// ============================================================================
const shrsDescription = "SHRS encrypted firmware"
func shrsMagic() [][]byte {
return [][]byte{[]byte("SHRS")}
}
func shrsParser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for SHRS header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
totalSize := headerSize + dataSize
if offset+totalSize > len(data) {
return nil, fmt.Errorf("SHRS size exceeds data")
}
// Extract IV (16 bytes after the fixed header)
ivStr := ""
if headerSize >= 28 && offset+28 <= len(data) {
iv := hdr[12:28]
ivStr = hex.EncodeToString(iv)
}
confidence := ConfidenceLow
if offset == 0 {
confidence = ConfidenceMedium
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, header size: %d bytes, encrypted data size: %d bytes, IV: %s", shrsDescription, headerSize, dataSize, ivStr),
Confidence: confidence,
}, nil
}
// ============================================================================
// DKBS firmware header
// ============================================================================
const dkbsDescription = "DKBS firmware header"
func dkbsMagic() [][]byte {
return [][]byte{[]byte("_dkbs_")}
}
func dkbsParser(data []byte, offset int) (*SignatureResult, error) {
const magicOff = 7
if offset < magicOff {
return nil, fmt.Errorf("DKBS magic offset too small")
}
startOffset := offset - magicOff
if startOffset+64 > len(data) {
return nil, fmt.Errorf("insufficient data for DKBS header")
}
hdr := data[startOffset:]
fields := []StructField{
{"version", "u32"}, {"magic", "u32"}, {"padding", "u32"},
{"header_size", "u32"}, {"data_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
dataSize := parsed["data_size"]
availableData := len(data) - startOffset
if availableData < headerSize+dataSize {
return nil, fmt.Errorf("DKBS size exceeds data")
}
confidence := ConfidenceMedium
if startOffset == 0 {
confidence = ConfidenceHigh
}
boardID := GetCString(hdr[24:])
return &SignatureResult{
Offset: startOffset,
Size: headerSize,
Description: fmt.Sprintf("%s, board ID: %s, header size: %d bytes, data size: %d", dkbsDescription, boardID, headerSize, dataSize),
Confidence: confidence,
}, nil
}
// ============================================================================
// DMS firmware image
// ============================================================================
const dmsDescription = "DMS firmware image"
func dmsMagic() [][]byte {
return [][]byte{[]byte("0><1")}
}
func dmsParser(data []byte, offset int) (*SignatureResult, error) {
const magicOff = 4
const minSize = 0x100
if offset < magicOff {
return nil, fmt.Errorf("DMS magic offset too small")
}
startOffset := offset - magicOff
if startOffset+minSize > len(data) {
return nil, fmt.Errorf("insufficient data for DMS header")
}
// DMS images have byte-swapped u16 pairs
dmsData := make([]byte, minSize)
copy(dmsData, data[startOffset:startOffset+minSize])
for i := 0; i+1 < len(dmsData); i += 2 {
dmsData[i], dmsData[i+1] = dmsData[i+1], dmsData[i]
}
fields := []StructField{
{"magic", "u32"}, {"unk1", "u32"}, {"image_size", "u32"},
}
parsed, err := Parse(dmsData, fields, "big")
if err != nil {
return nil, err
}
imageSize := parsed["image_size"]
if imageSize <= 0 || startOffset+imageSize > len(data) {
return nil, fmt.Errorf("invalid DMS image size")
}
return &SignatureResult{
Offset: startOffset,
Size: imageSize,
Description: fmt.Sprintf("%s, total size: %d bytes", dmsDescription, imageSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// Known encrypted firmware (encfw)
// ============================================================================
const encfwDescription = "Known encrypted firmware"
func encfwKnownFirmware() map[string]string {
return map[string]string{
"\xdf\x8c\x39\x0d": "D-Link DIR-822 rev C",
"\x35\x66\x6f\x68": "D-Link DAP-1665",
"\xf5\x2a\xa0\xb4": "D-Link DIR-842 rev C",
"\xe3\x13\x00\x5b": "D-Link DIR-850 rev A",
"\x0a\x14\xe4\x24": "D-Link DIR-850 rev B",
}
}
func encfwMagic() [][]byte {
var magics [][]byte
for k := range encfwKnownFirmware() {
magics = append(magics, []byte(k))
}
return magics
}
func encfwParser(data []byte, offset int) (*SignatureResult, error) {
if offset+4 > len(data) {
return nil, fmt.Errorf("insufficient data for encfw")
}
key := string(data[offset : offset+4])
known := encfwKnownFirmware()
model, ok := known[key]
if !ok {
return nil, fmt.Errorf("unknown encfw magic")
}
confidence := ConfidenceMedium
if offset != 0 {
confidence = ConfidenceLow
}
return &SignatureResult{
Offset: offset,
Description: fmt.Sprintf("%s, %s", encfwDescription, model),
Confidence: confidence,
}, nil
}
// ============================================================================
// D-Link encrpted_img
// ============================================================================
const encrptedImgDescription = "D-Link Encrpted Image"
func encrptedImgMagic() [][]byte {
return [][]byte{[]byte("encrpted_img")}
}
func encrptedImgParser(_ []byte, offset int) (*SignatureResult, error) {
confidence := ConfidenceMedium
if offset != 0 {
confidence = ConfidenceLow
}
return &SignatureResult{
Offset: offset,
Description: encrptedImgDescription,
Confidence: confidence,
}, nil
}
// ============================================================================
// Android boot image
// ============================================================================
const androidBootimgDescription = "Android boot image"
func androidBootimgMagic() [][]byte {
return [][]byte{[]byte("ANDROID!")}
}
func androidBootimgParser(data []byte, offset int) (*SignatureResult, error) {
if offset+40 > len(data) {
return nil, fmt.Errorf("insufficient data for Android boot image header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u64"}, {"kernel_size", "u32"}, {"kernel_load_addr", "u32"},
{"ramdisk_size", "u32"}, {"ramdisk_load_addr", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
confidence := ConfidenceLow
if offset == 0 {
confidence = ConfidenceMedium
}
return &SignatureResult{
Offset: offset,
Description: fmt.Sprintf("%s, kernel size: %d bytes, kernel load address: 0x%X, ramdisk size: %d bytes, ramdisk load address: 0x%X", androidBootimgDescription, parsed["kernel_size"], parsed["kernel_load_addr"], parsed["ramdisk_size"], parsed["ramdisk_load_addr"]),
Confidence: confidence,
}, nil
}
// ============================================================================
// Android sparse image
// ============================================================================
const androidSparseDescription = "Android sparse image"
func androidSparseMagic() [][]byte {
return [][]byte{{0x3A, 0xFF, 0x26, 0xED}}
}
func androidSparseParser(data []byte, offset int) (*SignatureResult, error) {
if offset+28 > len(data) {
return nil, fmt.Errorf("insufficient data for Android sparse header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"major_version", "u16"}, {"minor_version", "u16"},
{"header_size", "u16"}, {"chunk_header_size", "u16"},
{"block_size", "u32"}, {"total_blocks", "u32"},
{"total_chunks", "u32"}, {"checksum", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, version %d.%d, header size: %d, block size: %d, chunk count: %d", androidSparseDescription, parsed["major_version"], parsed["minor_version"], headerSize, parsed["block_size"], parsed["total_chunks"]),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// Windows CE binary image
// ============================================================================
const winceDescription = "Windows CE binary image"
func winceMagic() [][]byte {
return [][]byte{[]byte("B000FF\n")}
}
func winceParser(data []byte, offset int) (*SignatureResult, error) {
if offset+16 > len(data) {
return nil, fmt.Errorf("insufficient data for WinCE header")
}
hdr := data[offset:]
fields := []StructField{
{"magic1", "u32"}, {"magic2", "u32"},
{"base_address", "u32"}, {"image_size", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
return &SignatureResult{
Offset: offset,
Size: parsed["image_size"],
Description: fmt.Sprintf("%s, base address: 0x%X, image size: %d bytes", winceDescription, parsed["base_address"], parsed["image_size"]),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// D-Link MH01 firmware
// ============================================================================
const mh01Description = "D-Link MH01 firmware image"
func mh01Magic() [][]byte {
return [][]byte{[]byte("MH01")}
}
func mh01Parser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for MH01 header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"total_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
totalSize := parsed["total_size"]
if offset+totalSize > len(data) {
return nil, fmt.Errorf("MH01 size exceeds data")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, total size: %d bytes", mh01Description, totalSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// CSman DAT
// ============================================================================
const csmanDescription = "CSman DAT file"
func csmanMagic() [][]byte {
return [][]byte{[]byte("SC"), []byte("CS")}
}
func csmanParser(data []byte, offset int) (*SignatureResult, error) {
if offset+8 > len(data) {
return nil, fmt.Errorf("insufficient data for CSman header")
}
hdr := data[offset:]
endianness := "big"
if hdr[0] == 'C' && hdr[1] == 'S' {
endianness = "little"
}
fields := []StructField{
{"magic", "u16"}, {"count", "u16"}, {"size", "u32"},
}
parsed, err := Parse(hdr, fields, endianness)
if err != nil {
return nil, err
}
totalSize := parsed["size"]
if totalSize <= 0 || offset+totalSize > len(data) {
return nil, fmt.Errorf("invalid CSman size")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, total size: %d bytes", csmanDescription, totalSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// DXBC (DirectX shader bytecode)
// ============================================================================
const dxbcDescription = "DirectX shader bytecode"
func dxbcMagic() [][]byte {
return [][]byte{[]byte("DXBC")}
}
func dxbcParser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for DXBC header")
}
hdr := data[offset:]
// DXBC header: 4-byte magic, 16-byte hash, 4-byte version, 4-byte total_size, 4-byte chunk_count
fields := []StructField{
{"magic", "u32"}, {"hash1", "u32"}, {"hash2", "u32"},
{"hash3", "u32"}, {"hash4", "u32"},
{"version", "u32"}, {"total_size", "u32"}, {"chunk_count", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
totalSize := parsed["total_size"]
if totalSize <= 32 || offset+totalSize > len(data) {
return nil, fmt.Errorf("invalid DXBC size")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, total size: %d bytes", dxbcDescription, totalSize),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// DTB (Device Tree Blob)
// ============================================================================
const dtbDescription = "Device tree blob (DTB)"
func dtbMagic() [][]byte {
return [][]byte{{0xD0, 0x0D, 0xFE, 0xED}}
}
func dtbParser(data []byte, offset int) (*SignatureResult, error) {
if offset+40 > len(data) {
return nil, fmt.Errorf("insufficient data for DTB header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"total_size", "u32"}, {"struct_offset", "u32"},
{"strings_offset", "u32"}, {"mem_reserve_offset", "u32"},
{"version", "u32"}, {"last_comp_version", "u32"},
{"cpu_id", "u32"}, {"strings_size", "u32"}, {"struct_size", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
totalSize := parsed["total_size"]
structEnd := offset + parsed["struct_offset"] + parsed["struct_size"]
stringsEnd := offset + parsed["strings_offset"] + parsed["strings_size"]
if structEnd > len(data) || stringsEnd > len(data) {
return nil, fmt.Errorf("DTB offsets exceed data")
}
if totalSize <= 0 || offset+totalSize > len(data) {
return nil, fmt.Errorf("invalid DTB total size")
}
return &SignatureResult{
Offset: offset,
Size: totalSize,
Description: fmt.Sprintf("%s, version: %d, CPU ID: %d, total size: %d bytes", dtbDescription, parsed["version"], parsed["cpu_id"], totalSize),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// Matter OTA firmware
// ============================================================================
const matterOTADescription = "Matter OTA firmware"
func matterOTAMagic() [][]byte {
return [][]byte{{0x1e, 0xf1, 0xee, 0x1b}}
}
func matterOTAParser(data []byte, offset int) (*SignatureResult, error) {
if offset+32 > len(data) {
return nil, fmt.Errorf("insufficient data for Matter OTA header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"total_size", "u32"}, {"header_size", "u32"},
{"header_tlv_size", "u32"}, {"vendor_id", "u16"}, {"product_id", "u16"},
{"version", "u32"}, {"min_version", "u32"},
{"max_version", "u32"}, {"payload_size", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
headerSize := parsed["header_size"]
return &SignatureResult{
Offset: offset,
Size: headerSize,
Description: fmt.Sprintf("%s, total size: %d bytes, header size: %d bytes, vendor id: 0x%x, product id: 0x%x, version: %d, payload size: %d bytes", matterOTADescription, parsed["total_size"], headerSize, parsed["vendor_id"], parsed["product_id"], parsed["version"], parsed["payload_size"]),
Confidence: ConfidenceHigh,
}, nil
}
// ============================================================================
// RTK firmware header
// ============================================================================
const rtkDescription = "RTK firmware header"
func rtkMagic() [][]byte {
return [][]byte{[]byte("RTK0")}
}
func rtkParser(data []byte, offset int) (*SignatureResult, error) {
if offset+16 > len(data) {
return nil, fmt.Errorf("insufficient data for RTK header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"header_size", "u32"}, {"image_size", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
availableData := len(data) - offset
if parsed["image_size"] != availableData {
return nil, fmt.Errorf("RTK image size mismatch")
}
return &SignatureResult{
Offset: offset,
Size: parsed["header_size"],
Description: fmt.Sprintf("%s, header size: %d bytes, image size: %d", rtkDescription, parsed["header_size"], parsed["image_size"]),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// DPAPI blob data
// ============================================================================
const dpapiDescription = "DPAPI blob data"
func dpapiMagic() [][]byte {
return [][]byte{
{0x01, 0x00, 0x00, 0x00, 0xD0, 0x8c, 0x9d, 0xdf, 0x01, 0x15, 0xd1, 0x11, 0x8c, 0x7a, 0x00, 0xc0, 0x4f, 0xc2, 0x97, 0xeb},
}
}
func dpapiParser(data []byte, offset int) (*SignatureResult, error) {
if offset+44 > len(data) {
return nil, fmt.Errorf("insufficient data for DPAPI blob header")
}
hdr := data[offset:]
fields := []StructField{
{"version", "u32"}, {"provider_guid1", "u32"}, {"provider_guid2", "u32"},
{"provider_guid3", "u32"}, {"provider_guid4", "u32"},
{"mk_version", "u32"}, {"mk_guid1", "u32"}, {"mk_guid2", "u32"},
{"mk_guid3", "u32"}, {"mk_guid4", "u32"}, {"flags", "u32"},
}
parsed, err := Parse(hdr, fields, "little")
if err != nil {
return nil, err
}
return &SignatureResult{
Offset: offset,
Description: fmt.Sprintf("%s, version: %d, flags: %d", dpapiDescription, parsed["version"], parsed["flags"]),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// QEMU QCOW Image
// ============================================================================
const qcowDescription = "QEMU QCOW Image"
func qcowMagic() [][]byte {
return [][]byte{[]byte("QFI\xFB")}
}
func qcowParser(data []byte, offset int) (*SignatureResult, error) {
if offset+24 > len(data) {
return nil, fmt.Errorf("insufficient data for QCOW header")
}
hdr := data[offset:]
fields := []StructField{
{"magic", "u32"}, {"version", "u32"}, {"backing_file_offset", "u64"},
{"backing_file_size", "u32"}, {"cluster_bits", "u32"},
{"disk_size", "u64"}, {"encryption_method", "u32"},
}
parsed, err := Parse(hdr, fields, "big")
if err != nil {
return nil, err
}
encMap := map[int]string{0: "none", 1: "AES", 2: "LUKS"}
enc := lookupMap(encMap, parsed["encryption_method"], "unknown")
return &SignatureResult{
Offset: offset,
Description: fmt.Sprintf("%s, version: %d, storage media size: 0x%x bytes, cluster block size: 0x%x bytes, encryption method: %s", qcowDescription, parsed["version"], parsed["disk_size"], 1<<parsed["cluster_bits"], enc),
Confidence: ConfidenceMedium,
}, nil
}
// ============================================================================
// Helpers
// ============================================================================
func lookupMap(m map[int]string, key int, fallback string) string {
if v, ok := m[key]; ok {
return v
}
return fallback
}