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<