Skip to content

Commit

Permalink
fix: prebuilt loader set fallback in iOS18 (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop authored Sep 1, 2024
1 parent 4ef4fc2 commit 58d77dd
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 99 deletions.
14 changes: 11 additions & 3 deletions cmd/ipsw/cmd/dyld/dyld_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var ImageCmd = &cobra.Command{
return getDSCs(toComplete), cobra.ShellCompDirectiveDefault
},
SilenceErrors: true,
SilenceUsage: true,
Example: ` # List all the apps
❯ ipsw dyld image DSC
# Dump the closure info for a in-cache dylib
Expand Down Expand Up @@ -103,7 +104,9 @@ var ImageCmd = &cobra.Command{
if pbl, err := f.GetDylibPrebuiltLoader(image.Name); err == nil {
fmt.Println(pbl.String(f))
} else {
if !errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
if errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
log.Warn("prebuilt loader sets not supported for this version of dyld_shared_cache")
} else {
return fmt.Errorf("failed parsing launch loader sets: %v", err)
}
// try to parse the dylib closures using the old iOS14.x method
Expand All @@ -122,7 +125,9 @@ var ImageCmd = &cobra.Command{
if pset, err := f.GetLaunchLoaderSet(args[1]); err == nil {
fmt.Println(pset.String(f))
} else {
if !errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
if errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
log.Warn("prebuilt loader sets not supported for this version of dyld_shared_cache")
} else {
return fmt.Errorf("failed parsing launch loader sets: %v", err)
}
// try to parse the app closures using the old iOS14.x method
Expand All @@ -134,6 +139,7 @@ var ImageCmd = &cobra.Command{
fmt.Println(ci.String(f, viper.GetBool("verbose")))
return nil
} else {
log.Warn("failed to find app in 'other' image arrays: attempting to find in program launch closures")
for _, clos := range f.Closures {
for _, img := range clos.Images {
if img.Name == args[1] {
Expand All @@ -150,7 +156,9 @@ var ImageCmd = &cobra.Command{
if err := f.ForEachLaunchLoaderSetPath(func(execPath string) {
fmt.Println(execPath)
}); err != nil {
if !errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
if errors.Is(err, dyld.ErrPrebuiltLoaderSetNotSupported) {
log.Warn("prebuilt loader sets not supported for this version of dyld_shared_cache")
} else {
return fmt.Errorf("failed parsing launch loader sets: %v", err)
}
// try to parse the image array using the old iOS14.x method
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/dsc/dsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func GetDylibsThatImport(f *dyld.File, name string) (*ImportedBy, error) {
}
}

if f.SupportsPrebuiltLoaderSet() {
if f.SupportsProgramTrie() {
if err := f.ForEachLaunchLoaderSet(func(execPath string, pset *dyld.PrebuiltLoaderSet) {
for _, loader := range pset.Loaders {
for _, dep := range loader.Dependents {
Expand Down
7 changes: 0 additions & 7 deletions pkg/dyld/closure.go
Original file line number Diff line number Diff line change
Expand Up @@ -961,14 +961,7 @@ func (f *File) GetDylibsImageArray() error {
var size uint64

if f.Headers[f.UUID].DylibsImageArrayAddr == 0 {
if f.Headers[f.UUID].DylibsPblSetAddr > 0 {
return fmt.Errorf("ipsw cannot parse dylibs image array info for macOS12+/iOS15+ yet 😔")
}
return fmt.Errorf("cache does not contain dylibs image array info")
// } else {
// addr = f.Headers[f.UUID].DylibsPblSetAddr
// size = f.Headers[f.UUID].ProgramsPblSetPoolAddr - f.Headers[f.UUID].DylibsPblSetAddr
// }
} else {
addr = f.Headers[f.UUID].DylibsImageArrayAddr
size = f.Headers[f.UUID].DylibsImageArraySize
Expand Down
162 changes: 88 additions & 74 deletions pkg/dyld/prebuilt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/blacktop/go-macho/types"
)

func (f *File) SupportsPrebuiltLoaderSet() bool {
func (f *File) SupportsProgramTrie() bool {
if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].ProgramTrieSize)) {
return false
}
Expand All @@ -24,11 +24,8 @@ func (f *File) SupportsPrebuiltLoaderSet() bool {
}

func (f *File) ForEachLaunchLoaderSet(handler func(execPath string, pset *PrebuiltLoaderSet)) error {
if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].ProgramTrieSize)) {
return ErrPrebuiltLoaderSetNotSupported
}
if f.Headers[f.UUID].ProgramTrieAddr == 0 {
return ErrPrebuiltLoaderSetNotSupported
if !f.SupportsProgramTrie() {
return ErrProgramTrieNotSupported
}

uuid, off, err := f.GetOffset(f.Headers[f.UUID].ProgramTrieAddr)
Expand Down Expand Up @@ -73,11 +70,8 @@ func (f *File) ForEachLaunchLoaderSet(handler func(execPath string, pset *Prebui
}

func (f *File) ForEachLaunchLoaderSetPath(handler func(execPath string)) error {
if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].ProgramTrieSize)) {
return ErrPrebuiltLoaderSetNotSupported
}
if f.Headers[f.UUID].ProgramTrieAddr == 0 {
return ErrPrebuiltLoaderSetNotSupported
if !f.SupportsProgramTrie() {
return ErrProgramTrieNotSupported
}

uuid, off, err := f.GetOffset(f.Headers[f.UUID].ProgramTrieAddr)
Expand Down Expand Up @@ -106,10 +100,10 @@ func (f *File) ForEachLaunchLoaderSetPath(handler func(execPath string)) error {

// GetLaunchLoaderSet returns the PrebuiltLoaderSet for the given executable app path.
func (f *File) GetLaunchLoaderSet(executablePath string) (*PrebuiltLoaderSet, error) {
if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].ProgramTrieSize)) {
return nil, ErrPrebuiltLoaderSetNotSupported
if !f.SupportsProgramTrie() {
return nil, ErrProgramTrieNotSupported
}
if f.Headers[f.UUID].ProgramTrieAddr == 0 {
if !f.SupportsDylibPrebuiltLoader() {
return nil, ErrPrebuiltLoaderSetNotSupported
}

Expand Down Expand Up @@ -164,24 +158,17 @@ func (f *File) SupportsDylibPrebuiltLoader() bool {
// GetLaunchLoader returns the PrebuiltLoader for the given executable in-cache dylib path.
func (f *File) GetDylibPrebuiltLoader(executablePath string) (*PrebuiltLoader, error) {

if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].ProgramTrieSize)) {
return nil, ErrPrebuiltLoaderSetNotSupported
}
if f.Headers[f.UUID].MappingOffset < uint32(unsafe.Offsetof(f.Headers[f.UUID].DylibsPblSetAddr)) {
return nil, ErrPrebuiltLoaderSetNotSupported
}
// FIXME: REMOVE once I have added iOS 18.x support
if f.Headers[f.UUID].MappingOffset > uint32(unsafe.Offsetof(f.Headers[f.UUID].TPROMappingOffset)) {
return nil, ErrPrebuiltLoaderSetNotSupported
}
if f.Headers[f.UUID].DylibsPblSetAddr == 0 {
if !f.SupportsDylibPrebuiltLoader() {
return nil, ErrPrebuiltLoaderSetNotSupported
}

uuid, off, err := f.GetOffset(f.Headers[f.UUID].DylibsPblSetAddr)
if err != nil {
return nil, err
}
// if sc := f.GetSubCacheInfo(uuid); sc != nil {
// log.Debug(sc.Extention)
// }

sr := io.NewSectionReader(f.r[uuid], int64(off), 1<<63-1)

Expand All @@ -190,6 +177,10 @@ func (f *File) GetDylibPrebuiltLoader(executablePath string) (*PrebuiltLoader, e
return nil, err
}

if pset.Magic != PrebuiltLoaderSetMagic {
return nil, fmt.Errorf("invalid magic for PrebuiltLoaderSet: expected %x got %x", PrebuiltLoaderSetMagic, pset.Magic)
}

sr.Seek(int64(pset.LoadersArrayOffset), io.SeekStart)

loaderOffsets := make([]uint32, pset.LoadersArrayCount)
Expand All @@ -206,6 +197,8 @@ func (f *File) GetDylibPrebuiltLoader(executablePath string) (*PrebuiltLoader, e

sr.Seek(int64(loaderOffsets[imgIdx]), io.SeekStart)

// fmt.Println(executablePath)

return f.parsePrebuiltLoader(io.NewSectionReader(f.r[uuid], int64(off)+int64(loaderOffsets[imgIdx]), 1<<63-1))
}

Expand Down Expand Up @@ -281,7 +274,7 @@ func (f *File) parsePrebuiltLoaderSet(sr *io.SectionReader) (*PrebuiltLoaderSet,
}
pset.SelectorTable = &o
}
if pset.ObjcClassHashTableOffset > 0 {
if pset.ObjcClassHashTableOffset > 0 && !pset.Loaders[0].Loader.IsVersion2() {
sr.Seek(int64(pset.ObjcClassHashTableOffset), io.SeekStart)
var o ObjCClassOpt
if err := binary.Read(sr, f.ByteOrder, &o.objCStringTable); err != nil {
Expand Down Expand Up @@ -315,7 +308,7 @@ func (f *File) parsePrebuiltLoaderSet(sr *io.SectionReader) (*PrebuiltLoaderSet,
}
pset.ClassTable = &o
}
if pset.ObjcProtocolHashTableOffset > 0 {
if pset.ObjcProtocolHashTableOffset > 0 && !pset.Loaders[0].Loader.IsVersion2() {
sr.Seek(int64(pset.ObjcProtocolHashTableOffset), io.SeekStart)
var o ObjCClassOpt
if err := binary.Read(sr, f.ByteOrder, &o.objCStringTable); err != nil {
Expand Down Expand Up @@ -430,58 +423,77 @@ func (f *File) parsePrebuiltLoaderSet(sr *io.SectionReader) (*PrebuiltLoaderSet,
// parsePrebuiltLoader parses a prebuilt loader from a section reader.
func (f *File) parsePrebuiltLoader(sr *io.SectionReader) (*PrebuiltLoader, error) {
var pbl PrebuiltLoader
if err := binary.Read(sr, binary.LittleEndian, &pbl.prebuiltLoaderHeader); err != nil {
return nil, err

if err := binary.Read(sr, binary.LittleEndian, &pbl.Loader); err != nil {
return nil, fmt.Errorf("failed to read prebuilt loader: %v", err)
}

if pbl.Magic != LoaderMagic {
return nil, fmt.Errorf("invalid magic for prebuilt loader: expected %x got %x", LoaderMagic, pbl.Magic)
if pbl.Loader.IsVersion2() {
// skip unknown data => [12]uint16
sr.Seek(24, io.SeekCurrent)
}

if pbl.PathOffset > 0 {
sr.Seek(int64(pbl.PathOffset), io.SeekStart)
br := bufio.NewReader(sr)
path, err := br.ReadString('\x00')
if err := binary.Read(sr, binary.LittleEndian, &pbl.Header); err != nil {
return nil, fmt.Errorf("failed to read prebuilt loader header: %v", err)
}

if pbl.Loader.Magic != LoaderMagic {
return nil, fmt.Errorf("invalid magic for prebuilt loader: expected %x got %x", LoaderMagic, pbl.Loader.Magic)
}

// log.Debug(pbl.Loader.String())
// log.Debug(pbl.GetInfo())

// fmt.Println("Loader Info:")
// fmt.Println(utils.Bits(uint64(pbl.Loader.Info)))
// fmt.Println("Info:")
// fmt.Println(pbl.GetInfo())

if pbl.Header.PathOffset > 0 {
sr.Seek(int64(pbl.Header.PathOffset), io.SeekStart)
path, err := bufio.NewReader(sr).ReadString('\x00')
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader path: %v", err)
}
pbl.Path = strings.TrimSuffix(path, "\x00")
}
if pbl.AltPathOffset > 0 {
sr.Seek(int64(pbl.AltPathOffset), io.SeekStart)
br := bufio.NewReader(sr)
path, err := br.ReadString('\x00')
if pbl.Header.AltPathOffset > 0 {
sr.Seek(int64(pbl.Header.AltPathOffset), io.SeekStart)
path, err := bufio.NewReader(sr).ReadString('\x00')
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader alt path: %v", err)
}
pbl.AltPath = strings.TrimSuffix(path, "\x00")
}
if pbl.FileValidationOffset > 0 {
sr.Seek(int64(pbl.FileValidationOffset), io.SeekStart)
if pbl.Header.FileValidationOffset > 0 {
sr.Seek(int64(pbl.Header.FileValidationOffset), io.SeekStart)
var fv fileValidation
if err := binary.Read(sr, binary.LittleEndian, &fv); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader file validation: %v", err)
}
pbl.FileValidation = &fv
}
if pbl.RegionsCount() > 0 {
sr.Seek(int64(pbl.RegionsOffset), io.SeekStart)
sr.Seek(int64(pbl.Header.RegionsOffset), io.SeekStart)
pbl.Regions = make([]Region, pbl.RegionsCount())
if err := binary.Read(sr, binary.LittleEndian, &pbl.Regions); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader regions: %v", err)
}
// for i, r := range pbl.Regions {
// fmt.Printf("Region %d) %s\n", i, r)
// }
}
if pbl.DependentLoaderRefsArrayOffset > 0 {
sr.Seek(int64(pbl.DependentLoaderRefsArrayOffset), io.SeekStart)
depsArray := make([]LoaderRef, pbl.DepCount)
if pbl.Header.DependentLoaderRefsArrayOffset > 0 {
sr.Seek(int64(pbl.Header.DependentLoaderRefsArrayOffset), io.SeekStart)
depsArray := make([]LoaderRef, pbl.Header.DepCount)
if err := binary.Read(sr, binary.LittleEndian, &depsArray); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader dependent loader refs: %v", err)
}
kindsArray := make([]DependentKind, pbl.DepCount)
if pbl.DependentKindArrayOffset > 0 {
sr.Seek(int64(pbl.DependentKindArrayOffset), io.SeekStart)
kindsArray := make([]DependentKind, pbl.Header.DepCount)
if pbl.Header.DependentKindArrayOffset > 0 {
sr.Seek(int64(pbl.Header.DependentKindArrayOffset), io.SeekStart)
if err := binary.Read(sr, binary.LittleEndian, &kindsArray); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader dependent kinds: %v", err)
}
}
for idx, dep := range depsArray {
Expand All @@ -495,47 +507,47 @@ func (f *File) parsePrebuiltLoader(sr *io.SectionReader) (*PrebuiltLoader, error
})
}
}
if pbl.BindTargetRefsCount > 0 {
sr.Seek(int64(pbl.BindTargetRefsOffset), io.SeekStart)
pbl.BindTargets = make([]BindTargetRef, pbl.BindTargetRefsCount)
if pbl.Header.BindTargetRefsCount > 0 {
sr.Seek(int64(pbl.Header.BindTargetRefsOffset), io.SeekStart)
pbl.BindTargets = make([]BindTargetRef, pbl.Header.BindTargetRefsCount)
if err := binary.Read(sr, binary.LittleEndian, &pbl.BindTargets); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader bind target refs: %v", err)
}
}
if pbl.OverrideBindTargetRefsCount > 0 {
sr.Seek(int64(pbl.OverrideBindTargetRefsOffset), io.SeekStart)
pbl.OverrideBindTargets = make([]BindTargetRef, pbl.OverrideBindTargetRefsCount)
if pbl.Header.OverrideBindTargetRefsCount > 0 {
sr.Seek(int64(pbl.Header.OverrideBindTargetRefsOffset), io.SeekStart)
pbl.OverrideBindTargets = make([]BindTargetRef, pbl.Header.OverrideBindTargetRefsCount)
if err := binary.Read(sr, binary.LittleEndian, &pbl.OverrideBindTargets); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader override bind target refs: %v", err)
}
}
if pbl.ObjcBinaryInfoOffset > 0 {
sr.Seek(int64(pbl.ObjcBinaryInfoOffset), io.SeekStart)
if pbl.Header.ObjcBinaryInfoOffset > 0 {
sr.Seek(int64(pbl.Header.ObjcBinaryInfoOffset), io.SeekStart)
var ofi ObjCBinaryInfo
if err := binary.Read(sr, binary.LittleEndian, &ofi); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader objc binary info: %v", err)
}
pbl.ObjcFixupInfo = &ofi
sr.Seek(int64(pbl.ObjcBinaryInfoOffset)+int64(pbl.ObjcFixupInfo.ProtocolFixupsOffset), io.SeekStart)
sr.Seek(int64(pbl.Header.ObjcBinaryInfoOffset)+int64(pbl.ObjcFixupInfo.ProtocolFixupsOffset), io.SeekStart)
pbl.ObjcCanonicalProtocolFixups = make([]bool, pbl.ObjcFixupInfo.ProtocolListCount)
if err := binary.Read(sr, binary.LittleEndian, &pbl.ObjcCanonicalProtocolFixups); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader objc canonical protocol fixups: %v", err)
}
sr.Seek(int64(pbl.ObjcBinaryInfoOffset)+int64(pbl.ObjcFixupInfo.SelectorReferencesFixupsOffset), io.SeekStart)
sr.Seek(int64(pbl.Header.ObjcBinaryInfoOffset)+int64(pbl.ObjcFixupInfo.SelectorReferencesFixupsOffset), io.SeekStart)
pbl.ObjcSelectorFixups = make([]BindTargetRef, pbl.ObjcFixupInfo.SelectorReferencesFixupsCount)
if err := binary.Read(sr, binary.LittleEndian, &pbl.ObjcSelectorFixups); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader objc selector fixups: %v", err)
}
}
if pbl.IndexOfTwin != NoUnzipperedTwin {
pbl.Twin = f.Images[pbl.IndexOfTwin].Name
if pbl.Header.IndexOfTwin != NoUnzipperedTwin {
pbl.Twin = f.Images[pbl.Header.IndexOfTwin].Name
}
if pbl.PatchTableOffset > 0 {
sr.Seek(int64(pbl.PatchTableOffset), io.SeekStart)
if pbl.Header.PatchTableOffset > 0 {
sr.Seek(int64(pbl.Header.PatchTableOffset), io.SeekStart)
for {
var patch DylibPatch
if err := binary.Read(sr, binary.LittleEndian, &patch); err != nil {
return nil, err
return nil, fmt.Errorf("failed to read prebuilt loader dylib patch: %v", err)
}
pbl.DylibPatches = append(pbl.DylibPatches, patch)
if patch.Kind == endOfPatchTable {
Expand All @@ -546,3 +558,5 @@ func (f *File) parsePrebuiltLoader(sr *io.SectionReader) (*PrebuiltLoader, error

return &pbl, nil
}

// 110594112 [6978840h] SIZE = 0x222CA0; PADDING = 0x6B310;
Loading

0 comments on commit 58d77dd

Please sign in to comment.