Skip to content

Commit

Permalink
feat: add ability to parse NEW exclavecore_bundle format (iOS18.2+)
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop committed Oct 24, 2024
1 parent 2220330 commit d4da6b9
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 66 deletions.
31 changes: 24 additions & 7 deletions cmd/ipsw/cmd/fw/exc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
108 changes: 58 additions & 50 deletions internal/commands/fw/exclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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)
}

Expand Down
28 changes: 19 additions & 9 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -82,6 +82,7 @@ type File struct {
Segments []Segment
Sections []Section
Endpoints []Endpoint
Extra map[string]any
}

func (f File) Segment(name string) *Segment {
Expand All @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit d4da6b9

Please sign in to comment.