feat: add launchd config to ipsw diff

This commit is contained in:
blacktop
2023-04-09 23:15:55 -06:00
parent 48ecf929f8
commit 8a1365fc16
5 changed files with 146 additions and 73 deletions
+3 -24
View File
@@ -48,27 +48,6 @@ func isURL(str string) bool {
return err == nil && u.Scheme != "" && u.Host != ""
}
func extractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) error {
// check if filesystem DMG already exists (due to previous mount command)
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)
})
if err != nil {
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
}
if len(dmgs) == 0 {
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
}
defer os.Remove(dmgs[0])
}
if err := utils.ExtractFromDMG(dmgPath, destPath, pattern); err != nil {
return fmt.Errorf("failed to extract files matching pattern: %v", err)
}
return nil
}
func init() {
rootCmd.AddCommand(extractCmd)
@@ -346,17 +325,17 @@ var extractCmd = &cobra.Command{
if viper.GetBool("extract.files") { // SEARCH THE DMGs
if appOS, err := i.GetAppOsDmg(); err == nil {
if err := extractFromDMG(ipswPath, appOS, destPath, patternRE); err != nil {
if err := utils.ExtractFromDMG(ipswPath, appOS, destPath, patternRE); err != nil {
return fmt.Errorf("failed to extract files from AppOS %s: %v", appOS, err)
}
}
if systemOS, err := i.GetSystemOsDmg(); err == nil {
if err := extractFromDMG(ipswPath, systemOS, destPath, patternRE); err != nil {
if err := utils.ExtractFromDMG(ipswPath, systemOS, destPath, patternRE); err != nil {
return fmt.Errorf("failed to extract files from SystemOS %s: %v", systemOS, err)
}
}
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
if err := extractFromDMG(ipswPath, fsOS, destPath, patternRE); err != nil {
if err := utils.ExtractFromDMG(ipswPath, fsOS, destPath, patternRE); err != nil {
return fmt.Errorf("failed to extract files from filesystem %s: %v", fsOS, err)
}
}
+81
View File
@@ -1,3 +1,4 @@
// Package diff provides a way to diff two ipsws
package diff
import (
@@ -7,8 +8,10 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"text/tabwriter"
"time"
@@ -22,18 +25,32 @@ import (
"github.com/blacktop/ipsw/pkg/kernelcache"
)
const (
systemOsDmg = "sys"
appOsDmg = "app"
fileSystemDmg = "fs"
)
type kernel struct {
Path string
Version *kernelcache.Version
Kexts []string
}
type mount struct {
DmgPath string
MountPath string
IsMounted bool
}
// Context is the context for the diff
type Context struct {
IPSWPath string
Info *info.Info
Version string
Build string
Folder string
Mount map[string]mount
SystemOsDmgPath string
MountPath string
IsMounted bool
@@ -41,8 +58,11 @@ type Context struct {
DSC string
Webkit string
KDK string
mu *sync.Mutex
}
// Diff is the diff
type Diff struct {
Title string
@@ -57,6 +77,7 @@ type Diff struct {
Removed string
Updated string
}
Launchd string
tmpDir string
}
@@ -68,9 +89,11 @@ func New(title, ipswOld, ipswNew string, kdks []string) *Diff {
Title: title,
Old: Context{
IPSWPath: ipswOld,
Mount: make(map[string]mount),
},
New: Context{
IPSWPath: ipswNew,
Mount: make(map[string]mount),
},
}
}
@@ -78,10 +101,12 @@ func New(title, ipswOld, ipswNew string, kdks []string) *Diff {
Title: title,
Old: Context{
IPSWPath: ipswOld,
Mount: make(map[string]mount),
KDK: kdks[0],
},
New: Context{
IPSWPath: ipswNew,
Mount: make(map[string]mount),
KDK: kdks[1],
},
}
@@ -166,6 +191,11 @@ func (d *Diff) Diff() (err error) {
return err
}
log.Info("Diffing launchd PLIST")
if err := d.parseLaunchdPlists(); err != nil {
return fmt.Errorf("failed to parse launchd config plists: %v", err)
}
log.Info("Diffing ENTITLEMENTS")
d.Ents, err = d.parseEntitlements()
if err != nil {
@@ -459,3 +489,54 @@ func (d *Diff) parseEntitlements() (string, error) {
DiffTool: "git",
})
}
func (d *Diff) parseLaunchdPlists() error {
oldFsDMG, err := d.Old.Info.GetFileSystemOsDmg()
if err != nil {
return fmt.Errorf("failed to get 'Old' File System DMG: %v", err)
}
if err := utils.ExtractFromDMG(d.Old.IPSWPath, oldFsDMG, filepath.Join(d.tmpDir, "old"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
return err
}
m, err := macho.Open(filepath.Join(d.tmpDir, "old/sbin/launchd"))
if err != nil {
return err
}
defer m.Close()
oldData, err := m.Section("__TEXT", "__config").Data()
if err != nil {
return err
}
newFsDMG, err := d.New.Info.GetFileSystemOsDmg()
if err != nil {
return fmt.Errorf("failed to get 'New' File System DMG: %v", err)
}
if err := utils.ExtractFromDMG(d.New.IPSWPath, newFsDMG, filepath.Join(d.tmpDir, "new"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
return err
}
m2, err := macho.Open(filepath.Join(d.tmpDir, "new/sbin/launchd"))
if err != nil {
return err
}
defer m2.Close()
newData, err := m.Section("__TEXT", "__config").Data()
if err != nil {
return err
}
out, err := utils.GitDiff(
string(oldData)+"\n",
string(newData)+"\n",
&utils.GitDiffConfig{Color: false, Tool: "git"})
if err != nil {
return err
}
if len(out) > 0 {
d.Launchd = "```diff\n" + out + "\n```"
}
return nil
}
+5
View File
@@ -74,6 +74,11 @@ const diffMarkdownTemplate = `
{{ .Ents | noescape }}
{{ if .Launchd }}
## launchd Config
{{ .Launchd | noescape }}
{{end -}}
## DSC
### WebKit
+57 -43
View File
@@ -1,6 +1,7 @@
package utils
import (
"archive/zip"
"bytes"
"encoding/binary"
"errors"
@@ -12,6 +13,7 @@ import (
"regexp"
"runtime"
"strings"
"time"
"github.com/apex/log"
"github.com/blacktop/go-plist"
@@ -382,69 +384,70 @@ func Mount(image, mountPoint string) error {
}
return nil
}
} else if runtime.GOOS == "linux" {
cmd := exec.Command("apfs-fuse", image, mountPoint)
if _, err := exec.LookPath("apfs-fuse"); err != nil {
return fmt.Errorf("utils.Mount: apfs-fuse not found (required on non-darwin systems): %v", err)
}
out, err := cmd.CombinedOutput()
if err != nil {
if strings.Contains(string(out), "hdiutil: mount failed - Resource busy") {
return ErrMountResourceBusy
}
return fmt.Errorf("%v: %s", err, out)
cmd := exec.Command("apfs-fuse", image, mountPoint)
out, err := cmd.CombinedOutput()
if err != nil {
if strings.Contains(string(out), "hdiutil: mount failed - Resource busy") {
return ErrMountResourceBusy
}
return nil
return fmt.Errorf("%v: %s", err, out)
}
return nil
}
func IsAlreadyMounted(image string) (string, bool, error) {
info, err := MountInfo()
if err != nil {
return "", false, err
}
for _, i := range info.Images {
if strings.Contains(i.ImagePath, image) {
for _, entry := range i.SystemEntities {
if entry.MountPoint != "" {
return entry.MountPoint, true, nil
func IsAlreadyMounted(image, mountPoint string) (string, bool, error) {
if runtime.GOOS == "darwin" {
info, err := MountInfo()
if err != nil {
return "", false, err
}
for _, i := range info.Images {
if strings.Contains(i.ImagePath, image) {
for _, entry := range i.SystemEntities {
if entry.MountPoint != "" {
return entry.MountPoint, true, nil
}
}
return "", true, nil
}
return "", true, nil
}
} else if runtime.GOOS == "linux" {
if _, err := os.Stat(filepath.Join(mountPoint, "root")); !os.IsNotExist(err) {
return mountPoint, true, nil
}
}
return "", false, nil
}
func MountFS(image string) (string, bool, error) {
var mountPoint string
mountPoint := fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
if runtime.GOOS == "darwin" {
mountPoint = fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
// check if already mounted
if prevMountPoint, mounted, err := IsAlreadyMounted(image); mounted && err == nil {
if prevMountPoint, mounted, err := IsAlreadyMounted(image, mountPoint); mounted && err == nil {
if prevMountPoint != "" {
mountPoint = prevMountPoint
}
return mountPoint, true, nil
}
} else {
if _, ok := os.LookupEnv("IPSW_IN_DOCKER"); ok {
// Create in-docker mount point
os.MkdirAll("/data", 0750)
mountPoint = "/mnt"
} else {
// Create temporary non-darwin mount point
mountPoint = image + "_temp_mount"
if err := os.Mkdir(mountPoint, 0750); err != nil {
return "", false, fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
}
if err := os.Mkdir(mountPoint, 0750); err != nil {
return "", false, fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
}
}
if err := Mount(image, mountPoint); err != nil {
return "", false, fmt.Errorf("failed to mount %s: %v", image, err)
}
return mountPoint, false, nil
}
@@ -459,20 +462,16 @@ func Unmount(mountPoint string, force bool) error {
cmd = exec.Command("hdiutil", "detach", mountPoint)
}
err := cmd.Run()
if err != nil {
if err := cmd.Run(); err != nil {
var edetail string
if strings.Contains(err.Error(), "exit status 16") {
edetail = " (Resource busy)"
}
return fmt.Errorf("failed to unmount %s%s: %v", mountPoint, edetail, err)
}
} else if runtime.GOOS == "linux" {
cmd := exec.Command("umount", mountPoint)
err := cmd.Run()
if err != nil {
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to unmount %s: %v", mountPoint, err)
}
}
@@ -524,7 +523,20 @@ func MountInfo() (*HdiUtilInfo, error) {
return nil, fmt.Errorf("only supported on macOS")
}
func ExtractFromDMG(dmgPath, destPath string, pattern *regexp.Regexp) error {
func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) error {
// check if filesystem DMG already exists (due to previous mount command)
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
dmgs, err := Unzip(ipswPath, "", func(f *zip.File) bool {
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
})
if err != nil {
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
}
if len(dmgs) == 0 {
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
}
defer os.Remove(dmgs[0])
}
Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
mountPoint, alreadyMounted, err := MountFS(dmgPath)
@@ -536,8 +548,10 @@ func ExtractFromDMG(dmgPath, destPath string, pattern *regexp.Regexp) error {
} else {
defer func() {
Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
if err := Unmount(mountPoint, false); err != nil {
log.Errorf("failed to unmount DMG at %s: %v", dmgPath, err)
if err := Retry(3, 2*time.Second, func() error {
return Unmount(mountPoint, false)
}); err != nil {
log.Errorf("failed to unmount DMG %s at %s: %v", dmgPath, mountPoint, err)
}
}()
}
-6
View File
@@ -77,15 +77,9 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) err
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
}
} else {
// Create temporary mount point
if err := os.MkdirAll(destPath, 0750); err != nil {
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
}
if err := os.MkdirAll(mountPoint, 0750); err != nil {
return fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
} else {
defer os.RemoveAll(mountPoint)
}
}
}