mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
IsImg4 and IsIm4p returned errors when ASN.1 parsing failed, but parse failure just means the file isn't that format. This broke callers like `kernel kmutil inspect -x` on x86_64 Macs where the kernel collection is a Mach-O .kc file, not IMG4. Return (false, nil) for parse failures; only error on I/O failure. Closes #1081
420 lines
10 KiB
Go
420 lines
10 KiB
Go
package magic
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/asn1"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/blacktop/go-apfs/pkg/disk/dmg"
|
|
"github.com/blacktop/go-apfs/pkg/disk/hfsplus"
|
|
"github.com/blacktop/ipsw/pkg/bundle"
|
|
"github.com/blacktop/ipsw/pkg/ftab"
|
|
"github.com/blacktop/ipsw/pkg/img3"
|
|
"github.com/blacktop/lzss"
|
|
)
|
|
|
|
type Magic uint32
|
|
|
|
const (
|
|
Magic32 Magic = 0xfeedface
|
|
Magic64 Magic = 0xfeedfacf
|
|
MagicFatBE Magic = 0xcafebabe
|
|
MagicFatLE Magic = 0xbebafeca
|
|
MagicZip Magic = 0x504b0304
|
|
MagicYAA1 Magic = 0x31414159 // "YAA1"
|
|
MagicAA01 Magic = 0x31304141 // "AA01"
|
|
MagicAEA1 Magic = 0x41454131 // "AEA1"
|
|
MagicPBZX = 0x70627a78 // "pbzx"
|
|
)
|
|
|
|
func IsMachO(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
data := make([]byte, 4)
|
|
if _, err := f.Read(data); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
return IsMachOData(data)
|
|
}
|
|
|
|
func IsMachOData(dat []byte) (bool, error) {
|
|
var magic [4]byte
|
|
if err := binary.Read(bytes.NewReader(dat), binary.LittleEndian, &magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.LittleEndian.Uint32(magic[:])) {
|
|
case Magic32, Magic64, MagicFatBE, MagicFatLE:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsMachoOrImg4(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
var magic [4]byte
|
|
if _, err = f.Read(magic[:]); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
|
|
switch Magic(binary.LittleEndian.Uint32(magic[:])) {
|
|
case Magic32, Magic64, MagicFatBE, MagicFatLE:
|
|
return true, nil
|
|
default:
|
|
f.Seek(0, io.SeekStart)
|
|
if isIm4p, err := IsIm4p(filePath); isIm4p && err == nil {
|
|
if strings.Contains(filePath, "kernelcache") {
|
|
return false, fmt.Errorf("im4p file detected (run `ipsw kernel dec`)")
|
|
}
|
|
return false, fmt.Errorf("im4p file detected (run `ipsw img4 extract`)")
|
|
}
|
|
f.Seek(0, io.SeekStart)
|
|
if isImg4, err := IsImg4(filePath); isImg4 && err == nil {
|
|
if strings.Contains(filePath, "kernelcache") {
|
|
return false, fmt.Errorf("img4 file detected (run `ipsw kernel dec --km`)")
|
|
}
|
|
return false, fmt.Errorf("img4 file detected")
|
|
}
|
|
}
|
|
|
|
return false, fmt.Errorf("not a macho file")
|
|
}
|
|
|
|
type Asn1Header struct {
|
|
Raw asn1.RawContent
|
|
Name string `asn1:"ia5"` // IM4P
|
|
}
|
|
|
|
func IsImg4(filePath string) (bool, error) {
|
|
if filepath.Ext(filePath) == ".img4" {
|
|
return true, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to read file %s: %w", filePath, err)
|
|
}
|
|
|
|
var hdr Asn1Header
|
|
if _, err := asn1.Unmarshal(data, &hdr); err != nil {
|
|
return false, nil // not ASN.1 → not IMG4
|
|
}
|
|
|
|
return hdr.Name == "IMG4", nil
|
|
}
|
|
|
|
func IsIm4p(filePath string) (bool, error) {
|
|
if filepath.Ext(filePath) == ".im4p" {
|
|
return true, nil
|
|
}
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to read file %s: %w", filePath, err)
|
|
}
|
|
|
|
var hdr Asn1Header
|
|
if _, err := asn1.Unmarshal(data, &hdr); err != nil {
|
|
return false, nil // not ASN.1 → not IM4P
|
|
}
|
|
|
|
return hdr.Name == "IM4P", nil
|
|
}
|
|
|
|
func IsImg3(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
var hdr img3.Header
|
|
if err := binary.Read(f, binary.LittleEndian, &hdr); err != nil {
|
|
return false, fmt.Errorf("failed to read bundle header: %w", err)
|
|
}
|
|
if string(hdr.Magic[:]) == img3.Magic {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsBUND(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
var hdr bundle.Header
|
|
if err := binary.Read(f, binary.LittleEndian, &hdr); err != nil {
|
|
return false, fmt.Errorf("failed to read bundle header: %w", err)
|
|
}
|
|
if string(hdr.Magic[:]) == bundle.Magic {
|
|
return true, nil
|
|
}
|
|
slices.Reverse(hdr.Magic[:])
|
|
|
|
if string(hdr.Magic[:]) == bundle.Magic {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsZip(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 4)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicZip:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
func IsZipData(r io.Reader) (bool, error) {
|
|
magic := make([]byte, 4)
|
|
if _, err := r.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicZip:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsDMG(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
var encHdr dmg.EncryptionHeader
|
|
if err := binary.Read(f, binary.BigEndian, &encHdr); err != nil {
|
|
return false, fmt.Errorf("failed to read DMG encryption header: %v", err)
|
|
}
|
|
if string(encHdr.Magic[:]) == dmg.EncryptedMagic {
|
|
return true, nil
|
|
}
|
|
if _, err := f.Seek(int64(-binary.Size(dmg.UDIFResourceFile{})), io.SeekEnd); err != nil {
|
|
return false, fmt.Errorf("failed to seek to DMG footer: %v", err)
|
|
}
|
|
var footer dmg.UDIFResourceFile
|
|
if err := binary.Read(f, binary.BigEndian, &footer); err != nil {
|
|
return false, fmt.Errorf("failed to read DMG footer: %v", err)
|
|
}
|
|
if footer.Signature.String() != "koly" {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func IsEncryptedDMG(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 8)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
if string(magic) == dmg.EncryptedMagic {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsAPFS(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
if _, err := f.Seek(0x20, io.SeekStart); err != nil {
|
|
return false, fmt.Errorf("failed to seek to APFS magic: %w", err)
|
|
}
|
|
magic := make([]byte, 4)
|
|
if err := binary.Read(f, binary.BigEndian, &magic); err != nil {
|
|
return false, fmt.Errorf("failed to read APFS magic: %w", err)
|
|
}
|
|
if string(magic) == "NXSB" {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsHFSPlus(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
if _, err := f.Seek(1024, io.SeekStart); err != nil {
|
|
return false, fmt.Errorf("failed to seek to HFS+ magic: %w", err)
|
|
}
|
|
var magic uint16
|
|
if err := binary.Read(f, binary.BigEndian, &magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
if magic == uint16(hfsplus.HFSPlusSigWord) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsXar(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 4)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
if string(magic) == "xar!" {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsPBZX(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 4)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicPBZX:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsPBZXData(r io.Reader) (bool, error) {
|
|
magic := make([]byte, 4)
|
|
if _, err := r.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicPBZX:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsFTAB(filename string) (bool, error) {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filename, err)
|
|
}
|
|
defer f.Close()
|
|
if _, err := f.Seek(32, io.SeekStart); err != nil {
|
|
return false, fmt.Errorf("failed to seek to FTAB magic: %w", err)
|
|
}
|
|
var magic uint64
|
|
if err := binary.Read(f, binary.LittleEndian, &magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
if magic == ftab.FtabMagic {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsAA(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 4)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.LittleEndian.Uint32(magic[:])) {
|
|
case MagicYAA1, MagicAA01:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsAEA(filePath string) (bool, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to open file %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
magic := make([]byte, 4)
|
|
if _, err := f.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicAEA1:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsAEAData(rc io.Reader) (bool, error) {
|
|
magic := make([]byte, 4)
|
|
if _, err := rc.Read(magic); err != nil {
|
|
return false, fmt.Errorf("failed to read magic: %w", err)
|
|
}
|
|
switch Magic(binary.BigEndian.Uint32(magic[:])) {
|
|
case MagicAEA1:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func IsLZSS(data []byte) (bool, error) {
|
|
if len(data) > 8 && string(data[:8]) == lzss.Magic {
|
|
if len(data) < binary.Size(lzss.Header{}) {
|
|
return true, fmt.Errorf("data too short to contain valid LZSS header")
|
|
}
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsLZFSE(data []byte) (bool, error) {
|
|
if len(data) > 4 &&
|
|
(string(data[:4]) == "bvx2" ||
|
|
string(data[:4]) == "bvxn" ||
|
|
string(data[:4]) == "bvx1" ||
|
|
string(data[:4]) == "bvx-") {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|