Skip to content

Commit

Permalink
chore: add --dyld to ipsw ota extract cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop committed Aug 3, 2024
1 parent a7f9683 commit a4397c6
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 132 deletions.
83 changes: 48 additions & 35 deletions cmd/ipsw/cmd/ota/ota_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/apex/log"
"github.com/blacktop/ipsw/internal/utils"
"github.com/blacktop/ipsw/pkg/dyld"
"github.com/blacktop/ipsw/pkg/kernelcache"
"github.com/blacktop/ipsw/pkg/ota"
"github.com/fatih/color"
Expand Down Expand Up @@ -98,48 +99,60 @@ var otaExtractCmd = &cobra.Command{
output = filepath.Join(viper.GetString("ota.extract.output"), output)
}

/* DYLD_SHARED_CACHE */
if viper.GetBool("ota.extract.dyld") {
log.Info("Extracting dyld_shared_cache Files")
return fmt.Errorf("--dyld extraction not implemented yet")
}
/* KERNELCACHE */
if viper.GetBool("ota.extract.kernel") {
log.Info("Extracting kernelcache(s)")
re := regexp.MustCompile(`kernelcache.*$`)
for _, f := range o.Files() { // search in OTA asset files
if f.IsDir() {
continue
if viper.GetBool("ota.extract.dyld") || viper.GetBool("ota.extract.kernel") {
cwd, _ := os.Getwd()
/* DYLD_SHARED_CACHE */
if viper.GetBool("ota.extract.dyld") {
log.Info("Extracting dyld_shared_cache Files")
out, err := o.ExtractFromCryptexes(dyld.CacheUberRegex, output)
if err != nil {
return fmt.Errorf("failed to extract dyld_shared_cache: %v", err)
}
if re.MatchString(f.Path()) {
ff, err := o.Open(f.Path(), false)
if err != nil {
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
}
data, err := io.ReadAll(ff)
if err != nil {
return fmt.Errorf("failed to read kernelcache: %v", err)
}
comp, err := kernelcache.ParseImg4Data(data)
if err != nil {
return fmt.Errorf("failed to parse kernelcache: %v", err)
}
kdata, err := kernelcache.DecompressData(comp)
if err != nil {
return fmt.Errorf("failed to parse kernelcache compressed data: %v", err)
}
fname := filepath.Join(output, f.Name())
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
for _, fname := range out {
rel, _ := filepath.Rel(cwd, fname)
utils.Indent(log.Info, 2)(rel)
}
}
/* KERNELCACHE */
if viper.GetBool("ota.extract.kernel") {
log.Info("Extracting kernelcache(s)")
re := regexp.MustCompile(`kernelcache.*$`)
for _, f := range o.Files() { // search in OTA asset files
if f.IsDir() {
continue
}
utils.Indent(log.Info, 2)(fname)
if err := os.WriteFile(fname, kdata, 0o644); err != nil {
return fmt.Errorf("failed to write kernelcache: %v", err)
if re.MatchString(f.Path()) {
ff, err := o.Open(f.Path(), false)
if err != nil {
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
}
data, err := io.ReadAll(ff)
if err != nil {
return fmt.Errorf("failed to read kernelcache: %v", err)
}
comp, err := kernelcache.ParseImg4Data(data)
if err != nil {
return fmt.Errorf("failed to parse kernelcache: %v", err)
}
kdata, err := kernelcache.DecompressData(comp)
if err != nil {
return fmt.Errorf("failed to parse kernelcache compressed data: %v", err)
}
fname := filepath.Join(output, f.Name())
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
}
rel, _ := filepath.Rel(cwd, fname)
utils.Indent(log.Info, 2)(rel)
if err := os.WriteFile(fname, kdata, 0o644); err != nil {
return fmt.Errorf("failed to write kernelcache: %v", err)
}
}
}
}
return nil
}

/* ALL FILES */
if len(args) == 1 && !viper.IsSet("ota.extract.pattern") {
log.Info("Extracting All Files From OTA")
Expand Down
94 changes: 94 additions & 0 deletions pkg/ota/aa.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/blacktop/ipsw/pkg/bom"
"github.com/blacktop/ipsw/pkg/info"
"github.com/blacktop/ipsw/pkg/ota/pbzx"
"github.com/blacktop/ipsw/pkg/ota/ridiff"
"github.com/blacktop/ipsw/pkg/ota/yaa"
"github.com/dustin/go-humanize"
"golang.org/x/exp/maps"
Expand Down Expand Up @@ -538,6 +539,99 @@ func aaExtractPattern(in io.Reader, pattern, output string) error {
return nil
}

func (r *Reader) ExtractFromCryptexes(pattern, output string) ([]string, error) {
var out []string

match, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("failed to compile extract regex pattern '%s': %v", pattern, err)
}

tmpdir, err := os.MkdirTemp("", "ota_extract_cryptexes")
if err != nil {
return nil, fmt.Errorf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpdir)

for _, cryptex := range []string{"cryptex-system-arm64?e$", "cryptex-app$"} {
re := regexp.MustCompile(cryptex)
for _, file := range r.Files() {
if re.MatchString(file.Name()) {
cryptexFile, err := r.Open(file.Path(), false)
if err != nil {
return nil, fmt.Errorf("failed to open cryptex file: %v", err)
}
defer cryptexFile.Close()
// create a temp file to hold the OTA cryptex
cf, err := os.Create(filepath.Join(tmpdir, file.Name()))
if err != nil {
return nil, fmt.Errorf("failed to create file: %v", err)
}
// create a temp file to hold the PATCHED OTA cryptex DMG
dcf, err := os.Create(filepath.Join(tmpdir, file.Name()+".dmg"))
if err != nil {
return nil, fmt.Errorf("failed to create file: %v", err)
}
if _, err := io.Copy(cf, cryptexFile); err != nil {
return nil, fmt.Errorf("failed to write file: %v", err)
}
cf.Close()
// patch the cryptex
if err := ridiff.RawImagePatch("", cf.Name(), dcf.Name(), 0); err != nil {
return nil, fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Path()), err)
}
dcf.Close()
// mount the patched cryptex
utils.Indent(log.Info, 4)(fmt.Sprintf("Mounting DMG %s", dcf.Name()))
mountPoint, alreadyMounted, err := utils.MountDMG(dcf.Name())
if err != nil {
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
}
if alreadyMounted {
utils.Indent(log.Debug, 5)(fmt.Sprintf("%s already mounted", dcf.Name()))
} else {
defer func() {
utils.Indent(log.Debug, 4)(fmt.Sprintf("Unmounting %s", dcf.Name()))
if err := utils.Retry(3, 2*time.Second, func() error {
return utils.Unmount(mountPoint, false)
}); err != nil {
log.Errorf("failed to unmount DMG %s at %s: %v", dcf.Name(), mountPoint, err)
}
}()
}
// extract files from the mounted cryptex
if err := filepath.Walk(mountPoint, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk %s: %v", path, err)
}
if info.IsDir() {
return nil
}
if match.MatchString(path) {
fname := filepath.Join(output, strings.TrimPrefix(path, mountPoint))
if err := utils.MkdirAndCopy(path, fname); err != nil {
return fmt.Errorf("failed to copy %s to %s: %v", path, fname, err)
}
out = append(out, fname)
}
return nil
}); err != nil {
if errors.Is(err, filepath.SkipDir) {
break
}
return nil, fmt.Errorf("failed to read files in cryptex folder: %v", err)
}
}
}
}

if len(out) == 0 {
return nil, fmt.Errorf("no files found matching pattern '%s'", pattern)
}

return out, nil
}

// Open opens the named file in the ZIP archive,
// using the semantics of fs.FS.Open:
// paths are always slash separated, with no
Expand Down
97 changes: 0 additions & 97 deletions pkg/ota/ota.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import (
"encoding/binary"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/apex/log"
"github.com/blacktop/ipsw/internal/utils"
"github.com/blacktop/ipsw/pkg/bom"
"github.com/blacktop/ipsw/pkg/ota/ridiff"
"github.com/blacktop/ipsw/pkg/ota/yaa"
"github.com/dustin/go-humanize"
"github.com/pkg/errors"
Expand Down Expand Up @@ -149,100 +146,6 @@ func RemoteExtract(zr *zip.Reader, extractPattern, destPath string, shouldStop f
return nil, fmt.Errorf("%s not found", extractPattern)
}

// ExtractFromCryptexes extracts files from patched OTA cryptexes
func ExtractFromCryptexes(zr *zip.ReadCloser, extractPattern, destPath string, shouldStop func(string) bool) error {
found := false

for _, cryptex := range []string{"cryptex-system-arm64?e$", "cryptex-app$"} {
re := regexp.MustCompile(cryptex)
for _, zf := range zr.File {
if re.MatchString(zf.Name) {
rc, err := zf.Open()
if err != nil {
return fmt.Errorf("failed to open %s: %v", zf.Name, err)
}
defer rc.Close()

in, err := os.CreateTemp("", filepath.Base(zf.Name))
if err != nil {
return fmt.Errorf("failed to create temp file for %s: %v", filepath.Base(zf.Name), err)
}
defer os.Remove(in.Name())

utils.Indent(log.Info, 3)(fmt.Sprintf("Extracting '%s' from remote OTA", filepath.Base(zf.Name)))
io.Copy(in, rc)
in.Close()

out, err := os.CreateTemp("", filepath.Base(zf.Name)+".decrypted.*.dmg")
if err != nil {
return fmt.Errorf("failed to create temp file for cryptex-system-arm64e.decrypted: %v", err)
}
defer os.Remove(out.Name())
out.Close()

utils.Indent(log.Info, 3)(fmt.Sprintf("Patching '%s'", filepath.Base(zf.Name)))
if err := ridiff.RawImagePatch("", in.Name(), out.Name(), 0); err != nil {
return fmt.Errorf("failed to patch %s: %v", filepath.Base(zf.Name), err)
}

utils.Indent(log.Info, 4)(fmt.Sprintf("Mounting DMG %s", out.Name()))
var alreadyMounted bool
mountPoint, alreadyMounted, err := utils.MountDMG(out.Name())
if err != nil {
return fmt.Errorf("failed to IPSW FS dmg: %v", err)
}
if alreadyMounted {
utils.Indent(log.Debug, 5)(fmt.Sprintf("%s already mounted", out.Name()))
} else {
defer func() {
utils.Indent(log.Debug, 4)(fmt.Sprintf("Unmounting %s", out.Name()))
if err := utils.Retry(3, 2*time.Second, func() error {
return utils.Unmount(mountPoint, false)
}); err != nil {
log.Errorf("failed to unmount DMG %s at %s: %v", out.Name(), mountPoint, err)
}
}()
}

match, err := regexp.Compile(extractPattern)
if err != nil {
return fmt.Errorf("failed to compile extract regex pattern '%s': %v", extractPattern, err)
}
if err := filepath.Walk(mountPoint, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk %s: %v", path, err)
}
if info.IsDir() {
return nil
}
if match.MatchString(path) {
found = true
utils.Indent(log.Debug, 5)(fmt.Sprintf("Extracting %s", strings.TrimPrefix(path, mountPoint)))
if err := utils.MkdirAndCopy(path, filepath.Join(destPath, strings.TrimPrefix(path, mountPoint))); err != nil {
return fmt.Errorf("failed to copy %s to %s: %v", path, filepath.Join(destPath, strings.TrimPrefix(path, mountPoint)), err)
}
if shouldStop(path) {
return filepath.SkipAll
}
}
return nil
}); err != nil {
if errors.Is(err, filepath.SkipDir) {
break
}
return fmt.Errorf("failed to read files in cryptex folder: %v", err)
}
}
}
}

if found {
return nil
}

return fmt.Errorf("'%s' not found", extractPattern)
}

// Parse parses a ota payload file inside the zip
func Parse(payload *zip.File, folder, extractPattern string) (bool, string, error) {

Expand Down

0 comments on commit a4397c6

Please sign in to comment.