From d4da6b9775096ab0e8b6d0dd066942fa64a46df7 Mon Sep 17 00:00:00 2001 From: blacktop Date: Wed, 23 Oct 2024 19:42:08 -0600 Subject: [PATCH] feat: add ability to parse NEW exclavecore_bundle format (iOS18.2+) --- cmd/ipsw/cmd/fw/exc.go | 31 ++++++--- internal/commands/fw/exclave.go | 108 +++++++++++++++++--------------- pkg/bundle/bundle.go | 28 ++++++--- 3 files changed, 101 insertions(+), 66 deletions(-) diff --git a/cmd/ipsw/cmd/fw/exc.go b/cmd/ipsw/cmd/fw/exc.go index ca59dc337..54f3a43e2 100644 --- a/cmd/ipsw/cmd/fw/exc.go +++ b/cmd/ipsw/cmd/fw/exc.go @@ -26,7 +26,9 @@ import ( "path/filepath" "github.com/apex/log" + "github.com/blacktop/ipsw/internal/commands/extract" fwcmd "github.com/blacktop/ipsw/internal/commands/fw" + "github.com/blacktop/ipsw/internal/magic" "github.com/blacktop/ipsw/internal/utils" "github.com/blacktop/ipsw/pkg/bundle" "github.com/spf13/cobra" @@ -75,13 +77,28 @@ var excCmd = &cobra.Command{ fmt.Println(bn) } else { - log.Info("Extracting Exclave Bundle") - out, err := fwcmd.Extract(filepath.Clean(args[0]), output) - if err != nil { - return fmt.Errorf("failed to extract files from exclave bundle: %v", err) - } - for _, f := range out { - utils.Indent(log.Info, 2)("Created " + f) + if isZip, err := magic.IsZip(filepath.Clean(args[0])); err != nil { + return fmt.Errorf("failed to determine if file is a zip: %v", err) + } else if isZip { + out, err := extract.Exclave(&extract.Config{ + IPSW: filepath.Clean(args[0]), + Output: viper.GetString("fw.exclave.output"), + }) + if err != nil { + return err + } + for _, f := range out { + utils.Indent(log.Info, 2)("Created " + f) + } + } else { + log.Info("Extracting Exclave Bundle") + out, err := fwcmd.Extract(filepath.Clean(args[0]), output) + if err != nil { + return fmt.Errorf("failed to extract files from exclave bundle: %v", err) + } + for _, f := range out { + utils.Indent(log.Info, 2)("Created " + f) + } } } diff --git a/internal/commands/fw/exclave.go b/internal/commands/fw/exclave.go index 52da14488..395899483 100644 --- a/internal/commands/fw/exclave.go +++ b/internal/commands/fw/exclave.go @@ -32,6 +32,8 @@ func Extract(input, output string) ([]string, error) { } defer f.Close() + assetIndex := 0 + for idx, bf := range bn.Files { fname := filepath.Join(output, bf.Type, bf.Name) if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil { @@ -48,37 +50,36 @@ func Extract(input, output string) ([]string, error) { continue } - if entry := bn.Config.TOC[idx].GetEntry(); entry != nil && entry.Type == 1 { // roottask (APP) - fname := filepath.Join(output, bf.Type, string(entry.Name.Bytes)) - attr, err := os.Create(fname) - if err != nil { - return nil, fmt.Errorf("failed to create file %s: %v", fname, err) + entry := bn.Config.TOC[idx].GetEntry() + + switch { + case entry == nil: // APP (MachO) + text := bf.Segment("TEXT") + if text == nil { + text = bf.Segment("HEADER") + if text == nil { + return nil, fmt.Errorf("failed to find TEXT segment") + } } - defer attr.Close() - if _, err := f.Seek(int64(bn.Config.Assets[idx].Offset), io.SeekStart); err != nil { - return nil, fmt.Errorf("failed to seek to offset %d: %v", bn.Config.Assets[idx].Offset, err) + if _, err := f.Seek(int64(text.Offset), io.SeekStart); err != nil { + return nil, fmt.Errorf("failed to seek to offset %d: %v", text.Offset, err) } - adata := make([]byte, bn.Config.Assets[idx].Size) // brkr_artifact - if err := binary.Read(f, binary.LittleEndian, &adata); err != nil { + tdata := make([]byte, text.Size) + if err := binary.Read(f, binary.LittleEndian, &tdata); err != nil { return nil, fmt.Errorf("failed to read data from file %s: %v", fname, err) } - // var brkr dict - // _, err := asn1.Unmarshal(adata, &brkr) - // if err != nil { - // return nil, fmt.Errorf("failed to unmarshal data from file %s: %v", fname, err) - // } - if _, err := attr.Write(adata); err != nil { - return nil, fmt.Errorf("failed to write data to file %s: %v", fname, err) + m, err = macho.NewFile(bytes.NewReader(tdata), macho.FileConfig{ + LoadIncluding: []types.LoadCmd{types.LC_SEGMENT_64}, + }) + if err != nil { + return nil, fmt.Errorf("failed to parse MachO file: %v", err) } - outfiles = append(outfiles, fname) - } - - // Get MachO header - if entry := bn.Config.TOC[idx].GetEntry(); entry != nil && entry.Type == 2 { // kernel (SYSTEM) - if _, err := f.Seek(int64(bn.Config.Assets[idx].Offset), io.SeekStart); err != nil { - return nil, fmt.Errorf("failed to seek to offset %d: %v", bn.Config.Assets[idx].Offset, err) + defer m.Close() + case bf.Type == "SYSTEM": // ASSET (SYSTEM kernel) + if _, err := f.Seek(int64(bn.Config.Assets[assetIndex].Offset), io.SeekStart); err != nil { + return nil, fmt.Errorf("failed to seek to offset %d: %v", bn.Config.Assets[assetIndex].Offset, err) } - mHdrData := make([]byte, bn.Config.Assets[idx].Size) // __MACHOHEADERLC + mHdrData := make([]byte, bn.Config.Assets[assetIndex].Size) // __MACHOHEADERLC if err := binary.Read(f, binary.LittleEndian, &mHdrData); err != nil { return nil, fmt.Errorf("failed to read data from file %s: %v", fname, err) } @@ -93,44 +94,51 @@ func Extract(input, output string) ([]string, error) { if _, err := of.Write(mHdrData); err != nil { return nil, fmt.Errorf("failed to write data to file %s: %v", fname, err) } - } else { - if text := bf.Segment("TEXT"); text == nil { - return nil, fmt.Errorf("failed to find TEXT segment") - } else { - if _, err := f.Seek(int64(text.Offset), io.SeekStart); err != nil { - return nil, fmt.Errorf("failed to seek to offset %d: %v", text.Offset, err) - } - tdata := make([]byte, text.Size) - if err := binary.Read(f, binary.LittleEndian, &tdata); err != nil { - return nil, fmt.Errorf("failed to read data from file %s: %v", fname, err) - } - m, err = macho.NewFile(bytes.NewReader(tdata), macho.FileConfig{ - LoadIncluding: []types.LoadCmd{types.LC_SEGMENT_64}, - }) - if err != nil { - return nil, fmt.Errorf("failed to parse MachO file: %v", err) - } - defer m.Close() + assetIndex++ + default: // ASSET (APP non-MachO) + fname := filepath.Join(output, bf.Type, string(entry.Name.Bytes)) + attr, err := os.Create(fname) + if err != nil { + return nil, fmt.Errorf("failed to create file %s: %v", fname, err) } + defer attr.Close() + if _, err := f.Seek(int64(bn.Config.Assets[assetIndex].Offset), io.SeekStart); err != nil { + return nil, fmt.Errorf("failed to seek to offset %d: %v", bn.Config.Assets[assetIndex].Offset, err) + } + adata := make([]byte, bn.Config.Assets[assetIndex].Size) // brkr_artifact + if err := binary.Read(f, binary.LittleEndian, &adata); err != nil { + return nil, fmt.Errorf("failed to read data from file %s: %v", fname, err) + } + // TODO: parse 'brkr_artifact' which is a map[string]any or a plist essentially + // var brkr map[string]any + // if _, err := asn1.Unmarshal(adata, &brkr); err != nil { + // return nil, fmt.Errorf("failed to unmarshal data from file %s: %v", fname, err) + // } + // os.WriteFile(filepath.Join(output, bf.Type, "data.json"), adata, 0o644) + if _, err := attr.Write(adata); err != nil { + return nil, fmt.Errorf("failed to write data to file %s: %v", fname, err) + } + outfiles = append(outfiles, fname) + assetIndex++ + continue } - for _, seg := range bf.Segments { - if _, err := f.Seek(int64(seg.Offset), io.SeekStart); err != nil { - return nil, fmt.Errorf("failed to seek to offset %d: %v", seg.Offset, err) + for _, sec := range bf.Sections { + if _, err := f.Seek(int64(sec.Offset), io.SeekStart); err != nil { + return nil, fmt.Errorf("failed to seek to offset %d: %v", sec.Offset, err) } - data := make([]byte, seg.Size) + data := make([]byte, sec.Size) if err := binary.Read(f, binary.LittleEndian, &data); err != nil { return nil, fmt.Errorf("failed to read data from file %s: %v", fname, err) } - if s := m.Segment("__" + seg.Name); s == nil { // lookup segment in MachO header - return nil, fmt.Errorf("failed to find segment %s", seg.Name) + if s := m.Segment("__" + sec.Name); s == nil { // lookup segment in MachO header + return nil, fmt.Errorf("failed to find segment %s", sec.Name) } else { if _, err := of.WriteAt(data, int64(s.Offset)); err != nil { return nil, fmt.Errorf("failed to write data to file %s: %v", fname, err) } } } - outfiles = append(outfiles, fname) } diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index 5a516e24e..845e81d9a 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -45,7 +45,7 @@ func (b Bundle) String() string { s += fmt.Sprintf(" Unk2: %d\n", b.Config.Unk2) s += " Assets:\n" for i, h := range b.Config.Assets { - s += fmt.Sprintf(" %3s) %s\n", fmt.Sprintf("%d", i+1), h) + s += fmt.Sprintf(" %3s) %-20s\n", fmt.Sprintf("%d", i+1), h) } s += " TOC:\n" for _, t := range b.Config.TOC { @@ -82,6 +82,7 @@ type File struct { Segments []Segment Sections []Section Endpoints []Endpoint + Extra map[string]any } func (f File) Segment(name string) *Segment { @@ -99,22 +100,28 @@ func (f File) String() string { if seg.Size == 0 { continue } - s += fmt.Sprintf(" sz=0x%08x off=0x%08x-0x%08x __%s\n", seg.Size, seg.Offset, seg.Offset+seg.Size, seg.Name) - for _, sec := range f.Sections { - if sec.Size == 0 { - continue - } - if strings.HasPrefix(sec.Name, seg.Name) && !strings.EqualFold(sec.Name, seg.Name) { - s += fmt.Sprintf(" sz=0x%08x off=0x%08x-0x%08x __%s\n", sec.Size, sec.Offset, sec.Offset+sec.Size, sec.Name) - } + if seg.Name == "HEADER" { + s += fmt.Sprintf(" sz=0x%08x off=0x%08x-0x%08x __%s\n", seg.Size, seg.Offset, seg.Offset+seg.Size, seg.Name) } } + for _, sec := range f.Sections { + if sec.Size == 0 { + continue + } + s += fmt.Sprintf(" sz=0x%08x off=0x%08x-0x%08x __%s\n", sec.Size, sec.Offset, sec.Offset+sec.Size, sec.Name) + } if len(f.Endpoints) > 0 { s += " endpoints:\n" for i, ep := range f.Endpoints { s += fmt.Sprintf(" %3s) %s\n", fmt.Sprintf("%d", i+1), ep) } } + if len(f.Extra) > 0 { + s += " extra:\n" + for k, v := range f.Extra { + s += fmt.Sprintf(" %s: %v\n", k, v) + } + } return s } @@ -343,6 +350,7 @@ func (b *Bundle) ParseFiles() error { var sec Section var seg Segment entpoints := make(map[int]Endpoint, 0) + f.Extra = make(map[string]any) for _, md := range bf.Metadata { val, err := md.ParseValue() if err != nil { @@ -388,6 +396,8 @@ func (b *Bundle) ParseFiles() error { } else { return fmt.Errorf("failed to parse bundle file endpoint index: %v", err) } + } else { + f.Extra[string(md.Key.Bytes)] = string(md.Value.Bytes) } } sort.Slice(f.Sections, func(i, j int) bool {