mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
1991 lines
67 KiB
Go
1991 lines
67 KiB
Go
package dyld
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"maps"
|
|
"math/bits"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/AlecAivazis/survey/v2/terminal"
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/go-macho/pkg/codesign"
|
|
"github.com/blacktop/go-macho/pkg/trie"
|
|
mtypes "github.com/blacktop/go-macho/types"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/blacktop/ipsw/pkg/disass"
|
|
)
|
|
|
|
// Known good magic
|
|
var knownMagic = []string{
|
|
"dyld_v1 i386",
|
|
"dyld_v1 x86_64",
|
|
"dyld_v1 x86_64h",
|
|
"dyld_v1 armv5",
|
|
"dyld_v1 armv6",
|
|
"dyld_v1 armv7",
|
|
"dyld_v1 armv7",
|
|
"dyld_v1 arm64",
|
|
"dyld_v1arm64_32",
|
|
"dyld_v1 arm64e",
|
|
}
|
|
|
|
type localSymbolInfo struct {
|
|
CacheLocalSymbolsInfo
|
|
NListFileOffset uint32
|
|
NListByteSize uint32
|
|
StringsFileOffset uint32
|
|
}
|
|
|
|
type cacheImages []*CacheImage
|
|
type cacheMappings []*CacheMapping
|
|
type codesignature *codesign.CodeSignature
|
|
type cacheMappingsWithSlideInfo []*CacheMappingWithSlideInfo
|
|
|
|
func (m cacheMappingsWithSlideInfo) Len() int {
|
|
return len(m)
|
|
}
|
|
func (m cacheMappingsWithSlideInfo) Swap(i, j int) {
|
|
m[i], m[j] = m[j], m[i]
|
|
}
|
|
func (m cacheMappingsWithSlideInfo) Less(i, j int) bool {
|
|
return m[i].Address < m[j].Address
|
|
}
|
|
|
|
// A File represents an open dyld file.
|
|
type File struct {
|
|
UUID mtypes.UUID
|
|
Headers map[mtypes.UUID]CacheHeader
|
|
|
|
ByteOrder binary.ByteOrder
|
|
|
|
Mappings map[mtypes.UUID]cacheMappings
|
|
MappingsWithSlideInfo map[mtypes.UUID]cacheMappingsWithSlideInfo
|
|
|
|
Images cacheImages
|
|
|
|
SlideInfo slideInfo
|
|
PatchInfoVersion uint32
|
|
LocalSymInfo localSymbolInfo
|
|
AcceleratorInfo CacheAcceleratorInfo
|
|
ImageArray map[uint32]*CImage
|
|
Closures []*LaunchClosure
|
|
|
|
BranchPools []uint64
|
|
CodeSignatures map[mtypes.UUID]codesignature
|
|
|
|
AddressToSymbol *A2STable
|
|
|
|
IsDyld4 bool
|
|
SymCacheLoaded bool
|
|
SubCacheInfo []SubcacheEntry
|
|
TPROMappings []TPROMapping
|
|
symUUID mtypes.UUID
|
|
dyldImageAddr uint64
|
|
dyldStartFnAddr uint64
|
|
objcOptRoAddr uint64
|
|
objcOpt Optimization
|
|
objcOptOnce sync.Once
|
|
objcOptErr error
|
|
rsBaseOnce sync.Once
|
|
rsBase uint64
|
|
rsBaseErr error
|
|
islandStubs map[uint64]uint64
|
|
prewarmData *PrewarmingHeader
|
|
size int64
|
|
|
|
r map[mtypes.UUID]io.ReaderAt
|
|
closers map[mtypes.UUID]io.Closer
|
|
|
|
// sortedImages is Images sorted by LoadAddress for O(log N) binary search
|
|
sortedImages []*CacheImage
|
|
}
|
|
|
|
// FormatError is returned by some operations if the data does
|
|
// not have the correct format for an object file.
|
|
type FormatError struct {
|
|
off int64
|
|
msg string
|
|
val any
|
|
}
|
|
|
|
func (e *FormatError) Error() string {
|
|
msg := e.msg
|
|
if e.val != nil {
|
|
msg += fmt.Sprintf(" '%v'", e.val)
|
|
}
|
|
msg += fmt.Sprintf(" in record at byte %#x", e.off)
|
|
return msg
|
|
}
|
|
|
|
func getUUID(r io.ReaderAt) (mtypes.UUID, error) {
|
|
var uuidBytes [16]byte
|
|
var badUUID mtypes.UUID
|
|
|
|
if _, err := r.ReadAt(uuidBytes[0:], 0x58); err != nil {
|
|
return badUUID, err
|
|
}
|
|
|
|
uuid := mtypes.UUID(uuidBytes)
|
|
|
|
if uuid.IsNull() {
|
|
return badUUID, fmt.Errorf("file's UUID is empty") // FIXME: should this actually stop or continue
|
|
}
|
|
|
|
return uuid, nil
|
|
}
|
|
|
|
// Open opens the named file and prepares it for use as a dyld binary.
|
|
func Open(name string) (*File, error) {
|
|
|
|
log.WithFields(log.Fields{
|
|
"cache": name,
|
|
}).Debug("Parsing Cache")
|
|
|
|
r, closer, size, err := openCacheFile(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ff, err := NewFile(r)
|
|
if err != nil {
|
|
closer.Close()
|
|
return nil, err
|
|
}
|
|
|
|
ff.size = size
|
|
|
|
if ff.IsDyld4 {
|
|
|
|
for i := 1; i <= int(ff.Headers[ff.UUID].SubCacheArrayCount); i++ {
|
|
subCacheName := fmt.Sprintf("%s.%d", name, i)
|
|
if len(ff.SubCacheInfo[i-1].Extention) > 0 {
|
|
subCacheName = fmt.Sprintf("%s%s", name, ff.SubCacheInfo[i-1].Extention)
|
|
}
|
|
/* NOTE: removing because it feels too noisy */
|
|
// log.WithFields(log.Fields{
|
|
// "cache": subCacheName,
|
|
// }).Debug("Parsing SubCache")
|
|
|
|
sr, sc, subSize, err := openCacheFile(subCacheName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ff.size += subSize
|
|
|
|
uuid, err := getUUID(sr)
|
|
if err != nil {
|
|
sc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
ff.parseCache(sr, uuid)
|
|
|
|
ff.closers[uuid] = sc
|
|
|
|
if ff.Headers[uuid].UUID != ff.SubCacheInfo[i-1].UUID {
|
|
return nil, fmt.Errorf("sub cache %s did not match expected UUID: %#x, got: %#x", subCacheName,
|
|
ff.SubCacheInfo[i-1].UUID.String(),
|
|
ff.Headers[uuid].UUID.String())
|
|
}
|
|
}
|
|
|
|
if !ff.Headers[ff.UUID].SymbolFileUUID.IsNull() {
|
|
// log.WithFields(log.Fields{
|
|
// "cache": name + ".symbols",
|
|
// }).Debug("Parsing SubCache")
|
|
sr, sc, _, err := openCacheFile(name + ".symbols")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uuid, err := getUUID(sr)
|
|
if err != nil {
|
|
sc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
if uuid != ff.Headers[ff.UUID].SymbolFileUUID {
|
|
sc.Close()
|
|
return nil, fmt.Errorf("%s.symbols UUID %s did NOT match expected UUID %s", name, uuid.String(), ff.Headers[ff.UUID].SymbolFileUUID.String())
|
|
}
|
|
|
|
ff.symUUID = uuid
|
|
|
|
ff.parseCache(sr, uuid)
|
|
|
|
ff.closers[uuid] = sc
|
|
}
|
|
}
|
|
|
|
ff.closers[ff.UUID] = closer
|
|
|
|
// Build sorted image index for O(log N) text address lookups
|
|
ff.sortedImages = make([]*CacheImage, len(ff.Images))
|
|
copy(ff.sortedImages, ff.Images)
|
|
sort.Slice(ff.sortedImages, func(i, j int) bool {
|
|
return ff.sortedImages[i].LoadAddress < ff.sortedImages[j].LoadAddress
|
|
})
|
|
|
|
return ff, nil
|
|
}
|
|
|
|
// Close closes the File.
|
|
// If the File was created using NewFile directly instead of Open,
|
|
// Close has no effect.
|
|
func (f *File) Close() error {
|
|
if f.AddressToSymbol != nil {
|
|
f.AddressToSymbol.Close()
|
|
}
|
|
var err error
|
|
for uuid, closer := range f.closers {
|
|
if closer != nil {
|
|
err = closer.Close()
|
|
f.closers[uuid] = nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadHeader opens a given cache and returns the dyld_cache_header
|
|
func ReadHeader(name string) (*CacheHeader, error) {
|
|
var header CacheHeader
|
|
|
|
log.WithFields(log.Fields{
|
|
"cache": name,
|
|
}).Debug("Parsing Cache Header")
|
|
|
|
f, err := os.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := binary.Read(f, binary.LittleEndian, &header); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &header, nil
|
|
}
|
|
|
|
// NewFile creates a new File for accessing a dyld binary in an underlying reader.
|
|
// The dyld binary is expected to start at position 0 in the ReaderAt.
|
|
func NewFile(r io.ReaderAt) (*File, error) {
|
|
|
|
f := new(File)
|
|
|
|
// init all maps
|
|
f.Headers = make(map[mtypes.UUID]CacheHeader)
|
|
f.Mappings = make(map[mtypes.UUID]cacheMappings)
|
|
f.MappingsWithSlideInfo = make(map[mtypes.UUID]cacheMappingsWithSlideInfo)
|
|
f.CodeSignatures = make(map[mtypes.UUID]codesignature)
|
|
f.r = make(map[mtypes.UUID]io.ReaderAt)
|
|
f.closers = make(map[mtypes.UUID]io.Closer)
|
|
f.AddressToSymbol = NewA2STable(7000000)
|
|
f.ImageArray = make(map[uint32]*CImage)
|
|
f.islandStubs = make(map[uint64]uint64)
|
|
|
|
// Read and decode dyld magic
|
|
var ident [16]byte
|
|
if _, err := r.ReadAt(ident[0:], 0); err != nil {
|
|
return nil, err
|
|
}
|
|
// Verify magic
|
|
if !utils.StrSliceHas(knownMagic, strings.Trim(string(ident[:16]), "\x00")) {
|
|
return nil, &FormatError{0, "invalid dyld_shared_cache magic", string(ident[:16])}
|
|
}
|
|
|
|
f.ByteOrder = binary.LittleEndian
|
|
|
|
var uuidBytes [16]byte
|
|
if _, err := r.ReadAt(uuidBytes[0:], 0x58); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f.UUID = mtypes.UUID(uuidBytes)
|
|
|
|
if f.UUID.IsNull() {
|
|
return nil, fmt.Errorf("file's UUID is empty") // FIXME: should this actually stop or continue
|
|
}
|
|
|
|
if err := f.parseCache(r, f.UUID); err != nil {
|
|
return nil, fmt.Errorf("failed to parse cache %s: %v", f.UUID, err)
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
// parseCache parses dyld shared cache file
|
|
func (f *File) parseCache(r io.ReaderAt, uuid mtypes.UUID) error {
|
|
|
|
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
|
|
|
// Read and decode dyld magic
|
|
var ident [16]byte
|
|
if _, err := r.ReadAt(ident[0:], 0); err != nil {
|
|
return err
|
|
}
|
|
// Verify magic
|
|
if !utils.StrSliceHas(knownMagic, strings.Trim(string(ident[:16]), "\x00")) {
|
|
return &FormatError{0, "invalid dyld_shared_cache magic", string(ident[:16])}
|
|
}
|
|
|
|
f.r[uuid] = r
|
|
|
|
// Read entire file header.
|
|
var hdr CacheHeader
|
|
if err := binary.Read(sr, f.ByteOrder, &hdr); err != nil {
|
|
return err
|
|
}
|
|
f.Headers[uuid] = hdr
|
|
|
|
if f.Headers[f.UUID].ImagesOffsetOld == 0 && f.Headers[f.UUID].ImagesCountOld == 0 {
|
|
f.IsDyld4 = true // NEW macOS12.5+/iOS15+ dyld4 style caches
|
|
}
|
|
|
|
// Read dyld mappings.
|
|
sr.Seek(int64(f.Headers[uuid].MappingOffset), io.SeekStart)
|
|
|
|
for i := uint32(0); i != f.Headers[uuid].MappingCount; i++ {
|
|
cmInfo := CacheMappingInfo{}
|
|
if err := binary.Read(sr, f.ByteOrder, &cmInfo); err != nil {
|
|
return err
|
|
}
|
|
cm := &CacheMapping{CacheMappingInfo: cmInfo}
|
|
if cmInfo.InitProt.Execute() {
|
|
cm.Name = "__TEXT"
|
|
} else if cmInfo.InitProt.Write() {
|
|
cm.Name = "__DATA"
|
|
} else if cmInfo.InitProt.Read() {
|
|
cm.Name = "__LINKEDIT"
|
|
}
|
|
f.Mappings[uuid] = append(f.Mappings[uuid], cm)
|
|
}
|
|
|
|
/***********************
|
|
* Read dyld slide info
|
|
***********************/
|
|
if f.Headers[uuid].SlideInfoOffsetUnused > 0 {
|
|
for _, m := range f.Mappings[uuid] {
|
|
cm := &CacheMappingWithSlideInfo{CacheMappingAndSlideInfo: CacheMappingAndSlideInfo{
|
|
Address: m.Address,
|
|
Size: m.Size,
|
|
FileOffset: m.FileOffset,
|
|
MaxProt: m.MaxProt,
|
|
InitProt: m.InitProt,
|
|
}, Name: m.Name}
|
|
if m.Name == "__DATA" {
|
|
cm.SlideInfoOffset = f.Headers[uuid].SlideInfoOffsetUnused
|
|
cm.SlideInfoSize = f.Headers[uuid].SlideInfoSizeUnused
|
|
f.GetSlideInfo(uuid, cm)
|
|
}
|
|
f.MappingsWithSlideInfo[uuid] = append(f.MappingsWithSlideInfo[uuid], cm)
|
|
}
|
|
} else {
|
|
// Read NEW (in iOS 14) dyld mappings with slide info.
|
|
sr.Seek(int64(f.Headers[uuid].MappingWithSlideOffset), io.SeekStart)
|
|
for i := uint32(0); i != f.Headers[uuid].MappingWithSlideCount; i++ {
|
|
cxmInfo := CacheMappingAndSlideInfo{}
|
|
if err := binary.Read(sr, f.ByteOrder, &cxmInfo); err != nil {
|
|
return err
|
|
}
|
|
|
|
cm := &CacheMappingWithSlideInfo{CacheMappingAndSlideInfo: cxmInfo, Name: "UNKNOWN"}
|
|
if cxmInfo.MaxProt.Execute() {
|
|
if cm.Flags.IsTextStubs() {
|
|
cm.Name = "__TEXT_STUBS"
|
|
} else {
|
|
cm.Name = "__TEXT"
|
|
}
|
|
} else if cxmInfo.MaxProt.Write() {
|
|
if cm.Flags.IsAuthData() {
|
|
cm.Name = "__AUTH"
|
|
} else if cm.Flags.IsTPRO() {
|
|
cm.Name = "__TPRO"
|
|
} else {
|
|
cm.Name = "__DATA"
|
|
}
|
|
switch {
|
|
case cm.Flags.IsDirtyData():
|
|
cm.Name += "_DIRTY"
|
|
case cm.Flags.IsConstData():
|
|
cm.Name += "_CONST"
|
|
case cm.Flags.IsConfigData():
|
|
cm.Name += "_CONFIG"
|
|
}
|
|
} else if cxmInfo.InitProt.Read() {
|
|
cm.Name = "__LINKEDIT"
|
|
}
|
|
|
|
if cm.SlideInfoSize > 0 {
|
|
f.GetSlideInfo(uuid, cm)
|
|
}
|
|
|
|
f.MappingsWithSlideInfo[uuid] = append(f.MappingsWithSlideInfo[uuid], cm)
|
|
}
|
|
}
|
|
|
|
// Read dyld images.
|
|
var imagesCount uint32
|
|
if f.Headers[uuid].ImagesOffsetOld > 0 {
|
|
imagesCount = f.Headers[uuid].ImagesCountOld
|
|
sr.Seek(int64(f.Headers[uuid].ImagesOffsetOld), io.SeekStart)
|
|
} else {
|
|
imagesCount = f.Headers[uuid].ImagesCount
|
|
sr.Seek(int64(f.Headers[uuid].ImagesOffset), io.SeekStart)
|
|
}
|
|
|
|
if len(f.Images) == 0 {
|
|
for i := uint32(0); i != imagesCount; i++ {
|
|
iinfo := CacheImageInfo{}
|
|
if err := binary.Read(sr, f.ByteOrder, &iinfo); err != nil {
|
|
return fmt.Errorf("failed to read %T: %v", iinfo, err)
|
|
}
|
|
f.Images = append(f.Images, &CacheImage{
|
|
Index: i,
|
|
Info: iinfo,
|
|
cache: f,
|
|
})
|
|
}
|
|
for idx, image := range f.Images {
|
|
sr.Seek(int64(image.Info.PathFileOffset), io.SeekStart)
|
|
r := bufio.NewReader(sr)
|
|
if name, err := r.ReadString(byte(0)); err == nil {
|
|
f.Images[idx].Name = strings.Trim(name, "\x00")
|
|
}
|
|
// if offset, err := f.GetOffset(image.Info.Address); err == nil {
|
|
// f.Images[idx].CacheLocalSymbolsEntry.DylibOffset = offset
|
|
// }
|
|
}
|
|
}
|
|
for idx, img := range f.Images {
|
|
if f.IsAddressInCache(uuid, img.Info.Address) {
|
|
f.Images[idx].cuuid = uuid
|
|
}
|
|
}
|
|
|
|
// Read dyld code signature.
|
|
sr.Seek(int64(f.Headers[uuid].CodeSignatureOffset), io.SeekStart)
|
|
|
|
cs := make([]byte, f.Headers[uuid].CodeSignatureSize)
|
|
if err := binary.Read(sr, f.ByteOrder, &cs); err != nil {
|
|
return err
|
|
}
|
|
|
|
csig, err := codesign.ParseCodeSignature(cs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.CodeSignatures[uuid] = csig
|
|
|
|
// Read dyld local symbol entries.
|
|
if f.Headers[uuid].LocalSymbolsOffset != 0 {
|
|
sr.Seek(int64(f.Headers[uuid].LocalSymbolsOffset), io.SeekStart)
|
|
|
|
if err := binary.Read(sr, f.ByteOrder, &f.LocalSymInfo.CacheLocalSymbolsInfo); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f.Is64bit() {
|
|
f.LocalSymInfo.NListByteSize = f.LocalSymInfo.NlistCount * 16
|
|
} else {
|
|
f.LocalSymInfo.NListByteSize = f.LocalSymInfo.NlistCount * 12
|
|
}
|
|
f.LocalSymInfo.NListFileOffset = uint32(f.Headers[uuid].LocalSymbolsOffset) + f.LocalSymInfo.NlistOffset
|
|
f.LocalSymInfo.StringsFileOffset = uint32(f.Headers[uuid].LocalSymbolsOffset) + f.LocalSymInfo.StringsOffset
|
|
|
|
sr.Seek(int64(f.Headers[uuid].LocalSymbolsOffset+uint64(f.LocalSymInfo.EntriesOffset)), io.SeekStart)
|
|
|
|
for i := range int(f.LocalSymInfo.EntriesCount) {
|
|
// if err := binary.Read(sr, f.ByteOrder, &f.Images[i].CacheLocalSymbolsEntry); err != nil {
|
|
// return nil, err
|
|
// }
|
|
var localSymEntry CacheLocalSymbolsEntry64
|
|
if f.Headers[uuid].ImagesOffsetOld == 0 && f.Headers[uuid].ImagesCountOld == 0 { // NEW iOS15 dyld4 style caches
|
|
if err := binary.Read(sr, f.ByteOrder, &localSymEntry); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
var preDyld4LSEntry CacheLocalSymbolsEntry
|
|
if err := binary.Read(sr, f.ByteOrder, &preDyld4LSEntry); err != nil {
|
|
return err
|
|
}
|
|
localSymEntry.DylibOffset = uint64(preDyld4LSEntry.DylibOffset)
|
|
localSymEntry.NlistStartIndex = preDyld4LSEntry.NlistStartIndex
|
|
localSymEntry.NlistCount = preDyld4LSEntry.NlistCount
|
|
}
|
|
|
|
if len(f.Images) > i {
|
|
f.Images[i].CacheLocalSymbolsEntry64 = localSymEntry
|
|
} else {
|
|
f.Images = append(f.Images, &CacheImage{
|
|
Index: uint32(i),
|
|
// Info: iinfo,
|
|
cache: f,
|
|
CacheLocalSymbolsEntry64: localSymEntry,
|
|
})
|
|
}
|
|
// f.Images[i].ReaderAt = io.NewSectionReader(r, int64(f.Images[i].DylibOffset), 1<<63-1)
|
|
}
|
|
}
|
|
|
|
// Read dyld branch pool.
|
|
if f.Headers[uuid].BranchPoolsOffset != 0 {
|
|
sr.Seek(int64(f.Headers[uuid].BranchPoolsOffset), io.SeekStart)
|
|
|
|
var bPools []uint64
|
|
bpoolBytes := make([]byte, 8)
|
|
for i := uint32(0); i != f.Headers[uuid].BranchPoolsCount; i++ {
|
|
if err := binary.Read(sr, f.ByteOrder, &bpoolBytes); err != nil {
|
|
return err
|
|
}
|
|
bPools = append(bPools, binary.LittleEndian.Uint64(bpoolBytes))
|
|
}
|
|
f.BranchPools = bPools
|
|
}
|
|
|
|
if f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr != 0 {
|
|
if f.IsDyld4 { // iOS16 added dyld to the shared cache and resued these fields to point to the dyld image and _dyld_start
|
|
f.dyldImageAddr = f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr
|
|
f.dyldStartFnAddr = f.Headers[uuid].AccelerateInfoSizeUnusedOrDyldStartFuncAddr
|
|
} else {
|
|
// Read dyld optimization info.
|
|
for _, mapping := range f.Mappings[uuid] {
|
|
if mapping.Address <= f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr && f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr < mapping.Address+mapping.Size {
|
|
accelInfoPtr := int64(f.Headers[uuid].AccelerateInfoAddrUnusedOrDyldAddr - mapping.Address + mapping.FileOffset)
|
|
sr.Seek(accelInfoPtr, io.SeekStart)
|
|
if err := binary.Read(sr, f.ByteOrder, &f.AcceleratorInfo); err != nil {
|
|
return err
|
|
}
|
|
// Read dyld 16-bit array of sorted image indexes.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.BottomUpListOffset), io.SeekStart)
|
|
bottomUpList := make([]uint16, f.AcceleratorInfo.ImageExtrasCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &bottomUpList); err != nil {
|
|
return err
|
|
}
|
|
// Read dyld 16-bit array of dependencies.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.DepListOffset), io.SeekStart)
|
|
depList := make([]uint16, f.AcceleratorInfo.DepListCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &depList); err != nil {
|
|
return err
|
|
}
|
|
// Read dyld 16-bit array of re-exports.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.ReExportListOffset), io.SeekStart)
|
|
reExportList := make([]uint16, f.AcceleratorInfo.ReExportCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &reExportList); err != nil {
|
|
return err
|
|
}
|
|
// Read dyld image info extras.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.ImagesExtrasOffset), io.SeekStart)
|
|
for i := uint32(0); i != f.AcceleratorInfo.ImageExtrasCount; i++ {
|
|
imgXtrInfo := CacheImageInfoExtra{}
|
|
if err := binary.Read(sr, f.ByteOrder, &imgXtrInfo); err != nil {
|
|
return err
|
|
}
|
|
f.Images[i].CacheImageInfoExtra = imgXtrInfo
|
|
}
|
|
// Read dyld initializers list.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.InitializersOffset), io.SeekStart)
|
|
for i := uint32(0); i != f.AcceleratorInfo.InitializersCount; i++ {
|
|
accelInit := CacheAcceleratorInitializer{}
|
|
if err := binary.Read(sr, f.ByteOrder, &accelInit); err != nil {
|
|
return err
|
|
}
|
|
// fmt.Printf(" image[%3d] 0x%X\n", accelInit.ImageIndex, f.Mappings[0].Address+uint64(accelInit.FunctionOffset))
|
|
f.Images[accelInit.ImageIndex].Initializer = f.Mappings[uuid][0].Address + uint64(accelInit.FunctionOffset)
|
|
}
|
|
// Read dyld DOF sections list.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.DofSectionsOffset), io.SeekStart)
|
|
for i := uint32(0); i != f.AcceleratorInfo.DofSectionsCount; i++ {
|
|
accelDOF := CacheAcceleratorDof{}
|
|
if err := binary.Read(sr, f.ByteOrder, &accelDOF); err != nil {
|
|
return err
|
|
}
|
|
// fmt.Printf(" image[%3d] 0x%X -> 0x%X\n", accelDOF.ImageIndex, accelDOF.SectionAddress, accelDOF.SectionAddress+uint64(accelDOF.SectionSize))
|
|
f.Images[accelDOF.ImageIndex].DOFSectionAddr = accelDOF.SectionAddress
|
|
f.Images[accelDOF.ImageIndex].DOFSectionSize = accelDOF.SectionSize
|
|
}
|
|
// Read dyld offset to start of ss.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.RangeTableOffset), io.SeekStart)
|
|
for i := uint32(0); i != f.AcceleratorInfo.RangeTableCount; i++ {
|
|
rEntry := CacheRangeEntry{}
|
|
if err := binary.Read(sr, f.ByteOrder, &rEntry); err != nil {
|
|
return err
|
|
}
|
|
// fmt.Printf(" 0x%X -> 0x%X %s\n", rangeEntry.StartAddress, rangeEntry.StartAddress+uint64(rangeEntry.Size), f.Images[rangeEntry.ImageIndex].Name)
|
|
offset, err := f.GetOffsetForUUID(uuid, rEntry.StartAddress)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get range entry's file offset: %v", err)
|
|
}
|
|
f.Images[rEntry.ImageIndex].RangeEntries = append(f.Images[rEntry.ImageIndex].RangeEntries, rangeEntry{
|
|
StartAddr: rEntry.StartAddress,
|
|
FileOffset: offset,
|
|
Size: rEntry.Size,
|
|
})
|
|
}
|
|
// Read dyld trie containing all dylib paths.
|
|
sr.Seek(accelInfoPtr+int64(f.AcceleratorInfo.DylibTrieOffset), io.SeekStart)
|
|
dylibTrie := make([]byte, f.AcceleratorInfo.DylibTrieSize)
|
|
if err := binary.Read(sr, f.ByteOrder, &dylibTrie); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read dyld text_info entries.
|
|
sr.Seek(int64(f.Headers[uuid].ImagesTextOffset), io.SeekStart)
|
|
for i := uint64(0); i != f.Headers[uuid].ImagesTextCount; i++ {
|
|
if err := binary.Read(sr, f.ByteOrder, &f.Images[i].CacheImageTextInfo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if f.Headers[uuid].SubCacheArrayCount > 0 && f.Headers[uuid].SubCacheArrayOffset > 0 {
|
|
sr.Seek(int64(f.Headers[uuid].SubCacheArrayOffset), io.SeekStart)
|
|
f.SubCacheInfo = make([]SubcacheEntry, f.Headers[uuid].SubCacheArrayCount)
|
|
// TODO: gross hack to read the subcache info for pre iOS 16.
|
|
if f.Headers[f.UUID].MappingOffset >= 0x200 {
|
|
subCacheInfo := make([]subcacheEntry, f.Headers[uuid].SubCacheArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, subCacheInfo); err != nil {
|
|
return err
|
|
}
|
|
for idx, scinfo := range subCacheInfo {
|
|
f.SubCacheInfo[idx].UUID = scinfo.UUID
|
|
f.SubCacheInfo[idx].CacheVMOffset = scinfo.CacheVMOffset
|
|
f.SubCacheInfo[idx].Extention = string(bytes.Trim(scinfo.FileSuffix[:], "\x00"))
|
|
}
|
|
} else {
|
|
subCacheInfo := make([]subcacheEntryV1, f.Headers[uuid].SubCacheArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, subCacheInfo); err != nil {
|
|
return err
|
|
}
|
|
for idx, scinfo := range subCacheInfo {
|
|
f.SubCacheInfo[idx].UUID = scinfo.UUID
|
|
f.SubCacheInfo[idx].CacheVMOffset = scinfo.CacheVMOffset
|
|
}
|
|
}
|
|
}
|
|
|
|
if f.Headers[uuid].MappingOffset >= 0x208 {
|
|
// read TPRO mapping info
|
|
sr.Seek(int64(f.Headers[uuid].TPROMappingsOffset), io.SeekStart)
|
|
f.TPROMappings = make([]TPROMapping, f.Headers[uuid].TPROMappingsCount)
|
|
if err := binary.Read(sr, f.ByteOrder, f.TPROMappings); err != nil {
|
|
return fmt.Errorf("failed to read DSC TPRO mappings (new in iOS18.0): %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Size returns the size of the cache file(s)
|
|
func (f *File) Size() int64 {
|
|
return f.size
|
|
}
|
|
|
|
// GetSubCacheInfo returns the subcache info for the given UUID.
|
|
func (f *File) GetSubCacheInfo(uuid mtypes.UUID) *SubcacheEntry {
|
|
for _, info := range f.SubCacheInfo {
|
|
if info.UUID == uuid {
|
|
return &info
|
|
}
|
|
}
|
|
if f.UUID == uuid {
|
|
return &SubcacheEntry{
|
|
UUID: uuid,
|
|
CacheVMOffset: 0,
|
|
Extention: "primary",
|
|
}
|
|
}
|
|
if f.symUUID == uuid {
|
|
mapping, err := f.GetMappingForOffsetForUUID(f.SubCacheInfo[len(f.SubCacheInfo)-1].UUID, 0)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &SubcacheEntry{
|
|
UUID: uuid,
|
|
CacheVMOffset: f.SubCacheInfo[len(f.SubCacheInfo)-1].CacheVMOffset + mapping.Size,
|
|
Extention: ".symbols",
|
|
}
|
|
}
|
|
log.Errorf("UUID not found in subcache info %s", uuid)
|
|
return nil
|
|
}
|
|
|
|
// GetNextSubCacheInfo returns the subcache info after the given UUID.
|
|
func (f *File) GetNextSubCacheInfo(uuid mtypes.UUID) *SubcacheEntry {
|
|
for idx, info := range f.SubCacheInfo {
|
|
if info.UUID == uuid {
|
|
if idx+1 < len(f.SubCacheInfo) {
|
|
return &f.SubCacheInfo[idx+1]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *File) ParseImageArrays() error {
|
|
// Read dyld image array info
|
|
if f.Headers[f.UUID].DylibsImageArrayAddr > 0 || f.Headers[f.UUID].DylibsPblSetAddr > 0 {
|
|
if err := f.GetDylibsImageArray(); err != nil {
|
|
return fmt.Errorf("failed to parse dylibs image array: %v", err)
|
|
}
|
|
}
|
|
|
|
// Read other image array info
|
|
if f.Headers[f.UUID].OtherImageArrayAddr > 0 {
|
|
if err := f.GetDlopenOtherImageArray(); err != nil {
|
|
return fmt.Errorf("failed to parse other image array: %v", err)
|
|
}
|
|
}
|
|
|
|
// Read program closure image array info
|
|
if f.Headers[f.UUID].ProgClosuresTrieAddr > 0 || f.Headers[f.UUID].ProgramTrieAddr > 0 {
|
|
if err := f.GetProgClosureImageArray(); err != nil {
|
|
return fmt.Errorf("failed to parse program launch closures: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *File) ParseStubIslands() error {
|
|
for _, sc := range f.SubCacheInfo {
|
|
for _, mapping := range f.MappingsWithSlideInfo[sc.UUID] {
|
|
if mapping.Flags.IsTextStubs() {
|
|
// found a stub island
|
|
dat := make([]byte, f.Headers[sc.UUID].CodeSignatureOffset-0x4000)
|
|
if _, err := f.r[sc.UUID].ReadAt(dat, 0x4000); err != nil {
|
|
return fmt.Errorf("ParseStubIslands: failed to read stub island data: %v", err)
|
|
}
|
|
stubs, err := disass.ParseStubsASM(dat, mapping.Address+0x4000, func(u uint64) (uint64, error) {
|
|
return f.ReadPointerAtAddress(u)
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("ParseStubIslands: failed to parse stub island assembly: %v", err)
|
|
}
|
|
maps.Copy(f.islandStubs, stubs)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetStubIslandPointerSlots returns pointer slots keyed by stub-island entry point.
|
|
func (f *File) GetStubIslandPointerSlots() (map[uint64]uint64, error) {
|
|
slots := make(map[uint64]uint64)
|
|
for _, sc := range f.SubCacheInfo {
|
|
for _, mapping := range f.MappingsWithSlideInfo[sc.UUID] {
|
|
if !mapping.Flags.IsTextStubs() {
|
|
continue
|
|
}
|
|
dat := make([]byte, f.Headers[sc.UUID].CodeSignatureOffset-0x4000)
|
|
if _, err := f.r[sc.UUID].ReadAt(dat, 0x4000); err != nil {
|
|
return nil, fmt.Errorf("GetStubIslandPointerSlots: failed to read stub island data: %v", err)
|
|
}
|
|
stubs, err := disass.ParseStubsASM(dat, mapping.Address+0x4000, func(u uint64) (uint64, error) {
|
|
return u, nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetStubIslandPointerSlots: failed to parse stub island assembly: %v", err)
|
|
}
|
|
maps.Copy(slots, stubs)
|
|
}
|
|
}
|
|
return slots, nil
|
|
}
|
|
|
|
func (f *File) ParsePrewarmData() error {
|
|
if f.Headers[f.UUID].MappingOffset <= uint32(unsafe.Offsetof(CacheHeader{}.PrewarmingDataOffset)) {
|
|
return fmt.Errorf("prewarm data not supported in this DSC format")
|
|
}
|
|
// Read prewarm data
|
|
if f.Headers[f.UUID].PrewarmingDataOffset > 0 && f.Headers[f.UUID].PrewarmingDataSize > 0 {
|
|
uuid, off, err := f.GetOffset(f.Headers[f.UUID].SharedRegionStart + f.Headers[f.UUID].PrewarmingDataOffset)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get offset for prewarm data header at addr %#x: %v",
|
|
f.Headers[f.UUID].SharedRegionStart+f.Headers[f.UUID].PrewarmingDataOffset, err)
|
|
}
|
|
sr := io.NewSectionReader(f.r[uuid], int64(off), int64(f.Headers[f.UUID].PrewarmingDataSize))
|
|
var pw PrewarmingHeader
|
|
if err := binary.Read(sr, f.ByteOrder, &pw.Version); err != nil {
|
|
return fmt.Errorf("failed to read prewarming header version: %v", err)
|
|
}
|
|
if pw.Version != 1 {
|
|
return fmt.Errorf("unsupported prewarming data version: %d", pw.Version)
|
|
}
|
|
if err := binary.Read(sr, f.ByteOrder, &pw.Count); err != nil {
|
|
return fmt.Errorf("failed to read prewarming header count: %v", err)
|
|
}
|
|
pw.Entries = make([]PrewarmingEntry, pw.Count)
|
|
if err := binary.Read(sr, f.ByteOrder, &pw.Entries); err != nil {
|
|
return fmt.Errorf("failed to read prewarming entries: %v", err)
|
|
}
|
|
f.prewarmData = &pw
|
|
return nil
|
|
}
|
|
return fmt.Errorf("no prewarm data found in DSC %s", f.UUID)
|
|
}
|
|
|
|
func (f *File) ParseFunctionVariantInfo() error {
|
|
if f.Headers[f.UUID].MappingOffset <= uint32(unsafe.Offsetof(CacheHeader{}.FunctionVariantInfoAddr)) {
|
|
return fmt.Errorf("function variant info not supported in this DSC format")
|
|
}
|
|
// Read function variant info
|
|
if f.Headers[f.UUID].FunctionVariantInfoAddr > 0 && f.Headers[f.UUID].FunctionVariantInfoSize > 0 {
|
|
uuid, off, err := f.GetOffset(f.Headers[f.UUID].FunctionVariantInfoAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get offset for function variant info header at addr %#x: %v",
|
|
f.Headers[f.UUID].SharedRegionStart+f.Headers[f.UUID].FunctionVariantInfoAddr, err)
|
|
}
|
|
sr := io.NewSectionReader(f.r[uuid], int64(off), int64(f.Headers[f.UUID].FunctionVariantInfoSize))
|
|
var fv CacheFunctionVariantInfo
|
|
if err := binary.Read(sr, f.ByteOrder, &fv.Version); err != nil {
|
|
return fmt.Errorf("failed to read function variant info version: %v", err)
|
|
}
|
|
if fv.Version != 1 {
|
|
return fmt.Errorf("unsupported function variant info version: %d", fv.Version)
|
|
}
|
|
if err := binary.Read(sr, f.ByteOrder, &fv.Count); err != nil {
|
|
return fmt.Errorf("failed to read function variant info count: %v", err)
|
|
}
|
|
fv.Entries = make([]CacheFunctionVariantEntry, fv.Count)
|
|
if err := binary.Read(sr, f.ByteOrder, &fv.Entries); err != nil {
|
|
return fmt.Errorf("failed to read function variant entries: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
return fmt.Errorf("no function variant info found in DSC %s", f.UUID)
|
|
}
|
|
|
|
// GetSlideInfo returns just the slideinfo header info
|
|
func (f *File) GetSlideInfo(uuid mtypes.UUID, mapping *CacheMappingWithSlideInfo) error {
|
|
_, err := f.parseSlideInfo(uuid, mapping, false, false, 0, 0)
|
|
return err
|
|
}
|
|
|
|
// DumpSlideInfo dumps dyld slide info for a given mapping
|
|
func (f *File) DumpSlideInfo(uuid mtypes.UUID, mapping *CacheMappingWithSlideInfo) error {
|
|
_, err := f.parseSlideInfo(uuid, mapping, true, true, 0, 0)
|
|
return err
|
|
}
|
|
|
|
// GetRebaseInfoForPages returns an offset to rebase address map for a given page index range
|
|
func (f *File) GetRebaseInfoForPages(uuid mtypes.UUID, mapping *CacheMappingWithSlideInfo, start, end uint64) ([]Rebase, error) {
|
|
return f.parseSlideInfo(uuid, mapping, false, true, start, end)
|
|
}
|
|
|
|
func (f *File) parseSlideInfo(uuid mtypes.UUID, mapping *CacheMappingWithSlideInfo, dump bool, parsePages bool, startPage, endPage uint64) ([]Rebase, error) {
|
|
var symName string
|
|
var rebases []Rebase
|
|
|
|
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset), io.SeekStart)
|
|
|
|
// get version
|
|
slideInfoVersionData := make([]byte, 4)
|
|
sr.Read(slideInfoVersionData)
|
|
slideInfoVersion := binary.LittleEndian.Uint32(slideInfoVersionData)
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset), io.SeekStart)
|
|
|
|
switch slideInfoVersion {
|
|
case 1:
|
|
slideInfo := CacheSlideInfo{}
|
|
if err := binary.Read(sr, f.ByteOrder, &slideInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if f.SlideInfo != nil {
|
|
if f.SlideInfo.GetVersion() != slideInfo.GetVersion() {
|
|
return nil, fmt.Errorf("found mixed slide info versions: %d and %d", f.SlideInfo.GetVersion(), slideInfo.GetVersion())
|
|
}
|
|
}
|
|
|
|
f.SlideInfo = slideInfo
|
|
|
|
if !parsePages {
|
|
return nil, nil
|
|
}
|
|
|
|
output(dump, "slide info version = %d\n", slideInfo.Version)
|
|
output(dump, "toc_count = %d\n", slideInfo.TocCount)
|
|
output(dump, "data page count = %d\n", mapping.Size/4096)
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.EntriesOffset)), io.SeekStart)
|
|
entries := make([]CacheSlideInfoEntry, int(slideInfo.EntriesCount))
|
|
if err := binary.Read(sr, binary.LittleEndian, &entries); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.TocOffset)), io.SeekStart)
|
|
tocs := make([]uint16, int(slideInfo.TocCount))
|
|
if err := binary.Read(sr, binary.LittleEndian, &tocs); err != nil {
|
|
return nil, err
|
|
}
|
|
// FIXME: what should I do for version 1 rebases ?
|
|
for i, toc := range tocs {
|
|
output(dump, "%#08x: [% 5d,% 5d] ", int(mapping.Address)+i*4096, i, tocs[i])
|
|
for j := 0; i < int(slideInfo.EntriesSize); i++ {
|
|
output(dump, "%02x", entries[toc].bits[j])
|
|
}
|
|
output(dump, "\n")
|
|
}
|
|
case 2:
|
|
slideInfo := CacheSlideInfo2{}
|
|
if err := binary.Read(sr, f.ByteOrder, &slideInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if f.SlideInfo != nil {
|
|
if f.SlideInfo.GetVersion() != slideInfo.GetVersion() {
|
|
return nil, fmt.Errorf("found mixed slide info versions: %d and %d", f.SlideInfo.GetVersion(), slideInfo.GetVersion())
|
|
}
|
|
}
|
|
|
|
f.SlideInfo = slideInfo
|
|
|
|
if !parsePages {
|
|
return nil, nil
|
|
}
|
|
|
|
output(dump, "slide info version = %d\n", slideInfo.Version)
|
|
output(dump, "page_size = %d\n", slideInfo.PageSize)
|
|
output(dump, "delta_mask = %#016x\n", slideInfo.DeltaMask)
|
|
output(dump, "value_add = %#x\n", slideInfo.ValueAdd)
|
|
output(dump, "page_starts_count = %d\n", slideInfo.PageStartsCount)
|
|
output(dump, "page_extras_count = %d\n", slideInfo.PageExtrasCount)
|
|
|
|
var targetValue uint64
|
|
var pointer CacheSlidePointer2
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.PageStartsOffset)), io.SeekStart)
|
|
starts := make([]uint16, slideInfo.PageStartsCount)
|
|
if err := binary.Read(sr, binary.LittleEndian, &starts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if endPage == 0 || endPage > uint64(len(starts)-1) {
|
|
endPage = uint64(len(starts) - 1) // set end page to MAX
|
|
}
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.PageExtrasOffset)), io.SeekStart)
|
|
extras := make([]uint16, int(slideInfo.PageExtrasCount))
|
|
if err := binary.Read(sr, binary.LittleEndian, &extras); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, start := range starts[startPage:endPage] {
|
|
i += int(startPage)
|
|
pageAddress := mapping.Address + uint64(uint32(i)*slideInfo.PageSize)
|
|
pageOffset := mapping.FileOffset + uint64(uint32(i)*slideInfo.PageSize)
|
|
rebaseChain := func(pageContent uint64, startOffset uint32) error {
|
|
deltaShift := uint64(bits.TrailingZeros64(slideInfo.DeltaMask) - 2)
|
|
delta := uint32(1)
|
|
for delta != 0 {
|
|
sr.Seek(int64(pageContent+uint64(startOffset)), io.SeekStart)
|
|
if err := binary.Read(sr, binary.LittleEndian, &pointer); err != nil {
|
|
return err
|
|
}
|
|
|
|
delta = uint32(uint64(pointer) & slideInfo.DeltaMask >> deltaShift)
|
|
targetValue = slideInfo.SlidePointer(uint64(pointer))
|
|
|
|
if dump {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = "?"
|
|
} else {
|
|
symName = sym
|
|
}
|
|
fmt.Printf(" [% 5d + %#04x]: %#016x = %#016x, sym: %s\n", i, startOffset, pointer, targetValue, symName)
|
|
} else {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = ""
|
|
} else {
|
|
symName = sym
|
|
}
|
|
rebases = append(rebases, Rebase{
|
|
CacheFileOffset: uint64(startOffset) + pageOffset,
|
|
CacheVMAddress: uint64(startOffset) + pageAddress,
|
|
Target: targetValue,
|
|
Pointer: pointer,
|
|
Symbol: symName,
|
|
})
|
|
}
|
|
startOffset += delta
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE {
|
|
output(dump, "page[% 5d]: no rebasing\n", i)
|
|
} else if start&DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA != 0 {
|
|
output(dump, "page[% 5d]: ", i)
|
|
j := start & 0x3FFF
|
|
done := false
|
|
for !done {
|
|
aStart := extras[j]
|
|
output(dump, "start=%#04x ", aStart&0x3FFF)
|
|
pageStartOffset := (aStart & 0x3FFF) * 4
|
|
rebaseChain(pageOffset, uint32(pageStartOffset))
|
|
done = (extras[j] & DYLD_CACHE_SLIDE_PAGE_ATTR_END) != 0
|
|
j++
|
|
}
|
|
output(dump, "\n")
|
|
} else {
|
|
output(dump, "page[% 5d]: start=%#04X\n", i, starts[i])
|
|
rebaseChain(pageOffset, uint32(start*4))
|
|
}
|
|
}
|
|
case 3:
|
|
slideInfo := CacheSlideInfo3{}
|
|
if err := binary.Read(sr, binary.LittleEndian, &slideInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if f.SlideInfo != nil {
|
|
if f.SlideInfo.GetVersion() != slideInfo.GetVersion() {
|
|
return nil, fmt.Errorf("found mixed slide info versions: %d and %d", f.SlideInfo.GetVersion(), slideInfo.GetVersion())
|
|
}
|
|
}
|
|
|
|
f.SlideInfo = slideInfo
|
|
|
|
if !parsePages {
|
|
return nil, nil
|
|
}
|
|
|
|
output(dump, "slide info version = %d\n", slideInfo.Version)
|
|
output(dump, "page_size = %d\n", slideInfo.PageSize)
|
|
output(dump, "page_starts_count = %d\n", slideInfo.PageStartsCount)
|
|
output(dump, "auth_value_add = %#x\n", slideInfo.AuthValueAdd)
|
|
|
|
var targetValue uint64
|
|
var pointer CacheSlidePointer3
|
|
|
|
starts := make([]uint16, slideInfo.PageStartsCount)
|
|
if err := binary.Read(sr, binary.LittleEndian, &starts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if endPage == 0 || endPage > uint64(len(starts)-1) {
|
|
endPage = uint64(len(starts) - 1) // set end page to MAX
|
|
}
|
|
|
|
for i, start := range starts[startPage:endPage] {
|
|
i += int(startPage)
|
|
pageAddress := mapping.Address + uint64(uint32(i)*slideInfo.PageSize)
|
|
pageOffset := mapping.FileOffset + uint64(uint32(i)*slideInfo.PageSize)
|
|
|
|
delta := uint64(start)
|
|
|
|
if delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE {
|
|
output(dump, "page[% 5d]: no rebasing\n", i)
|
|
continue
|
|
}
|
|
|
|
output(dump, "page[% 5d]: start=0x%04X\n", i, delta)
|
|
|
|
rebaseLocation := pageOffset
|
|
rebaseAddr := pageAddress
|
|
|
|
for {
|
|
rebaseLocation += delta
|
|
rebaseAddr += delta
|
|
|
|
sr.Seek(int64(rebaseLocation), io.SeekStart)
|
|
if err := binary.Read(sr, binary.LittleEndian, &pointer); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pointer.Authenticated() {
|
|
targetValue = slideInfo.AuthValueAdd + pointer.OffsetFromSharedCacheBase()
|
|
} else {
|
|
targetValue = pointer.SignExtend51()
|
|
}
|
|
|
|
if dump {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = "?"
|
|
} else {
|
|
symName = sym
|
|
}
|
|
fmt.Printf(" [% 5d + 0x%05X] (off: %#x @ vaddr: %#x; raw: %#x => target: %#x) %s, sym: %s\n", i, (uint64)(rebaseLocation-pageOffset), rebaseLocation, rebaseAddr, pointer.Raw(), targetValue, pointer, symName)
|
|
} else {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = ""
|
|
} else {
|
|
symName = sym
|
|
}
|
|
rebases = append(rebases, Rebase{
|
|
CacheFileOffset: rebaseLocation,
|
|
CacheVMAddress: rebaseAddr,
|
|
Target: targetValue,
|
|
Pointer: pointer,
|
|
Symbol: symName,
|
|
})
|
|
}
|
|
|
|
if pointer.OffsetToNextPointer() == 0 {
|
|
break
|
|
}
|
|
|
|
delta = pointer.OffsetToNextPointer() * 8
|
|
}
|
|
}
|
|
case 4:
|
|
slideInfo := CacheSlideInfo4{}
|
|
if err := binary.Read(sr, f.ByteOrder, &slideInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if f.SlideInfo != nil {
|
|
if f.SlideInfo.GetVersion() != slideInfo.GetVersion() {
|
|
return nil, fmt.Errorf("found mixed slide info versions: %d and %d", f.SlideInfo.GetVersion(), slideInfo.GetVersion())
|
|
}
|
|
}
|
|
|
|
f.SlideInfo = slideInfo
|
|
|
|
if !parsePages {
|
|
return nil, nil
|
|
}
|
|
|
|
output(dump, "slide info version = %d\n", slideInfo.Version)
|
|
output(dump, "page_size = %d\n", slideInfo.PageSize)
|
|
output(dump, "delta_mask = %#016x\n", slideInfo.DeltaMask)
|
|
output(dump, "value_add = %#016x\n", slideInfo.ValueAdd)
|
|
output(dump, "page_starts_count = %d\n", slideInfo.PageStartsCount)
|
|
output(dump, "page_extras_count = %d\n", slideInfo.PageExtrasCount)
|
|
|
|
var targetValue uint64
|
|
var pointer CacheSlidePointer4 // uint32
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.PageStartsOffset)), io.SeekStart)
|
|
starts := make([]uint16, slideInfo.PageStartsCount)
|
|
if err := binary.Read(sr, binary.LittleEndian, &starts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if endPage == 0 { // set end page to MAX
|
|
endPage = uint64(len(starts) - 1)
|
|
}
|
|
|
|
sr.Seek(int64(mapping.SlideInfoOffset+uint64(slideInfo.PageExtrasOffset)), io.SeekStart)
|
|
extras := make([]uint16, int(slideInfo.PageExtrasCount))
|
|
if err := binary.Read(sr, binary.LittleEndian, &extras); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, start := range starts[startPage:endPage] {
|
|
i += int(startPage)
|
|
pageAddress := mapping.Address + uint64(uint32(i)*slideInfo.PageSize)
|
|
pageOffset := mapping.FileOffset + uint64(uint32(i)*slideInfo.PageSize)
|
|
rebaseChainV4 := func(pageContent uint64, startOffset uint16) error {
|
|
deltaShift := uint64(bits.TrailingZeros64(slideInfo.DeltaMask) - 2)
|
|
pageOffset := uint32(startOffset)
|
|
delta := uint32(1)
|
|
for delta != 0 {
|
|
sr.Seek(int64(pageContent+uint64(pageOffset)), io.SeekStart)
|
|
if err := binary.Read(sr, binary.LittleEndian, &pointer); err != nil {
|
|
return err
|
|
}
|
|
|
|
delta = uint32(uint64(pointer) & slideInfo.DeltaMask >> deltaShift)
|
|
targetValue = slideInfo.SlidePointer(uint64(pointer))
|
|
|
|
if dump {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = "?"
|
|
} else {
|
|
symName = sym
|
|
}
|
|
fmt.Printf(" [% 5d + %#04x]: %#08x = %#08x, sym: %s\n", i, pageOffset, pointer, targetValue, symName)
|
|
} else {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = ""
|
|
} else {
|
|
symName = sym
|
|
}
|
|
rebases = append(rebases, Rebase{
|
|
CacheFileOffset: pageContent + uint64(pageOffset),
|
|
CacheVMAddress: pageContent + uint64(pageAddress),
|
|
Target: targetValue,
|
|
Pointer: pointer,
|
|
Symbol: symName,
|
|
})
|
|
}
|
|
pageOffset += delta
|
|
}
|
|
|
|
return nil
|
|
}
|
|
if start == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE {
|
|
output(dump, "page[% 5d]: no rebasing\n", i)
|
|
} else if start&DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA != 0 {
|
|
output(dump, "page[% 5d]: ", i)
|
|
j := (start & DYLD_CACHE_SLIDE4_PAGE_INDEX)
|
|
done := false
|
|
for !done {
|
|
aStart := extras[j]
|
|
output(dump, "start=0x%04X ", aStart&DYLD_CACHE_SLIDE4_PAGE_INDEX)
|
|
pageStartOffset := (aStart & DYLD_CACHE_SLIDE4_PAGE_INDEX) * 4
|
|
rebaseChainV4(pageOffset, pageStartOffset)
|
|
done = (extras[j] & DYLD_CACHE_SLIDE4_PAGE_EXTRA_END) != 0
|
|
j++
|
|
}
|
|
output(dump, "\n")
|
|
} else {
|
|
output(dump, "page[% 5d]: start=0x%04X\n", i, starts[i])
|
|
rebaseChainV4(pageOffset, start*4)
|
|
}
|
|
}
|
|
case 5:
|
|
slideInfo := CacheSlideInfo5{}
|
|
if err := binary.Read(sr, binary.LittleEndian, &slideInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f.SlideInfo = slideInfo
|
|
|
|
if !parsePages {
|
|
return nil, nil
|
|
}
|
|
|
|
output(dump, "slide info version = %d\n", slideInfo.Version)
|
|
output(dump, "page_size = %#x\n", slideInfo.PageSize)
|
|
output(dump, "page_starts_count = %d\n", slideInfo.PageStartsCount)
|
|
output(dump, "auth_value_add = %#x\n", slideInfo.ValueAdd)
|
|
|
|
var targetValue uint64
|
|
var pointer CacheSlidePointer5
|
|
|
|
starts := make([]uint16, slideInfo.PageStartsCount)
|
|
if err := binary.Read(sr, binary.LittleEndian, &starts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if endPage == 0 || endPage > uint64(len(starts)-1) {
|
|
endPage = uint64(len(starts) - 1) // set end page to MAX
|
|
}
|
|
|
|
for i, start := range starts[startPage:endPage] {
|
|
i += int(startPage)
|
|
pageAddress := mapping.Address + uint64(uint32(i)*slideInfo.PageSize)
|
|
pageOffset := mapping.FileOffset + uint64(uint32(i)*slideInfo.PageSize)
|
|
|
|
delta := uint64(start)
|
|
|
|
if delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE {
|
|
output(dump, "page[% 5d]: no rebasing\n", i)
|
|
continue
|
|
}
|
|
|
|
output(dump, "page[% 5d]: start=0x%04X\n", i, delta)
|
|
|
|
rebaseLocation := pageOffset
|
|
rebaseAddr := pageAddress
|
|
|
|
for {
|
|
rebaseLocation += delta
|
|
rebaseAddr += delta
|
|
|
|
sr.Seek(int64(rebaseLocation), io.SeekStart)
|
|
if err := binary.Read(sr, binary.LittleEndian, &pointer); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pointer.Authenticated() {
|
|
targetValue = slideInfo.ValueAdd + pointer.Value()
|
|
} else {
|
|
targetValue = slideInfo.ValueAdd + pointer.SignExtend51()
|
|
}
|
|
|
|
if dump {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = "?"
|
|
} else {
|
|
symName = sym
|
|
}
|
|
fmt.Printf(" [% 5d + 0x%05X] (off: %#x @ vaddr: %#x; raw: %#x => target: %#x) %s, sym: %s\n", i, (uint64)(rebaseLocation-pageOffset), rebaseLocation, rebaseAddr, pointer.Raw(), targetValue, pointer, symName)
|
|
} else {
|
|
sym, ok := f.AddressToSymbol.Get(targetValue)
|
|
if !ok {
|
|
symName = ""
|
|
} else {
|
|
symName = sym
|
|
}
|
|
rebases = append(rebases, Rebase{
|
|
CacheFileOffset: rebaseLocation,
|
|
CacheVMAddress: rebaseAddr,
|
|
Target: targetValue,
|
|
Pointer: pointer,
|
|
Symbol: symName,
|
|
})
|
|
}
|
|
|
|
if pointer.OffsetToNextPointer() == 0 {
|
|
break
|
|
}
|
|
|
|
delta = pointer.OffsetToNextPointer() * 8
|
|
}
|
|
}
|
|
default:
|
|
log.Errorf("got unexpected dyld slide info version: %d", slideInfoVersion)
|
|
}
|
|
|
|
return rebases, nil
|
|
}
|
|
|
|
func (f *File) patchInfoVersion() (uint32, error) {
|
|
if f.IsDyld4 {
|
|
uuid, patchInfoOffset, err := f.GetOffset(f.Headers[f.UUID].PatchInfoAddr)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get patch info v2 offset: %v", err)
|
|
}
|
|
|
|
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchInfoOffset), io.SeekStart)
|
|
|
|
var pinfo CachePatchInfoV2
|
|
if err := binary.Read(sr, f.ByteOrder, &pinfo); err != nil {
|
|
return 0, fmt.Errorf("failed to read patch info v2: %v", err)
|
|
}
|
|
|
|
return pinfo.TableVersion, nil
|
|
}
|
|
|
|
return 1, nil
|
|
}
|
|
|
|
// ParsePatchInfo parses dyld patch info
|
|
func (f *File) ParsePatchInfo() error {
|
|
var err error
|
|
if f.Headers[f.UUID].PatchInfoAddr > 0 {
|
|
f.PatchInfoVersion, err = f.patchInfoVersion()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch info version: %v", err)
|
|
}
|
|
switch f.PatchInfoVersion {
|
|
case 1:
|
|
if err := f.parsePatchInfoV1(); err != nil {
|
|
return fmt.Errorf("failed to parse patch info v1: %v", err)
|
|
}
|
|
case 2, 3:
|
|
if err := f.parsePatchInfoV2(); err != nil {
|
|
return fmt.Errorf("failed to parse patch info v%d: %v", f.PatchInfoVersion, err)
|
|
}
|
|
case 4:
|
|
if err := f.parsePatchInfoV4(); err != nil {
|
|
return fmt.Errorf("failed to parse patch info v%d: %v", f.PatchInfoVersion, err)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported patch info version: %d", f.PatchInfoVersion)
|
|
}
|
|
return nil
|
|
}
|
|
return fmt.Errorf("cache does NOT contain patch info")
|
|
}
|
|
|
|
func (f *File) parsePatchInfoV1() error {
|
|
// Read dyld patch_info entries.
|
|
uuid, patchInfoOffset, err := f.GetOffset(f.Headers[f.UUID].PatchInfoAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch info v1 offset: %v", err)
|
|
}
|
|
|
|
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
|
|
sr.Seek(int64(patchInfoOffset), io.SeekStart)
|
|
|
|
var patchInfo CachePatchInfoV1
|
|
if err := binary.Read(sr, f.ByteOrder, &patchInfo); err != nil {
|
|
return fmt.Errorf("failed to read patch info v1: %v", err)
|
|
}
|
|
|
|
// Read all the other patch_info structs
|
|
uuid, patchTableArrayOffset, err := f.GetOffset(patchInfo.PatchTableArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch table v1 array offset: %v", err)
|
|
}
|
|
|
|
sr.Seek(int64(patchTableArrayOffset), io.SeekStart)
|
|
|
|
imagePatches := make([]CacheImagePatchesV1, patchInfo.PatchTableArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &imagePatches); err != nil {
|
|
return fmt.Errorf("failed to read patch table v1 array: %v", err)
|
|
}
|
|
|
|
uuid, patchExportNamesOffset, err := f.GetOffset(patchInfo.PatchExportNamesAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch export v1 names offset: %v", err)
|
|
}
|
|
|
|
exportNames := io.NewSectionReader(f.r[uuid], int64(patchExportNamesOffset), int64(patchInfo.PatchExportNamesSize))
|
|
|
|
uuid, patchExportArrayOffset, err := f.GetOffset(patchInfo.PatchExportArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch export v1 array offset: %v", err)
|
|
}
|
|
|
|
sr.Seek(int64(patchExportArrayOffset), io.SeekStart)
|
|
|
|
patchExports := make([]CachePatchableExportV1, patchInfo.PatchExportArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchExports); err != nil {
|
|
return fmt.Errorf("failed to read patch export v1 array: %v", err)
|
|
}
|
|
|
|
uuid, patchLocationArrayOffset, err := f.GetOffset(patchInfo.PatchLocationArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch location v1 array offset: %v", err)
|
|
}
|
|
|
|
sr.Seek(int64(patchLocationArrayOffset), io.SeekStart)
|
|
patchableLocations := make([]CachePatchableLocationV1, patchInfo.PatchLocationArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchableLocations); err != nil {
|
|
return fmt.Errorf("failed to read patch location v1 array: %v", err)
|
|
}
|
|
|
|
// Add patchabled export info to images
|
|
for i, iPatch := range imagePatches {
|
|
if iPatch.PatchExportsCount > 0 {
|
|
for exportIndex := uint32(0); exportIndex != iPatch.PatchExportsCount; exportIndex++ {
|
|
patchExport := patchExports[iPatch.PatchExportsStartIndex+exportIndex]
|
|
var exportName string
|
|
if uint64(patchExport.ExportNameOffset) < patchInfo.PatchExportNamesSize {
|
|
exportNames.Seek(int64(patchExport.ExportNameOffset), io.SeekStart)
|
|
s, err := bufio.NewReader(exportNames).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read patch info v1 export string at %x: %v", uint32(patchExportNamesOffset)+patchExport.ExportNameOffset, err)
|
|
}
|
|
exportName = strings.Trim(s, "\x00")
|
|
} else {
|
|
exportName = ""
|
|
}
|
|
plocs := make([]CachePatchableLocationV1, patchExport.PatchLocationsCount)
|
|
for locationIndex := uint32(0); locationIndex != patchExport.PatchLocationsCount; locationIndex++ {
|
|
plocs[locationIndex] = patchableLocations[patchExport.PatchLocationsStartIndex+locationIndex]
|
|
}
|
|
f.Images[i].PatchableExports = append(f.Images[i].PatchableExports, PatchableExport{
|
|
Name: exportName,
|
|
OffsetOfImpl: patchExport.CacheOffsetOfImpl,
|
|
PatchLocations: plocs,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *File) parsePatchInfoV2() error {
|
|
|
|
uuid, patchInfoOffset, err := f.GetOffset(f.Headers[f.UUID].PatchInfoAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch info v2 offset: %v", err)
|
|
}
|
|
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchInfoOffset), io.SeekStart)
|
|
var patchInfo CachePatchInfoV3
|
|
if err := binary.Read(sr, f.ByteOrder, &patchInfo); err != nil {
|
|
return fmt.Errorf("failed to read patch info v2: %v", err)
|
|
}
|
|
// patchArray
|
|
uuid, patchTableArrayOffset, err := f.GetOffset(patchInfo.TableArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch table v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchTableArrayOffset), io.SeekStart)
|
|
patchArray := make([]CacheImagePatchesV2, patchInfo.TableArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchArray); err != nil {
|
|
return fmt.Errorf("failed to read patch table v2 array: %v", err)
|
|
}
|
|
// imageExports
|
|
uuid, patchImageExportsOffset, err := f.GetOffset(patchInfo.ImageExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch image exports v2 offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchImageExportsOffset), io.SeekStart)
|
|
imageExports := make([]CacheImageExportV2, patchInfo.ImageExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &imageExports); err != nil {
|
|
return fmt.Errorf("failed to read patch image exports v2 array: %v", err)
|
|
}
|
|
// clientArray
|
|
uuid, patchClientsArrayOffset, err := f.GetOffset(patchInfo.ClientsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch clients v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchClientsArrayOffset), io.SeekStart)
|
|
clientArray := make([]CacheImageClientsV2, patchInfo.ClientsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &clientArray); err != nil {
|
|
return fmt.Errorf("failed to read patch clients v2 array: %v", err)
|
|
}
|
|
// clientExports
|
|
uuid, patchClientExportsArrayOffset, err := f.GetOffset(patchInfo.ClientExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch client exports v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchClientExportsArrayOffset), io.SeekStart)
|
|
clientExports := make([]CachePatchableExportV2, patchInfo.ClientExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &clientExports); err != nil {
|
|
return fmt.Errorf("failed to read patch client exports v2 array: %v", err)
|
|
}
|
|
// patchLocations
|
|
uuid, patchLocationArrayOffset, err := f.GetOffset(patchInfo.LocationArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch location v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchLocationArrayOffset), io.SeekStart)
|
|
patchLocations := make([]CachePatchableLocationV2, patchInfo.LocationArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchLocations); err != nil {
|
|
return fmt.Errorf("failed to read patch location v2 array: %v", err)
|
|
}
|
|
// exportNames
|
|
uuid, patchExportNamesOffset, err := f.GetOffset(patchInfo.ExportNamesAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch export v2 names offset: %v", err)
|
|
}
|
|
exportNames := io.NewSectionReader(f.r[uuid], int64(patchExportNamesOffset), int64(patchInfo.ExportNamesSize))
|
|
|
|
// Add patchabled export info to images
|
|
for i, patch := range patchArray {
|
|
for clientIndex := uint32(0); clientIndex < patch.ClientsCount; clientIndex++ {
|
|
client := clientArray[patch.ClientsStartIndex+clientIndex]
|
|
for exportIndex := uint32(0); exportIndex != client.PatchExportsCount; exportIndex++ {
|
|
clientExport := clientExports[client.PatchExportsStartIndex+exportIndex]
|
|
imageExport := imageExports[clientExport.ImageExportIndex]
|
|
var exportName string
|
|
if uint64(imageExport.GetExportNameOffset()) < patchInfo.ExportNamesSize {
|
|
exportNames.Seek(int64(imageExport.GetExportNameOffset()), io.SeekStart)
|
|
s, err := bufio.NewReader(exportNames).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read patch info v1 export string at %x: %v", uint32(patchExportNamesOffset)+imageExport.ExportNameOffset, err)
|
|
}
|
|
exportName = strings.Trim(s, "\x00")
|
|
} else {
|
|
exportName = ""
|
|
}
|
|
plocs := make([]CachePatchableLocationV2, clientExport.PatchLocationsCount)
|
|
for locationIndex := uint32(0); locationIndex != clientExport.PatchLocationsCount; locationIndex++ {
|
|
plocs[locationIndex] = patchLocations[clientExport.PatchLocationsStartIndex+locationIndex]
|
|
}
|
|
f.Images[i].PatchableExports = append(f.Images[i].PatchableExports, PatchableExport{
|
|
Name: exportName,
|
|
Kind: imageExport.GetPatchKind().String(),
|
|
ClientIndex: client.ClientDylibIndex,
|
|
OffsetOfImpl: imageExport.DylibOffsetOfImpl,
|
|
PatchLocationsV2: plocs,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if f.PatchInfoVersion == 3 {
|
|
// GOT clients
|
|
uuid, patchGOTClientsArrayOffset, err := f.GetOffset(patchInfo.GotClientsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT clients v3 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTClientsArrayOffset), io.SeekStart)
|
|
gotClients := make([]CacheImageGotClientsV3, patchInfo.GotClientsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotClients); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT clients v3 array: %v", err)
|
|
}
|
|
// GOT client exports
|
|
uuid, patchGOTClientExportsArrayOffset, err := f.GetOffset(patchInfo.GotClientExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT client exports v3 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTClientExportsArrayOffset), io.SeekStart)
|
|
gotClientExports := make([]CachePatchableExportV3, patchInfo.GotClientExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotClientExports); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT client exports v3 array: %v", err)
|
|
}
|
|
// GOT patch locations
|
|
uuid, patchGOTLocationArrayOffset, err := f.GetOffset(patchInfo.GotLocationArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT location v3 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTLocationArrayOffset), io.SeekStart)
|
|
gotPatchLocations := make([]CachePatchableLocationV3, patchInfo.GotLocationArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotPatchLocations); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT location v3 array: %v", err)
|
|
}
|
|
|
|
for idx, gcli := range gotClients {
|
|
for exportIndex := uint32(0); exportIndex < gcli.PatchExportsCount; exportIndex++ {
|
|
gcliexp := gotClientExports[gcli.PatchExportsStartIndex+exportIndex]
|
|
imageExport := imageExports[gcliexp.ImageExportIndex]
|
|
var exportName string
|
|
if uint64(imageExport.GetExportNameOffset()) < patchInfo.ExportNamesSize {
|
|
exportNames.Seek(int64(imageExport.GetExportNameOffset()), io.SeekStart)
|
|
s, err := bufio.NewReader(exportNames).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read patch info v1 export string at %x: %v", uint32(patchExportNamesOffset)+imageExport.ExportNameOffset, err)
|
|
}
|
|
exportName = strings.Trim(s, "\x00")
|
|
} else {
|
|
exportName = ""
|
|
}
|
|
gots := make([]CachePatchableLocationV3, gcliexp.PatchLocationsCount)
|
|
for locIndex := uint32(0); locIndex != gcliexp.PatchLocationsCount; locIndex++ {
|
|
gots[locIndex] = gotPatchLocations[gcliexp.PatchLocationsStartIndex+locIndex]
|
|
}
|
|
f.Images[idx].PatchableGOTs = append(f.Images[idx].PatchableGOTs, PatchableGotExport{
|
|
Name: exportName,
|
|
Kind: imageExport.GetPatchKind().String(),
|
|
OffsetOfImpl: imageExport.DylibOffsetOfImpl,
|
|
GotLocationsV3: gots,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *File) parsePatchInfoV4() error {
|
|
|
|
uuid, patchInfoOffset, err := f.GetOffset(f.Headers[f.UUID].PatchInfoAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch info v4 offset: %v", err)
|
|
}
|
|
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchInfoOffset), io.SeekStart)
|
|
var patchInfo CachePatchInfoV3
|
|
if err := binary.Read(sr, f.ByteOrder, &patchInfo); err != nil {
|
|
return fmt.Errorf("failed to read patch info v3: %v", err)
|
|
}
|
|
// patchArray
|
|
uuid, patchTableArrayOffset, err := f.GetOffset(patchInfo.TableArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch table v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchTableArrayOffset), io.SeekStart)
|
|
patchArray := make([]CacheImagePatchesV2, patchInfo.TableArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchArray); err != nil {
|
|
return fmt.Errorf("failed to read patch table v2 array: %v", err)
|
|
}
|
|
// imageExports
|
|
uuid, patchImageExportsOffset, err := f.GetOffset(patchInfo.ImageExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch image exports v2 offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchImageExportsOffset), io.SeekStart)
|
|
imageExports := make([]CacheImageExportV2, patchInfo.ImageExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &imageExports); err != nil {
|
|
return fmt.Errorf("failed to read patch image exports v2 array: %v", err)
|
|
}
|
|
// clientArray
|
|
uuid, patchClientsArrayOffset, err := f.GetOffset(patchInfo.ClientsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch clients v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchClientsArrayOffset), io.SeekStart)
|
|
clientArray := make([]CacheImageClientsV2, patchInfo.ClientsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &clientArray); err != nil {
|
|
return fmt.Errorf("failed to read patch clients v2 array: %v", err)
|
|
}
|
|
// clientExports
|
|
uuid, patchClientExportsArrayOffset, err := f.GetOffset(patchInfo.ClientExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch client exports v2 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchClientExportsArrayOffset), io.SeekStart)
|
|
clientExports := make([]CachePatchableExportV2, patchInfo.ClientExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &clientExports); err != nil {
|
|
return fmt.Errorf("failed to read patch client exports v2 array: %v", err)
|
|
}
|
|
// patchLocations
|
|
uuid, patchLocationArrayOffset, err := f.GetOffset(patchInfo.LocationArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch location v4 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchLocationArrayOffset), io.SeekStart)
|
|
patchLocations := make([]CachePatchableLocationV4, patchInfo.LocationArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &patchLocations); err != nil {
|
|
return fmt.Errorf("failed to read patch location v4 array: %v", err)
|
|
}
|
|
// exportNames
|
|
uuid, patchExportNamesOffset, err := f.GetOffset(patchInfo.ExportNamesAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch export names offset: %v", err)
|
|
}
|
|
exportNames := io.NewSectionReader(f.r[uuid], int64(patchExportNamesOffset), int64(patchInfo.ExportNamesSize))
|
|
|
|
// Add patchabled export info to images
|
|
for i, patch := range patchArray {
|
|
for clientIndex := uint32(0); clientIndex < patch.ClientsCount; clientIndex++ {
|
|
client := clientArray[patch.ClientsStartIndex+clientIndex]
|
|
for exportIndex := uint32(0); exportIndex != client.PatchExportsCount; exportIndex++ {
|
|
clientExport := clientExports[client.PatchExportsStartIndex+exportIndex]
|
|
imageExport := imageExports[clientExport.ImageExportIndex]
|
|
var exportName string
|
|
if uint64(imageExport.GetExportNameOffset()) < patchInfo.ExportNamesSize {
|
|
exportNames.Seek(int64(imageExport.GetExportNameOffset()), io.SeekStart)
|
|
s, err := bufio.NewReader(exportNames).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read patch info export string at %x: %v", uint32(patchExportNamesOffset)+imageExport.ExportNameOffset, err)
|
|
}
|
|
exportName = strings.Trim(s, "\x00")
|
|
} else {
|
|
exportName = ""
|
|
}
|
|
plocs := make([]CachePatchableLocationV4, clientExport.PatchLocationsCount)
|
|
for locationIndex := uint32(0); locationIndex != clientExport.PatchLocationsCount; locationIndex++ {
|
|
plocs[locationIndex] = patchLocations[clientExport.PatchLocationsStartIndex+locationIndex]
|
|
}
|
|
f.Images[i].PatchableExports = append(f.Images[i].PatchableExports, PatchableExport{
|
|
Name: exportName,
|
|
Kind: imageExport.GetPatchKind().String(),
|
|
ClientIndex: client.ClientDylibIndex,
|
|
OffsetOfImpl: imageExport.DylibOffsetOfImpl,
|
|
PatchLocationsV4: plocs,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// GOT clients
|
|
uuid, patchGOTClientsArrayOffset, err := f.GetOffset(patchInfo.GotClientsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT clients v3 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTClientsArrayOffset), io.SeekStart)
|
|
gotClients := make([]CacheImageGotClientsV3, patchInfo.GotClientsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotClients); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT clients v3 array: %v", err)
|
|
}
|
|
// GOT client exports
|
|
uuid, patchGOTClientExportsArrayOffset, err := f.GetOffset(patchInfo.GotClientExportsArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT client exports v3 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTClientExportsArrayOffset), io.SeekStart)
|
|
gotClientExports := make([]CachePatchableExportV3, patchInfo.GotClientExportsArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotClientExports); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT client exports v3 array: %v", err)
|
|
}
|
|
// GOT patch locations
|
|
uuid, patchGOTLocationArrayOffset, err := f.GetOffset(patchInfo.GotLocationArrayAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get patch GOT location v4 array offset: %v", err)
|
|
}
|
|
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
|
sr.Seek(int64(patchGOTLocationArrayOffset), io.SeekStart)
|
|
gotPatchLocations := make([]CachePatchableLocationV4Got, patchInfo.GotLocationArrayCount)
|
|
if err := binary.Read(sr, f.ByteOrder, &gotPatchLocations); err != nil {
|
|
return fmt.Errorf("failed to read patch GOT location v4 array: %v", err)
|
|
}
|
|
|
|
for idx, gcli := range gotClients {
|
|
for exportIndex := uint32(0); exportIndex < gcli.PatchExportsCount; exportIndex++ {
|
|
gcliexp := gotClientExports[gcli.PatchExportsStartIndex+exportIndex]
|
|
imageExport := imageExports[gcliexp.ImageExportIndex]
|
|
var exportName string
|
|
if uint64(imageExport.GetExportNameOffset()) < patchInfo.ExportNamesSize {
|
|
exportNames.Seek(int64(imageExport.GetExportNameOffset()), io.SeekStart)
|
|
s, err := bufio.NewReader(exportNames).ReadString('\x00')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read patch info GOT export string at %x: %v", uint32(patchExportNamesOffset)+imageExport.ExportNameOffset, err)
|
|
}
|
|
exportName = strings.Trim(s, "\x00")
|
|
} else {
|
|
exportName = ""
|
|
}
|
|
gots := make([]CachePatchableLocationV4Got, gcliexp.PatchLocationsCount)
|
|
for locIndex := uint32(0); locIndex != gcliexp.PatchLocationsCount; locIndex++ {
|
|
gots[locIndex] = gotPatchLocations[gcliexp.PatchLocationsStartIndex+locIndex]
|
|
}
|
|
f.Images[idx].PatchableGOTs = append(f.Images[idx].PatchableGOTs, PatchableGotExport{
|
|
Name: exportName,
|
|
Kind: imageExport.GetPatchKind().String(),
|
|
ImageIndex: gcliexp.ImageExportIndex,
|
|
OffsetOfImpl: imageExport.DylibOffsetOfImpl,
|
|
GotLocationsV4: gots,
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Image returns the Image with the given name, or nil if no such image exists.
|
|
func (f *File) Image(name string) (*CacheImage, error) {
|
|
// fast path
|
|
if idx, err := f.GetDylibIndex(name); err == nil {
|
|
return f.Images[idx], nil
|
|
}
|
|
// slow path
|
|
var matches []*CacheImage
|
|
for _, i := range f.Images {
|
|
if strings.EqualFold(strings.ToLower(i.Name), strings.ToLower(name)) {
|
|
matches = append(matches, i)
|
|
} else if strings.EqualFold(strings.ToLower(filepath.Base(i.Name)), strings.ToLower(name)) {
|
|
matches = append(matches, i)
|
|
}
|
|
}
|
|
if len(matches) == 1 {
|
|
return matches[0], nil
|
|
} else if len(matches) > 1 {
|
|
var names []string
|
|
for _, m := range matches {
|
|
names = append(names, m.Name)
|
|
}
|
|
var choice string
|
|
prompt := &survey.Select{
|
|
Message: "Multiple images found for " + name + ", please select one:",
|
|
Options: names,
|
|
}
|
|
if err := survey.AskOne(prompt, &choice); err == terminal.InterruptErr {
|
|
log.Warn("Exiting...")
|
|
return nil, fmt.Errorf("multiple images found for %s (please supply FULL path):\n\t- %s", name, strings.Join(names, "\n\t- "))
|
|
}
|
|
return f.Image(choice)
|
|
}
|
|
return nil, &ImageNotFoundError{Name: name}
|
|
}
|
|
|
|
func (f *File) Search(search []byte) (map[mtypes.UUID][]uint64, error) {
|
|
chunkSize := uint64(4096)
|
|
tailLen := uint64(len(search) - 1)
|
|
chunk := make([]byte, chunkSize+tailLen)
|
|
matches := make(map[mtypes.UUID][]uint64)
|
|
for uuid, cache := range f.r {
|
|
var offset uint64
|
|
sr := io.NewSectionReader(cache, 0, 1<<63-1)
|
|
n, err := sr.Read(chunk[tailLen:])
|
|
idx := bytes.Index(chunk[tailLen:uint64(n)+tailLen], search)
|
|
for {
|
|
if idx >= 0 {
|
|
// scan backwards to find the start of the line
|
|
nullIdx := bytes.LastIndexByte(chunk[:uint64(idx)+tailLen], 0x00)
|
|
if nullIdx >= 0 {
|
|
if uint64(offset+uint64(nullIdx+1)) > 0 && uint64(offset+uint64(nullIdx+1)) >= uint64(len(search)-1) {
|
|
matches[uuid] = append(matches[uuid], uint64(offset+uint64(nullIdx+1))-uint64(len(search)-1))
|
|
} else {
|
|
matches[uuid] = append(matches[uuid], uint64(offset+uint64(nullIdx+1)))
|
|
}
|
|
} else {
|
|
matches[uuid] = append(matches[uuid], uint64(offset+uint64(idx)))
|
|
}
|
|
}
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return matches, err
|
|
}
|
|
copy(chunk, chunk[chunkSize:])
|
|
offset += chunkSize
|
|
n, err = sr.Read(chunk[tailLen:])
|
|
idx = bytes.Index(chunk[:uint64(n)+tailLen], search)
|
|
}
|
|
}
|
|
return matches, nil
|
|
}
|
|
|
|
// GetImageContainingTextAddr returns a dylib whose __TEXT segment contains a given virtual address.
|
|
// Uses binary search on the sorted image index for O(log N) performance.
|
|
// NOTE: this can be faster than GetImageContainingVMAddr as it avoids parsing the MachO
|
|
func (f *File) GetImageContainingTextAddr(addr uint64) (*CacheImage, error) {
|
|
n := len(f.sortedImages)
|
|
if n == 0 {
|
|
return nil, fmt.Errorf("address %#x not in any dylib __TEXT", addr)
|
|
}
|
|
// Binary search: find the last image whose LoadAddress <= addr
|
|
idx := sort.Search(n, func(i int) bool {
|
|
return f.sortedImages[i].LoadAddress > addr
|
|
}) - 1
|
|
if idx >= 0 {
|
|
img := f.sortedImages[idx]
|
|
if addr < img.LoadAddress+uint64(img.TextSegmentSize) {
|
|
return img, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("address %#x not in any dylib __TEXT", addr)
|
|
}
|
|
|
|
// GetImageContainingVMAddr returns a dylib whose segment contains a given virtual address
|
|
func (f *File) GetImageContainingVMAddr(address uint64) (*CacheImage, error) {
|
|
for _, img := range f.Images {
|
|
m, err := img.GetPartialMacho()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer m.Close()
|
|
if seg := m.FindSegmentForVMAddr(address); seg != nil {
|
|
return img, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("address %#x not in any dylib", address)
|
|
}
|
|
|
|
// HasImagePath returns the index of a given image path
|
|
func (f *File) HasImagePath(path string) (int, error) {
|
|
var imageIndex uint64
|
|
if f.Headers[f.UUID].MappingOffset >= 0x118 {
|
|
uuid, off, err := f.GetOffset(f.Headers[f.UUID].DylibsTrieAddr)
|
|
if err != nil {
|
|
return -1, fmt.Errorf("failed to get dylibs trie offset: %v", err)
|
|
}
|
|
dylibTrie, err := f.ReadBytesForUUID(uuid, int64(off), f.Headers[f.UUID].DylibsTrieSize)
|
|
if err != nil {
|
|
return -1, fmt.Errorf("failed to read dylibs trie: %v", err)
|
|
}
|
|
imageNode, err := trie.WalkTrie(bytes.NewReader(dylibTrie), path)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
imageIndex, _, err = trie.ReadUleb128FromBuffer(bytes.NewBuffer(dylibTrie[imageNode:]))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
} else {
|
|
img, err := f.Image(path)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
imageIndex = uint64(img.Index)
|
|
}
|
|
|
|
return int(imageIndex), nil
|
|
}
|