mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
chore: add ability to diff IPSW vs. OTA entitlements
This commit is contained in:
@@ -75,15 +75,17 @@ func init() {
|
||||
|
||||
// diffCmd represents the diff command
|
||||
var diffCmd = &cobra.Command{
|
||||
Use: "diff <IPSW> <IPSW>",
|
||||
Short: "Diff IPSWs",
|
||||
Use: "diff <OLD_FW> <NEW_FW>",
|
||||
Short: "Diff IPSWs and OTAs",
|
||||
Example: heredoc.Doc(`
|
||||
# Diff two IPSWs
|
||||
❯ ipsw diff <old.ipsw> <new.ipsw> --fw --launchd --output <output/folder> --markdown
|
||||
# Diff two IPSWs with KDKs
|
||||
❯ ipsw diff <old.ipsw> <new.ipsw> --output <output/folder> --markdown
|
||||
--kdk /Library/Developer/KDKs/KDK_15.0_24A5264n.kdk/System/Library/Kernels/kernel.release.t6031
|
||||
--kdk /Library/Developer/KDKs/KDK_15.0_24A5279h.kdk/System/Library/Kernels/kernel.release.t6031`),
|
||||
--kdk /Library/Developer/KDKs/KDK_15.0_24A5279h.kdk/System/Library/Kernels/kernel.release.t6031
|
||||
# Diff entitlements between an IPSW and an OTA
|
||||
❯ ipsw diff <old.ipsw> <new.ota> --ent --markdown`),
|
||||
Args: cobra.ExactArgs(2),
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
+568
-186
@@ -6,11 +6,17 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -21,6 +27,9 @@ import (
|
||||
"github.com/blacktop/ipsw/internal/utils"
|
||||
"github.com/blacktop/ipsw/pkg/aea"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/ota"
|
||||
"github.com/blacktop/ipsw/pkg/ota/pbzx"
|
||||
"github.com/blacktop/ipsw/pkg/ota/yaa"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@@ -47,136 +56,579 @@ type Config struct {
|
||||
func GetDatabase(conf *Config) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
// create or load entitlement database
|
||||
if _, err := os.Stat(conf.Database); os.IsNotExist(err) {
|
||||
utils.Indent(log.Info, 2)("Generating entitlement database file...")
|
||||
if len(conf.IPSW) > 0 {
|
||||
i, err := info.Parse(conf.IPSW)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse IPSW: %v", err)
|
||||
}
|
||||
|
||||
if appOS, err := i.GetAppOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning AppOS")
|
||||
if ents, err := scanEnts(conf.IPSW, appOS, "AppOS", conf.PemDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in AppOS %s: %v", appOS, err)
|
||||
} else {
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
}
|
||||
if systemOS, err := i.GetSystemOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning SystemOS")
|
||||
if ents, err := scanEnts(conf.IPSW, systemOS, "SystemOS", conf.PemDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in SystemOS %s: %v", systemOS, err)
|
||||
} else {
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
}
|
||||
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning FileSystem")
|
||||
if ents, err := scanEnts(conf.IPSW, fsOS, "filesystem", conf.PemDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in FileSystem %s: %v", fsOS, err)
|
||||
} else {
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
}
|
||||
if excOS, err := i.GetExclaveOSDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning ExclaveOS")
|
||||
if ents, err := scanEnts(conf.IPSW, excOS, "ExclaveOS", conf.PemDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in ExclaveOS %s: %v", excOS, err)
|
||||
} else {
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
}
|
||||
if conf.Database != "" {
|
||||
if _, err := os.Stat(conf.Database); err == nil {
|
||||
return loadEntitlementDatabase(conf.Database)
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("failed to stat entitlement database file %s: %v", conf.Database, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Folder) > 0 {
|
||||
var files []string
|
||||
if err := filepath.Walk(conf.Folder, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Errorf("failed to walk mount %s: %v", conf.Folder, err)
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
utils.Indent(log.Info, 2)("Generating entitlement database file...")
|
||||
|
||||
if len(conf.IPSW) > 0 {
|
||||
ents, err := collectEntitlementsFromArchive(conf.IPSW, conf.PemDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
|
||||
if len(conf.Folder) > 0 {
|
||||
var files []string
|
||||
if err := filepath.Walk(conf.Folder, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Errorf("failed to walk mount %s: %v", conf.Folder, err)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to walk files in dir %s: %v", conf.Folder, err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to walk files in dir %s: %v", conf.Folder, err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
var m *macho.File
|
||||
fat, err := macho.OpenFat(file)
|
||||
if err == nil {
|
||||
m = fat.Arches[len(fat.Arches)-1].File // grab last arch (probably arm64e)
|
||||
} else {
|
||||
if err == macho.ErrNotFat {
|
||||
m, err = macho.Open(file)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("failed to get entitlements for %s", file)
|
||||
continue // bad macho file (skip)
|
||||
}
|
||||
} else {
|
||||
continue // not a macho file (skip)
|
||||
for _, file := range files {
|
||||
var m *macho.File
|
||||
fat, err := macho.OpenFat(file)
|
||||
if err == nil {
|
||||
m = fat.Arches[len(fat.Arches)-1].File // grab last arch (probably arm64e)
|
||||
} else {
|
||||
if err == macho.ErrNotFat {
|
||||
m, err = macho.Open(file)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("failed to get entitlements for %s", file)
|
||||
continue // bad macho file (skip)
|
||||
}
|
||||
}
|
||||
if m.CodeSignature() != nil && len(m.CodeSignature().Entitlements) > 0 {
|
||||
entDB[strings.TrimPrefix(file, conf.Folder)] = m.CodeSignature().Entitlements
|
||||
} else {
|
||||
entDB[strings.TrimPrefix(file, conf.Folder)] = ""
|
||||
continue // not a macho file (skip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(conf.Database) > 0 {
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
e := gob.NewEncoder(buff)
|
||||
|
||||
// Encoding the map
|
||||
err := e.Encode(entDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode entitlement db to binary: %v", err)
|
||||
}
|
||||
|
||||
of, err := os.Create(conf.Database)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file %s: %v", conf.Database, err)
|
||||
}
|
||||
defer of.Close()
|
||||
|
||||
gzw := gzip.NewWriter(of)
|
||||
defer gzw.Close()
|
||||
|
||||
if _, err := buff.WriteTo(gzw); err != nil {
|
||||
return nil, fmt.Errorf("failed to write entitlement db to gzip file: %v", err)
|
||||
if m.CodeSignature() != nil && len(m.CodeSignature().Entitlements) > 0 {
|
||||
entDB[strings.TrimPrefix(file, conf.Folder)] = m.CodeSignature().Entitlements
|
||||
} else {
|
||||
entDB[strings.TrimPrefix(file, conf.Folder)] = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.WithField("database", filepath.Base(conf.Database)).Info("Loading Entitlement DB")
|
||||
}
|
||||
|
||||
edbFile, err := os.Open(conf.Database)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open entitlement database file %s; %v", conf.Database, err)
|
||||
}
|
||||
defer edbFile.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(edbFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create gzip reader: %v", err)
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
// Decoding the serialized data
|
||||
if err := gob.NewDecoder(gzr).Decode(&entDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode entitlement database; %v", err)
|
||||
if len(conf.Database) > 0 {
|
||||
if err := saveEntitlementDatabase(conf.Database, entDB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func loadEntitlementDatabase(path string) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
log.WithField("database", filepath.Base(path)).Info("Loading Entitlement DB")
|
||||
|
||||
edbFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open entitlement database file %s; %v", path, err)
|
||||
}
|
||||
defer edbFile.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(edbFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create gzip reader: %v", err)
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
if err := gob.NewDecoder(gzr).Decode(&entDB); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode entitlement database; %v", err)
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func saveEntitlementDatabase(path string, entDB map[string]string) error {
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
if err := gob.NewEncoder(buff).Encode(entDB); err != nil {
|
||||
return fmt.Errorf("failed to encode entitlement db to binary: %v", err)
|
||||
}
|
||||
|
||||
of, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %s: %v", path, err)
|
||||
}
|
||||
defer of.Close()
|
||||
|
||||
gzw := gzip.NewWriter(of)
|
||||
defer gzw.Close()
|
||||
|
||||
if _, err := buff.WriteTo(gzw); err != nil {
|
||||
return fmt.Errorf("failed to write entitlement db to gzip file: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectEntitlementsFromArchive(path, pemDb string) (map[string]string, error) {
|
||||
infoData, parseErr := info.Parse(path)
|
||||
if parseErr == nil && infoData != nil && infoData.Plists != nil && infoData.Plists.Type != "OTA" {
|
||||
return collectEntitlementsFromIPSW(path, infoData, pemDb)
|
||||
}
|
||||
|
||||
aa, otaErr := ota.Open(path)
|
||||
if otaErr != nil {
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("failed to parse IPSW: %v; failed to open OTA: %w", parseErr, otaErr)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to open OTA: %w", otaErr)
|
||||
}
|
||||
defer aa.Close()
|
||||
|
||||
if _, err := aa.Info(); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse OTA metadata: %w", err)
|
||||
}
|
||||
|
||||
return collectEntitlementsFromOTA(aa, pemDb)
|
||||
}
|
||||
|
||||
func collectEntitlementsFromIPSW(ipswPath string, i *info.Info, pemDb string) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
if appOS, err := i.GetAppOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning AppOS")
|
||||
ents, err := scanEnts(ipswPath, appOS, "AppOS", pemDb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in AppOS %s: %v", appOS, err)
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
if systemOS, err := i.GetSystemOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning SystemOS")
|
||||
ents, err := scanEnts(ipswPath, systemOS, "SystemOS", pemDb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in SystemOS %s: %v", systemOS, err)
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning FileSystem")
|
||||
ents, err := scanEnts(ipswPath, fsOS, "filesystem", pemDb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in FileSystem %s: %v", fsOS, err)
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
if excOS, err := i.GetExclaveOSDmg(); err == nil {
|
||||
utils.Indent(log.Info, 3)("Scanning ExclaveOS")
|
||||
ents, err := scanEnts(ipswPath, excOS, "ExclaveOS", pemDb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan files in ExclaveOS %s: %v", excOS, err)
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func collectEntitlementsFromOTA(aa *ota.AA, pemDb string) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "ipsw-ota-ents")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary directory for OTA entitlements: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cryptexes := []struct {
|
||||
name string
|
||||
label string
|
||||
required bool
|
||||
}{
|
||||
{"system", "OTA System Cryptex", true},
|
||||
{"app", "OTA App Cryptex", false},
|
||||
}
|
||||
|
||||
for _, cx := range cryptexes {
|
||||
dmgPath, err := aa.ExtractCryptex(cx.name, tmpDir)
|
||||
if err != nil {
|
||||
if cx.required {
|
||||
return nil, fmt.Errorf("failed to extract %s: %w", cx.label, err)
|
||||
}
|
||||
log.WithError(err).Debugf("skipping %s", cx.label)
|
||||
continue
|
||||
}
|
||||
ents, err := scanEntsFromDMG(dmgPath, cx.label, pemDb)
|
||||
if err != nil {
|
||||
if cx.required {
|
||||
return nil, fmt.Errorf("failed to scan %s entitlements: %w", cx.label, err)
|
||||
}
|
||||
log.WithError(err).Warnf("failed to scan %s entitlements", cx.label)
|
||||
continue
|
||||
}
|
||||
maps.Copy(entDB, ents)
|
||||
}
|
||||
|
||||
payloadEnts, err := collectEntitlementsFromPayload(aa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maps.Copy(entDB, payloadEnts)
|
||||
|
||||
// NOTE: this found nothing when ran on iOS 26 IPSW vs. OTA
|
||||
// looseEnts, err := collectEntitlementsFromLooseFiles(aa)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// maps.Copy(entDB, looseEnts)
|
||||
|
||||
if len(entDB) == 0 {
|
||||
return nil, fmt.Errorf("no entitlements extracted from OTA payload")
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func collectEntitlementsFromPayload(aa *ota.AA) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
pre := regexp.MustCompile(`^payload.\d+$`)
|
||||
files := aa.Files()
|
||||
if len(files) == 0 {
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "ipsw-ota-payload")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary directory for OTA payload extraction: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
for _, file := range files {
|
||||
if file == nil || file.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !pre.MatchString(file.Base()) {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := aa.Open(file.Name(), false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open OTA payload %s: %w", file.Name(), err)
|
||||
}
|
||||
var payloadBuf bytes.Buffer
|
||||
if err := pbzx.Extract(context.Background(), rc, &payloadBuf, runtime.NumCPU()); err != nil {
|
||||
rc.Close()
|
||||
return nil, fmt.Errorf("failed to extract OTA payload %s: %w", file.Name(), err)
|
||||
}
|
||||
rc.Close()
|
||||
|
||||
y := &yaa.YAA{}
|
||||
if err := y.Parse(bytes.NewReader(payloadBuf.Bytes())); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse OTA payload %s: %w", file.Name(), err)
|
||||
}
|
||||
|
||||
payloadRoot := filepath.Join(tmpDir, file.Base())
|
||||
if err := extractYAAEntries(y, payloadRoot); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract OTA payload %s: %w", file.Name(), err)
|
||||
}
|
||||
|
||||
payloadEnts, err := scanEntsFromFolder(payloadRoot, "OTA payload")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan OTA payload %s: %w", file.Name(), err)
|
||||
}
|
||||
maps.Copy(entDB, payloadEnts)
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func scanEntsFromDMG(dmgPath, dmgLabel, pemDbPath string) (map[string]string, error) {
|
||||
originalPath := dmgPath
|
||||
if filepath.Ext(dmgPath) == ".aea" {
|
||||
var err error
|
||||
dmgPath, err = aea.Decrypt(&aea.DecryptConfig{
|
||||
Input: dmgPath,
|
||||
Output: filepath.Dir(dmgPath),
|
||||
PemDB: pemDbPath,
|
||||
Insecure: false, // TODO: make insecure configurable
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse AEA encrypted DMG %s: %v", originalPath, err)
|
||||
}
|
||||
defer os.Remove(dmgPath)
|
||||
}
|
||||
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgLabel, dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
if alreadyMounted {
|
||||
utils.Indent(log.Debug, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
} else {
|
||||
defer func() {
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
|
||||
if err := utils.Retry(3, 2*time.Second, func() error {
|
||||
return utils.Unmount(mountPoint, true)
|
||||
}); err != nil {
|
||||
utils.Indent(log.Error, 3)(fmt.Sprintf("failed to unmount %s at %s: %v", dmgPath, mountPoint, err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
entDB := make(map[string]string)
|
||||
if err := filepath.Walk(mountPoint, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Errorf("failed to walk mount %s: %v", mountPoint, err)
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
var m *macho.File
|
||||
fat, ferr := macho.OpenFat(path)
|
||||
if ferr == nil {
|
||||
m = fat.Arches[len(fat.Arches)-1].File // grab last arch (probably arm64e)
|
||||
} else {
|
||||
if ferr == macho.ErrNotFat {
|
||||
m, ferr = macho.Open(path)
|
||||
if ferr != nil {
|
||||
log.WithError(ferr).Warnf("failed to get entitlements for %s", path)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil // not a macho file (skip)
|
||||
}
|
||||
}
|
||||
relPath := strings.TrimPrefix(path, mountPoint)
|
||||
if m.CodeSignature() != nil && len(m.CodeSignature().Entitlements) > 0 {
|
||||
entDB[relPath] = m.CodeSignature().Entitlements
|
||||
} else {
|
||||
entDB[relPath] = ""
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to walk files in dir %s: %v", mountPoint, err)
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func collectEntitlementsFromLooseFiles(aa *ota.AA) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
files := aa.Files()
|
||||
if len(files) == 0 {
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
payloadRE := regexp.MustCompile(`^payload.\d+$`)
|
||||
cryptexRE := regexp.MustCompile(`^cryptex-`)
|
||||
for _, file := range files {
|
||||
if file == nil || file.IsDir() {
|
||||
continue
|
||||
}
|
||||
base := file.Base()
|
||||
if payloadRE.MatchString(base) {
|
||||
continue
|
||||
}
|
||||
if cryptexRE.MatchString(base) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name(), "payloadv2/") {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := aa.Open(file.Name(), true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open OTA file %s: %w", file.Name(), err)
|
||||
}
|
||||
data, err := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read OTA file %s: %w", file.Name(), err)
|
||||
}
|
||||
|
||||
ent, ok, err := entitlementsFromData(data)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("failed to parse entitlements for OTA file %s", file.Name())
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
rel := sanitizeLoosePath(file.Name())
|
||||
if _, exists := entDB[rel]; !exists {
|
||||
entDB[rel] = ent
|
||||
}
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func scanEntsFromFolder(root, label string) (map[string]string, error) {
|
||||
entDB := make(map[string]string)
|
||||
|
||||
if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
log.Errorf("failed to walk %s %s: %v", label, path, err)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if d.Type()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ent, ok, err := readEntitlements(path)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("failed to read entitlements from %s", path)
|
||||
return nil
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, relErr := filepath.Rel(root, path)
|
||||
if relErr != nil {
|
||||
rel = d.Name()
|
||||
}
|
||||
rel = filepath.ToSlash(rel)
|
||||
if !strings.HasPrefix(rel, "/") {
|
||||
rel = "/" + rel
|
||||
}
|
||||
|
||||
entDB[rel] = ent
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan %s folder: %w", label, err)
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
}
|
||||
|
||||
func readEntitlements(path string) (string, bool, error) {
|
||||
if fat, err := macho.OpenFat(path); err == nil {
|
||||
defer fat.Close()
|
||||
for _, arch := range fat.Arches {
|
||||
if arch.File == nil {
|
||||
continue
|
||||
}
|
||||
if cs := arch.File.CodeSignature(); cs != nil {
|
||||
return cs.Entitlements, true, nil
|
||||
}
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
m, err := macho.Open(path)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
if cs := m.CodeSignature(); cs != nil {
|
||||
return cs.Entitlements, true, nil
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
func entitlementsFromData(data []byte) (string, bool, error) {
|
||||
if len(data) < 4 {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
if fat, err := macho.NewFatFile(bytes.NewReader(data)); err == nil {
|
||||
defer fat.Close()
|
||||
for _, arch := range fat.Arches {
|
||||
if arch.File == nil {
|
||||
continue
|
||||
}
|
||||
if cs := arch.File.CodeSignature(); cs != nil {
|
||||
return cs.Entitlements, true, nil
|
||||
}
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
m, err := macho.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
defer m.Close()
|
||||
if cs := m.CodeSignature(); cs != nil {
|
||||
return cs.Entitlements, true, nil
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
func sanitizeLoosePath(name string) string {
|
||||
clean := filepath.Clean(name)
|
||||
clean = filepath.ToSlash(clean)
|
||||
for strings.HasPrefix(clean, "./") {
|
||||
clean = strings.TrimPrefix(clean, "./")
|
||||
}
|
||||
clean = strings.TrimPrefix(clean, "AssetData/")
|
||||
for strings.HasPrefix(clean, "/") {
|
||||
clean = strings.TrimPrefix(clean, "/")
|
||||
}
|
||||
if clean == "" {
|
||||
return "/" + filepath.ToSlash(filepath.Base(name))
|
||||
}
|
||||
return "/" + clean
|
||||
}
|
||||
|
||||
func extractYAAEntries(archive *yaa.YAA, dest string) error {
|
||||
if err := os.MkdirAll(dest, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create OTA payload root %s: %w", dest, err)
|
||||
}
|
||||
|
||||
for _, entry := range archive.Entries {
|
||||
cleanName := filepath.Clean(entry.Path)
|
||||
if cleanName == "." || cleanName == "" {
|
||||
continue
|
||||
}
|
||||
dstPath := filepath.Join(dest, cleanName)
|
||||
rel, err := filepath.Rel(dest, dstPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute relative OTA payload path for %s: %w", entry.Path, err)
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
return fmt.Errorf("invalid OTA payload path traversal detected: %s", entry.Path)
|
||||
}
|
||||
switch entry.Type {
|
||||
case yaa.Directory:
|
||||
if err := os.MkdirAll(dstPath, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create OTA payload directory %s: %w", dstPath, err)
|
||||
}
|
||||
case yaa.RegularFile:
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||
return fmt.Errorf("failed to prepare OTA payload directory %s: %w", filepath.Dir(dstPath), err)
|
||||
}
|
||||
data := make([]byte, entry.Size)
|
||||
if _, err := entry.Read(data); err != nil {
|
||||
return fmt.Errorf("failed to read OTA payload entry %s: %w", entry.Path, err)
|
||||
}
|
||||
if err := os.WriteFile(dstPath, data, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write OTA payload entry %s: %w", entry.Path, err)
|
||||
}
|
||||
case yaa.SymbolicLink:
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0o755); err != nil {
|
||||
return fmt.Errorf("failed to prepare OTA payload symlink dir %s: %w", filepath.Dir(dstPath), err)
|
||||
}
|
||||
if err := os.Symlink(entry.Link, dstPath); err != nil && !errors.Is(err, os.ErrExist) {
|
||||
return fmt.Errorf("failed to create OTA payload symlink %s: %w", entry.Path, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiffDatabases compares two entitlement databases and returns a diff
|
||||
func DiffDatabases(db1, db2 map[string]string, conf *Config) (string, error) {
|
||||
var err error
|
||||
@@ -253,7 +705,7 @@ func DiffDatabases(db1, db2 map[string]string, conf *Config) (string, error) {
|
||||
}
|
||||
|
||||
func scanEnts(ipswPath, dmgPath, dmgType, pemDbPath string) (map[string]string, error) {
|
||||
// check if filesystem DMG already exists (due to previous mount command)
|
||||
localPath := dmgPath
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
dmgs, err := utils.Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
@@ -264,81 +716,11 @@ func scanEnts(ipswPath, dmgPath, dmgType, pemDbPath string) (map[string]string,
|
||||
if len(dmgs) == 0 {
|
||||
return nil, fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
localPath = dmgs[0]
|
||||
defer os.Remove(localPath)
|
||||
} else {
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Found extracted %s", dmgPath))
|
||||
}
|
||||
|
||||
if filepath.Ext(dmgPath) == ".aea" {
|
||||
var err error
|
||||
dmgPath, err = aea.Decrypt(&aea.DecryptConfig{
|
||||
Input: dmgPath,
|
||||
Output: filepath.Dir(dmgPath),
|
||||
PemDB: pemDbPath,
|
||||
Insecure: false, // TODO: make insecure configurable
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse AEA encrypted DMG: %v", err)
|
||||
}
|
||||
defer os.Remove(dmgPath)
|
||||
}
|
||||
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgType, dmgPath))
|
||||
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to mount DMG: %v", err)
|
||||
}
|
||||
if alreadyMounted {
|
||||
utils.Indent(log.Debug, 3)(fmt.Sprintf("%s already mounted", dmgPath))
|
||||
} else {
|
||||
defer func() {
|
||||
utils.Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
|
||||
if err := utils.Retry(3, 2*time.Second, func() error {
|
||||
return utils.Unmount(mountPoint, true)
|
||||
}); err != nil {
|
||||
utils.Indent(log.Error, 3)(fmt.Sprintf("failed to unmount %s at %s: %v", dmgPath, mountPoint, err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var files []string
|
||||
if err := filepath.Walk(mountPoint, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Errorf("failed to walk mount %s: %v", mountPoint, err)
|
||||
return nil
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to walk files in dir %s: %v", mountPoint, err)
|
||||
}
|
||||
|
||||
entDB := make(map[string]string)
|
||||
|
||||
for _, file := range files {
|
||||
var m *macho.File
|
||||
fat, err := macho.OpenFat(file)
|
||||
if err == nil {
|
||||
m = fat.Arches[len(fat.Arches)-1].File // grab last arch (probably arm64e)
|
||||
} else {
|
||||
if err == macho.ErrNotFat {
|
||||
m, err = macho.Open(file)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("failed to get entitlements for %s", file)
|
||||
continue // bad macho file (skip)
|
||||
}
|
||||
} else {
|
||||
continue // not a macho file (skip)
|
||||
}
|
||||
}
|
||||
if m.CodeSignature() != nil && len(m.CodeSignature().Entitlements) > 0 {
|
||||
entDB[strings.TrimPrefix(file, mountPoint)] = m.CodeSignature().Entitlements
|
||||
} else {
|
||||
entDB[strings.TrimPrefix(file, mountPoint)] = ""
|
||||
}
|
||||
}
|
||||
|
||||
return entDB, nil
|
||||
return scanEntsFromDMG(localPath, dmgType, pemDbPath)
|
||||
}
|
||||
|
||||
+168
-73
@@ -32,10 +32,29 @@ import (
|
||||
"github.com/blacktop/ipsw/pkg/img4"
|
||||
"github.com/blacktop/ipsw/pkg/info"
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
"github.com/blacktop/ipsw/pkg/ota"
|
||||
"github.com/blacktop/ipsw/pkg/signature"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type InputKind int
|
||||
|
||||
const (
|
||||
InputKindIPSW InputKind = iota
|
||||
InputKindOTA
|
||||
)
|
||||
|
||||
func (k InputKind) String() string {
|
||||
switch k {
|
||||
case InputKindOTA:
|
||||
return "ota"
|
||||
case InputKindIPSW:
|
||||
fallthrough
|
||||
default:
|
||||
return "ipsw"
|
||||
}
|
||||
}
|
||||
|
||||
type kernel struct {
|
||||
Path string
|
||||
Version *kernelcache.Version
|
||||
@@ -93,6 +112,7 @@ type Context struct {
|
||||
Version string
|
||||
Build string
|
||||
Folder string
|
||||
Kind InputKind
|
||||
Mount map[string]mount
|
||||
SystemOsDmgPath string
|
||||
MountPath string
|
||||
@@ -206,37 +226,82 @@ func (d *Diff) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Diff) populateContext(ctx *Context, label string) error {
|
||||
ctx.Kind = InputKindIPSW
|
||||
|
||||
meta, parseErr := info.Parse(ctx.IPSWPath)
|
||||
if parseErr == nil && meta != nil && meta.Plists != nil && meta.Plists.Type != "OTA" {
|
||||
ctx.Info = meta
|
||||
} else {
|
||||
aa, otaErr := ota.Open(ctx.IPSWPath)
|
||||
if otaErr != nil {
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("failed to parse '%s' IPSW: %v; failed to open OTA: %w", strings.ToLower(label), parseErr, otaErr)
|
||||
}
|
||||
return fmt.Errorf("failed to open '%s' OTA: %w", strings.ToLower(label), otaErr)
|
||||
}
|
||||
defer aa.Close()
|
||||
|
||||
otaInfo, err := aa.Info()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse '%s' OTA metadata: %w", strings.ToLower(label), err)
|
||||
}
|
||||
ctx.Info = otaInfo
|
||||
ctx.Kind = InputKindOTA
|
||||
}
|
||||
|
||||
if ctx.Info == nil || ctx.Info.Plists == nil {
|
||||
return fmt.Errorf("missing metadata for %s firmware", strings.ToLower(label))
|
||||
}
|
||||
|
||||
if ctx.Info.Plists.BuildManifest != nil {
|
||||
ctx.Version = ctx.Info.Plists.BuildManifest.ProductVersion
|
||||
ctx.Build = ctx.Info.Plists.BuildManifest.ProductBuildVersion
|
||||
} else if ctx.Info.Plists.AssetDataInfo != nil {
|
||||
ctx.Version = ctx.Info.Plists.AssetDataInfo.ProductVersion
|
||||
ctx.Build = ctx.Info.Plists.AssetDataInfo.Build
|
||||
} else if ctx.Info.Plists.SystemVersion != nil {
|
||||
ctx.Version = ctx.Info.Plists.SystemVersion.ProductVersion
|
||||
ctx.Build = ctx.Info.Plists.SystemVersion.ProductBuildVersion
|
||||
}
|
||||
|
||||
if folder, err := ctx.Info.GetFolder(); err == nil {
|
||||
ctx.Folder = filepath.Join(d.tmpDir, folder)
|
||||
} else {
|
||||
log.Errorf("failed to get folder from '%s' firmware metadata: %v", strings.ToLower(label), err)
|
||||
}
|
||||
|
||||
if ctx.Info.IsMacOS() {
|
||||
ctx.IsMacOS = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Diff) getInfo() (err error) {
|
||||
d.Old.Info, err = info.Parse(d.Old.IPSWPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse 'Old' IPSW: %v", err)
|
||||
if err := d.populateContext(&d.Old, "Old"); err != nil {
|
||||
return err
|
||||
}
|
||||
d.New.Info, err = info.Parse(d.New.IPSWPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse 'New' IPSW: %v", err)
|
||||
if err := d.populateContext(&d.New, "New"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Old.Version = d.Old.Info.Plists.BuildManifest.ProductVersion
|
||||
d.Old.Build = d.Old.Info.Plists.BuildManifest.ProductBuildVersion
|
||||
d.Old.Folder, err = d.Old.Info.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from 'Old' IPSW metadata: %v", err)
|
||||
}
|
||||
d.Old.Folder = filepath.Join(d.tmpDir, d.Old.Folder)
|
||||
|
||||
d.New.Version = d.New.Info.Plists.BuildManifest.ProductVersion
|
||||
d.New.Build = d.New.Info.Plists.BuildManifest.ProductBuildVersion
|
||||
d.New.Folder, err = d.New.Info.GetFolder()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get folder from 'New' IPSW metadata: %v", err)
|
||||
}
|
||||
d.New.Folder = filepath.Join(d.tmpDir, d.New.Folder)
|
||||
|
||||
if d.Title == "" {
|
||||
d.Title = fmt.Sprintf("%s (%s) .vs %s (%s)", d.Old.Version, d.Old.Build, d.New.Version, d.New.Build)
|
||||
fallback := func(val, def string) string {
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
return val
|
||||
}
|
||||
d.Title = fmt.Sprintf("%s (%s) .vs %s (%s)",
|
||||
fallback(d.Old.Version, "unknown"),
|
||||
fallback(d.Old.Build, "unknown"),
|
||||
fallback(d.New.Version, "unknown"),
|
||||
fallback(d.New.Build, "unknown"),
|
||||
)
|
||||
}
|
||||
|
||||
if d.Old.Info.IsMacOS() || d.New.Info.IsMacOS() {
|
||||
if d.Old.IsMacOS || d.New.IsMacOS {
|
||||
d.Old.IsMacOS = true
|
||||
d.New.IsMacOS = true
|
||||
}
|
||||
@@ -244,6 +309,30 @@ func (d *Diff) getInfo() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Diff) logIPSWOnlySectionsSkipped() {
|
||||
skipped := []string{"kernelcache", "dyld_shared_cache", "machos"}
|
||||
if len(d.conf.KDKs) == 2 {
|
||||
skipped = append(skipped, "kdks")
|
||||
}
|
||||
if d.conf.LaunchD {
|
||||
skipped = append(skipped, "launchd")
|
||||
}
|
||||
if d.conf.Firmware {
|
||||
skipped = append(skipped, "firmware", "iboot")
|
||||
}
|
||||
if d.conf.Features {
|
||||
skipped = append(skipped, "feature-flags")
|
||||
}
|
||||
if d.conf.Files {
|
||||
skipped = append(skipped, "files")
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"old": d.Old.Kind.String(),
|
||||
"new": d.New.Kind.String(),
|
||||
}).Warnf("Skipping %s diff sections; OTA support is currently limited to --ent", strings.Join(skipped, ", "))
|
||||
}
|
||||
|
||||
// Diff diffs the diff
|
||||
func (d *Diff) Diff() (err error) {
|
||||
|
||||
@@ -257,63 +346,69 @@ func (d *Diff) Diff() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Diffing KERNELCACHES")
|
||||
if err := d.parseKernelcache(); err != nil {
|
||||
log.WithError(err).Error("failed to parse kernelcaches")
|
||||
}
|
||||
bothIPSW := d.Old.Kind == InputKindIPSW && d.New.Kind == InputKindIPSW
|
||||
|
||||
if d.Old.KDK != "" && d.New.KDK != "" {
|
||||
log.Info("Diffing KDKS")
|
||||
if err := d.parseKDKs(); err != nil {
|
||||
log.WithError(err).Error("failed to parse KDKs")
|
||||
if bothIPSW {
|
||||
log.Info("Diffing KERNELCACHES")
|
||||
if err := d.parseKernelcache(); err != nil {
|
||||
log.WithError(err).Error("failed to parse kernelcaches")
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Diffing DYLD_SHARED_CACHES")
|
||||
if err := d.mountSystemOsDMGs(); err != nil {
|
||||
return fmt.Errorf("failed to mount DMGs: %v", err)
|
||||
}
|
||||
defer d.unmountSystemOsDMGs()
|
||||
|
||||
if err := d.parseDSC(); err != nil {
|
||||
log.WithError(err).Error("failed to parse DSCs")
|
||||
}
|
||||
|
||||
log.Info("Diffing MachOs")
|
||||
if err := d.parseMachos(); err != nil {
|
||||
log.WithError(err).Error("failed to parse MachOs")
|
||||
}
|
||||
|
||||
if d.conf.LaunchD {
|
||||
log.Info("Diffing launchd PLIST")
|
||||
if err := d.parseLaunchdPlists(); err != nil {
|
||||
log.WithError(err).Error("failed to parse launchd plists")
|
||||
if d.Old.KDK != "" && d.New.KDK != "" {
|
||||
log.Info("Diffing KDKS")
|
||||
if err := d.parseKDKs(); err != nil {
|
||||
log.WithError(err).Error("failed to parse KDKs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.conf.Firmware {
|
||||
log.Info("Diffing Firmware")
|
||||
if err := d.parseFirmwares(); err != nil {
|
||||
log.WithError(err).Error("failed to parse firmwares")
|
||||
log.Info("Diffing DYLD_SHARED_CACHES")
|
||||
if err := d.mountSystemOsDMGs(); err != nil {
|
||||
return fmt.Errorf("failed to mount DMGs: %v", err)
|
||||
}
|
||||
log.Info("Diffing iBoot")
|
||||
if err := d.parseIBoot(); err != nil {
|
||||
log.WithError(err).Error("failed to parse iBoot")
|
||||
}
|
||||
}
|
||||
defer d.unmountSystemOsDMGs()
|
||||
|
||||
if d.conf.Features {
|
||||
log.Info("Diffing Feature Flags")
|
||||
if err := d.parseFeatureFlags(); err != nil {
|
||||
log.WithError(err).Error("failed to parse feature flags")
|
||||
if err := d.parseDSC(); err != nil {
|
||||
log.WithError(err).Error("failed to parse DSCs")
|
||||
}
|
||||
}
|
||||
|
||||
if d.conf.Files {
|
||||
log.Info("Diffing Files")
|
||||
if err := d.parseFiles(); err != nil {
|
||||
log.WithError(err).Error("failed to parse files")
|
||||
log.Info("Diffing MachOs")
|
||||
if err := d.parseMachos(); err != nil {
|
||||
log.WithError(err).Error("failed to parse MachOs")
|
||||
}
|
||||
|
||||
if d.conf.LaunchD {
|
||||
log.Info("Diffing launchd PLIST")
|
||||
if err := d.parseLaunchdPlists(); err != nil {
|
||||
log.WithError(err).Error("failed to parse launchd plists")
|
||||
}
|
||||
}
|
||||
|
||||
if d.conf.Firmware {
|
||||
log.Info("Diffing Firmware")
|
||||
if err := d.parseFirmwares(); err != nil {
|
||||
log.WithError(err).Error("failed to parse firmwares")
|
||||
}
|
||||
log.Info("Diffing iBoot")
|
||||
if err := d.parseIBoot(); err != nil {
|
||||
log.WithError(err).Error("failed to parse iBoot")
|
||||
}
|
||||
}
|
||||
|
||||
if d.conf.Features {
|
||||
log.Info("Diffing Feature Flags")
|
||||
if err := d.parseFeatureFlags(); err != nil {
|
||||
log.WithError(err).Error("failed to parse feature flags")
|
||||
}
|
||||
}
|
||||
|
||||
if d.conf.Files {
|
||||
log.Info("Diffing Files")
|
||||
if err := d.parseFiles(); err != nil {
|
||||
log.WithError(err).Error("failed to parse files")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
d.logIPSWOnlySectionsSkipped()
|
||||
}
|
||||
|
||||
if d.conf.Entitlements {
|
||||
@@ -640,12 +735,12 @@ func (d *Diff) parseDSC() error {
|
||||
}
|
||||
|
||||
func (d *Diff) parseEntitlements() (string, error) {
|
||||
oldDB, err := ent.GetDatabase(&ent.Config{IPSW: d.Old.IPSWPath})
|
||||
oldDB, err := ent.GetDatabase(&ent.Config{IPSW: d.Old.IPSWPath, PemDB: d.conf.PemDB})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newDB, err := ent.GetDatabase(&ent.Config{IPSW: d.New.IPSWPath})
|
||||
newDB, err := ent.GetDatabase(&ent.Config{IPSW: d.New.IPSWPath, PemDB: d.conf.PemDB})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user