mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
Add dyld softlink/import/disassembly helpers, sandbox reach and diff support, richer WebKit IPC metadata, and expanded IOKit method recovery. Fix MOVK softlink address reconstruction and avoid cross-case argument-state poisoning while scanning switch-dispatched user-client methods.
1389 lines
37 KiB
Go
1389 lines
37 KiB
Go
package dyld
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"text/tabwriter"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/pkg/trie"
|
|
"github.com/blacktop/go-macho/types"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/blacktop/ipsw/pkg/disass"
|
|
"github.com/blacktop/ipsw/pkg/symbols"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type rangeEntry struct {
|
|
StartAddr uint64
|
|
FileOffset uint64
|
|
Size uint32
|
|
}
|
|
|
|
type Patch interface {
|
|
GetName() string
|
|
GetKind() string
|
|
GetImplOffset() uint64
|
|
GetClientIndex() uint32
|
|
GetPatchLocations() any
|
|
GetGotLocations() any
|
|
}
|
|
|
|
type PatchableExport struct {
|
|
Name string
|
|
Kind string
|
|
OffsetOfImpl uint32
|
|
ClientIndex uint32
|
|
PatchLocations []CachePatchableLocationV1
|
|
PatchLocationsV2 []CachePatchableLocationV2
|
|
PatchLocationsV4 []CachePatchableLocationV4
|
|
}
|
|
|
|
func (pe PatchableExport) GetName() string {
|
|
return pe.Name
|
|
}
|
|
func (pe PatchableExport) GetKind() string {
|
|
return pe.Kind
|
|
}
|
|
func (pe PatchableExport) GetImplOffset() uint64 {
|
|
return uint64(pe.OffsetOfImpl)
|
|
}
|
|
func (pe PatchableExport) GetClientIndex() uint32 {
|
|
return pe.ClientIndex
|
|
}
|
|
func (pe PatchableExport) GetPatchLocations() any {
|
|
if len(pe.PatchLocations) > 0 {
|
|
return pe.PatchLocations
|
|
} else if len(pe.PatchLocationsV2) > 0 {
|
|
return pe.PatchLocationsV2
|
|
} else if len(pe.PatchLocationsV4) > 0 {
|
|
return pe.PatchLocationsV4
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
func (pe PatchableExport) GetGotLocations() any {
|
|
return nil
|
|
}
|
|
|
|
type PatchableGotExport struct {
|
|
Name string
|
|
Kind string
|
|
OffsetOfImpl uint32
|
|
ImageIndex uint32
|
|
GotLocationsV3 []CachePatchableLocationV3
|
|
GotLocationsV4 []CachePatchableLocationV4Got
|
|
}
|
|
|
|
func (pg PatchableGotExport) GetName() string {
|
|
return pg.Name
|
|
}
|
|
|
|
func (pg PatchableGotExport) GetKind() string {
|
|
return pg.Kind
|
|
}
|
|
func (pg PatchableGotExport) GetImplOffset() uint64 {
|
|
return uint64(pg.OffsetOfImpl)
|
|
}
|
|
func (pg PatchableGotExport) GetClientIndex() uint32 {
|
|
return 0
|
|
}
|
|
func (pg PatchableGotExport) GetPatchLocations() any {
|
|
return nil
|
|
}
|
|
func (pg PatchableGotExport) GetGotLocations() any {
|
|
if len(pg.GotLocationsV3) > 0 {
|
|
return pg.GotLocationsV3
|
|
} else if len(pg.GotLocationsV4) > 0 {
|
|
return pg.GotLocationsV4
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
type astate struct {
|
|
mu sync.Mutex
|
|
|
|
Deps bool
|
|
Got bool
|
|
Stubs bool
|
|
Helpers bool
|
|
Exports bool
|
|
ParsingExports bool
|
|
Privates bool
|
|
Starts bool
|
|
ObjC bool
|
|
Slide bool
|
|
Swift bool
|
|
}
|
|
|
|
func (a *astate) SetDeps(done bool) {
|
|
a.mu.Lock()
|
|
a.Deps = done
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsDepsDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Deps
|
|
}
|
|
|
|
func (a *astate) SetGot(done bool) {
|
|
a.mu.Lock()
|
|
a.Got = done
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsGotDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Got
|
|
}
|
|
|
|
func (a *astate) SetHelpers(done bool) {
|
|
a.mu.Lock()
|
|
a.Helpers = done
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsHelpersDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Helpers
|
|
}
|
|
|
|
func (a *astate) SetStubs(done bool) {
|
|
a.mu.Lock()
|
|
a.Stubs = done
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsStubsDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Stubs
|
|
}
|
|
|
|
func (a *astate) SetExports(done bool) {
|
|
a.mu.Lock()
|
|
a.Exports = done
|
|
a.ParsingExports = false
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsExportsDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Exports
|
|
}
|
|
func (a *astate) BeginExports() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
if a.Exports || a.ParsingExports {
|
|
return false
|
|
}
|
|
a.ParsingExports = true
|
|
return true
|
|
}
|
|
func (a *astate) FinishExports(done bool) {
|
|
a.mu.Lock()
|
|
a.Exports = done
|
|
a.ParsingExports = false
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
func (a *astate) SetPrivates(done bool) {
|
|
a.mu.Lock()
|
|
a.Privates = done
|
|
a.mu.Unlock()
|
|
}
|
|
func (a *astate) IsPrivatesDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Privates
|
|
}
|
|
|
|
func (a *astate) SetStarts(done bool) {
|
|
a.mu.Lock()
|
|
a.Starts = done
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
func (a *astate) IsStartsDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Starts
|
|
}
|
|
|
|
func (a *astate) SetObjC(done bool) {
|
|
a.mu.Lock()
|
|
a.ObjC = done
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
func (a *astate) IsObjcDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.ObjC
|
|
}
|
|
|
|
func (a *astate) SetSlideInfo(done bool) {
|
|
a.mu.Lock()
|
|
a.Slide = done
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
func (a *astate) IsSlideInfoDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Slide
|
|
}
|
|
|
|
func (a *astate) SetSwift(done bool) {
|
|
a.mu.Lock()
|
|
a.Swift = done
|
|
a.mu.Unlock()
|
|
}
|
|
|
|
func (a *astate) IsSwiftDone() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.Swift
|
|
}
|
|
|
|
type analysis struct {
|
|
State astate
|
|
Dependencies []string
|
|
GotPointers map[uint64]uint64
|
|
SymbolStubs map[uint64]uint64
|
|
Helpers map[uint64]uint64
|
|
}
|
|
|
|
// CacheImage represents a dyld dylib image.
|
|
type CacheImage struct {
|
|
Name string
|
|
Index uint32
|
|
Info CacheImageInfo
|
|
Mappings *cacheMappingsWithSlideInfo
|
|
CacheLocalSymbolsEntry64
|
|
CacheImageInfoExtra
|
|
CacheImageTextInfo
|
|
Initializer uint64
|
|
DOFSectionAddr uint64
|
|
DOFSectionSize uint32
|
|
|
|
SlideInfo slideInfo
|
|
RangeEntries []rangeEntry
|
|
PatchableExports []Patch
|
|
PatchableGOTs []Patch
|
|
LocalSymbols []*CacheLocalSymbol64
|
|
PublicSymbols []*Symbol
|
|
ObjC objcInfo
|
|
|
|
Analysis analysis
|
|
|
|
cache *File // pointer back to the dyld cache that the image belongs to
|
|
cuuid types.UUID
|
|
CacheReader
|
|
m *macho.File
|
|
pm *macho.File // partial macho
|
|
sinfo map[uint64]uint64
|
|
}
|
|
|
|
// NewCacheReader returns a CacheReader that reads from r
|
|
// starting at offset off and stops with EOF after n bytes.
|
|
// It also stubs out the MachoReader required SeekToAddr and ReadAtAddr
|
|
func NewCacheReader(off int64, n int64, u types.UUID) CacheReader {
|
|
return CacheReader{off, off, off + n, u}
|
|
}
|
|
|
|
// CacheReader implements Read, Seek, and ReadAt on a section
|
|
// of an underlying ReaderAt.
|
|
type CacheReader struct {
|
|
base int64
|
|
off int64
|
|
limit int64
|
|
ruuid types.UUID
|
|
}
|
|
|
|
// Free frees the underlying data so the GC can reclaim it.
|
|
func (i *CacheImage) Free() {
|
|
i.m = nil
|
|
i.pm = nil
|
|
i.sinfo = nil
|
|
i.RangeEntries = nil
|
|
i.PatchableExports = nil
|
|
i.PatchableGOTs = nil
|
|
i.LocalSymbols = nil
|
|
i.PublicSymbols = nil
|
|
i.ObjC = objcInfo{}
|
|
i.Analysis = analysis{}
|
|
}
|
|
|
|
func (i *CacheImage) Read(p []byte) (n int, err error) {
|
|
if i.off >= i.limit {
|
|
return 0, io.EOF
|
|
}
|
|
if max := i.limit - i.off; int64(len(p)) > max {
|
|
p = p[0:max]
|
|
}
|
|
n, err = i.cache.r[i.ruuid].ReadAt(p, i.off)
|
|
i.off += int64(n)
|
|
return
|
|
}
|
|
|
|
func (i *CacheImage) Seek(offset int64, whence int) (int64, error) {
|
|
switch whence {
|
|
default:
|
|
return 0, fmt.Errorf("Seek: invalid whence")
|
|
case io.SeekStart:
|
|
offset += i.base
|
|
case io.SeekCurrent:
|
|
offset += i.off
|
|
case io.SeekEnd:
|
|
offset += i.limit
|
|
}
|
|
// if scinfo := i.cache.GetSubCacheInfo(i.ruuid); scinfo != nil {
|
|
// if offset > int64(scinfo.CacheVMOffset) {
|
|
// if next := i.cache.GetNextSubCacheInfo(i.ruuid); next != nil {
|
|
// if offset <= int64(next.CacheVMOffset) {
|
|
// i.ruuid = next.UUID
|
|
// offset = offset - int64(scinfo.CacheVMOffset)
|
|
// }
|
|
// } else {
|
|
// return 0, fmt.Errorf("Seek: invalid offset")
|
|
// }
|
|
// }
|
|
// } else {
|
|
if offset < i.base {
|
|
return 0, fmt.Errorf("Seek: invalid offset")
|
|
}
|
|
// }
|
|
i.off = offset
|
|
return offset - i.base, nil
|
|
}
|
|
|
|
func (i *CacheImage) ReadAt(p []byte, off int64) (n int, err error) {
|
|
if i.pm == nil {
|
|
i.pm, err = i.GetPartialMacho()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
}
|
|
if i.pm.Segment("__LINKEDIT") != nil {
|
|
i.ruuid, _, err = i.cache.GetOffset(i.pm.Segment("__LINKEDIT").Addr)
|
|
} else {
|
|
return -1, fmt.Errorf("failed to get __LINKEDIT segment")
|
|
}
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
if off < 0 || off >= i.limit-i.base {
|
|
return 0, io.EOF
|
|
}
|
|
off += i.base
|
|
if max := i.limit - off; int64(len(p)) > max {
|
|
p = p[0:max]
|
|
n, err = i.cache.r[i.ruuid].ReadAt(p, off)
|
|
if err == nil {
|
|
err = io.EOF
|
|
}
|
|
return n, err
|
|
}
|
|
// fmt.Printf("image.ReadAt: cache_uuid=%s, uuid=%s, off=%#x\n", i.cuuid, uuid, off)
|
|
return i.cache.r[i.ruuid].ReadAt(p, off)
|
|
}
|
|
|
|
func (i *CacheImage) SeekToAddr(addr uint64) error {
|
|
uuid, offset, err := i.cache.GetOffset(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
i.ruuid = uuid
|
|
i.Seek(int64(offset), io.SeekStart)
|
|
return nil
|
|
}
|
|
|
|
// ReadAtAddr reads data at a given virtual address
|
|
func (i *CacheImage) ReadAtAddr(buf []byte, addr uint64) (int, error) {
|
|
uuid, off, err := i.cache.GetOffset(addr)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
i.ruuid = uuid
|
|
// fmt.Printf("image.ReadAt: cache_uuid=%s, uuid=%s, off=%#x\n", i.cuuid, uuid, off)
|
|
return i.cache.r[i.ruuid].ReadAt(buf, int64(off))
|
|
}
|
|
|
|
// GetOffset returns the offset for a given virtual address
|
|
func (i *CacheImage) GetOffset(address uint64) (uint64, error) {
|
|
u, o, err := i.cache.GetOffset(address)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
i.ruuid = u
|
|
// fmt.Printf("prim_uuid=%s, cache_uuid=%s, uuid=%s, off=%#x\n", i.cache.UUID, i.cuuid, u, o)
|
|
return o, nil
|
|
}
|
|
|
|
// GetVMAddress returns the virtual address for a given offset
|
|
func (i *CacheImage) GetVMAddress(offset uint64) (uint64, error) {
|
|
return i.cache.GetVMAddressForUUID(i.cuuid, offset)
|
|
}
|
|
|
|
func (i *CacheImage) SlidePointer(addr uint64) uint64 {
|
|
if addr == 0 {
|
|
return addr
|
|
}
|
|
// check if addr is in the cache (not slid)
|
|
if _, _, err := i.cache.GetMappingForVMAddress(addr); err == nil {
|
|
return addr
|
|
}
|
|
// try and slide the encoded pointer
|
|
return i.cache.SlideInfo.SlidePointer(addr)
|
|
}
|
|
|
|
func (i *CacheImage) relativeSelectorBase() (uint64, error) {
|
|
return i.cache.relativeSelectorBase()
|
|
}
|
|
|
|
func (i *CacheImage) partialRelativeSelectorBase() (uint64, error) {
|
|
if i.Name == "/usr/lib/libobjc.A.dylib" {
|
|
return 0, nil
|
|
}
|
|
|
|
return i.relativeSelectorBase()
|
|
}
|
|
|
|
func (f *File) relativeSelectorBase() (uint64, error) {
|
|
f.rsBaseOnce.Do(func() {
|
|
if _, err := f.Image("/usr/lib/libobjc.A.dylib"); err != nil {
|
|
return
|
|
}
|
|
|
|
opt, err := f.GetOptimizations()
|
|
if err != nil {
|
|
f.rsBaseErr = err
|
|
return
|
|
}
|
|
|
|
f.rsBase = f.relativeSelectorBaseForOptimization(opt)
|
|
})
|
|
|
|
return f.rsBase, f.rsBaseErr
|
|
}
|
|
|
|
func (f *File) relativeSelectorBaseForOptimization(opt Optimization) uint64 {
|
|
base := opt.RelativeMethodListsBaseAddress(f.objcOptRoAddr)
|
|
if _, ok := opt.(*ObjCOptimizationHeader); ok {
|
|
base += f.Headers[f.UUID].SharedRegionStart // TODO: can I trust SharedRegionStart? should this be Mapping[0].Address?
|
|
}
|
|
return base
|
|
}
|
|
|
|
// GetMacho parses dyld image as a MachO (slow)
|
|
func (i *CacheImage) GetMacho() (*macho.File, error) {
|
|
if i.m != nil {
|
|
return i.m, nil
|
|
}
|
|
|
|
offset, err := i.GetOffset(i.LoadAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rsBase, err := i.relativeSelectorBase()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i.CacheReader = NewCacheReader(0, 1<<63-1, i.cuuid)
|
|
vma := types.VMAddrConverter{
|
|
Converter: func(addr uint64) uint64 {
|
|
return i.SlidePointer(addr)
|
|
},
|
|
VMAddr2Offet: func(address uint64) (uint64, error) {
|
|
return i.GetOffset(address)
|
|
},
|
|
Offet2VMAddr: func(offset uint64) (uint64, error) {
|
|
return i.GetVMAddress(offset)
|
|
},
|
|
}
|
|
i.m, err = macho.NewFile(io.NewSectionReader(i.cache.r[i.cuuid], int64(offset), int64(i.TextSegmentSize)), macho.FileConfig{
|
|
Offset: int64(offset),
|
|
SectionReader: types.NewCustomSectionReader(i.cache.r[i.cuuid], &vma, 0, 1<<63-1),
|
|
CacheReader: i,
|
|
VMAddrConverter: vma,
|
|
RelativeSelectorBase: rsBase,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return i.m, nil
|
|
}
|
|
|
|
// GetPartialMacho parses dyld image as a partial MachO (fast)
|
|
func (i *CacheImage) GetPartialMacho() (*macho.File, error) {
|
|
if i.pm != nil {
|
|
return i.pm, nil
|
|
}
|
|
offset, err := i.GetOffset(i.LoadAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
i.CacheReader = NewCacheReader(0, 1<<63-1, i.cuuid)
|
|
var rsBase uint64
|
|
if base, err := i.partialRelativeSelectorBase(); err == nil {
|
|
rsBase = base
|
|
}
|
|
vma := types.VMAddrConverter{
|
|
Converter: func(addr uint64) uint64 {
|
|
return i.SlidePointer(addr)
|
|
},
|
|
VMAddr2Offet: func(address uint64) (uint64, error) {
|
|
return i.GetOffset(address)
|
|
},
|
|
Offet2VMAddr: func(offset uint64) (uint64, error) {
|
|
return i.GetVMAddress(offset)
|
|
},
|
|
}
|
|
i.pm, err = macho.NewFile(io.NewSectionReader(i.cache.r[i.cuuid], int64(offset), int64(i.TextSegmentSize)), macho.FileConfig{
|
|
LoadIncluding: []types.LoadCmd{
|
|
types.LC_SEGMENT,
|
|
types.LC_SEGMENT_64,
|
|
types.LC_DYLD_INFO,
|
|
types.LC_DYLD_INFO_ONLY,
|
|
types.LC_ID_DYLIB,
|
|
types.LC_UUID,
|
|
types.LC_BUILD_VERSION,
|
|
types.LC_SOURCE_VERSION,
|
|
types.LC_SUB_FRAMEWORK,
|
|
types.LC_SUB_CLIENT,
|
|
types.LC_REEXPORT_DYLIB,
|
|
types.LC_LOAD_DYLIB,
|
|
types.LC_LOAD_WEAK_DYLIB,
|
|
types.LC_LOAD_UPWARD_DYLIB},
|
|
Offset: int64(offset),
|
|
SectionReader: types.NewCustomSectionReader(i.cache.r[i.cuuid], &vma, 0, 1<<63-1),
|
|
CacheReader: i,
|
|
VMAddrConverter: vma,
|
|
RelativeSelectorBase: rsBase,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return i.pm, nil
|
|
}
|
|
|
|
// Analyze analyzes an image by parsing it's symbols, stubs and GOT
|
|
func (i *CacheImage) Analyze() error {
|
|
|
|
if err := i.ParseObjC(); err != nil {
|
|
log.Errorf("failed to parse objc data for image %s: %v", filepath.Base(i.Name), err)
|
|
// return fmt.Errorf("failed to parse objc data for image %s: %v", filepath.Base(i.Name), err) FIXME: should this error out?
|
|
}
|
|
|
|
if err := i.ParsePublicSymbols(false); err != nil {
|
|
log.Errorf("failed to parse exported symbols for %s: %w", i.Name, err)
|
|
}
|
|
|
|
if err := i.ParseLocalSymbols(false); err != nil {
|
|
if !errors.Is(err, ErrNoLocals) {
|
|
return fmt.Errorf("failed to parse local symbols for %s: %w", i.Name, err)
|
|
}
|
|
}
|
|
|
|
if !i.cache.IsArm64() {
|
|
utils.Indent(log.Warn, 2)("image analysis of stubs and GOT only works on arm64 architectures")
|
|
}
|
|
|
|
if !i.Analysis.State.IsSwiftDone() {
|
|
// TODO: add more swift runtime metadata
|
|
if i.cache.IsArm64() {
|
|
if err := i.ParseSwiftStrings(); err != nil {
|
|
return fmt.Errorf("failed to parse swift strings for %s: %w", i.Name, err)
|
|
}
|
|
}
|
|
i.Analysis.State.SetSwift(true)
|
|
}
|
|
|
|
if !i.Analysis.State.IsSlideInfoDone() {
|
|
if err := i.ParseSlideInfo(); err != nil {
|
|
return fmt.Errorf("failed to parse slide info for %s: %w", i.Name, err)
|
|
}
|
|
}
|
|
|
|
if !i.Analysis.State.IsHelpersDone() && i.cache.IsArm64() {
|
|
log.Debugf("parsing %s symbol stub helpers", i.Name)
|
|
if err := i.ParseHelpers(); err != nil {
|
|
if !errors.Is(err, macho.ErrMachOSectionNotFound) {
|
|
return fmt.Errorf("failed to parse stub helpers for %s: %w", i.Name, err)
|
|
}
|
|
}
|
|
|
|
for start, target := range i.Analysis.Helpers {
|
|
if slide, ok := i.sinfo[start]; ok {
|
|
target = slide
|
|
}
|
|
if symName, ok := i.cache.AddressToSymbol.Get(target); ok {
|
|
i.cache.AddressToSymbol.Set(start, fmt.Sprintf("%s%s", symbols.PrefixStubHelper, symName))
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(start, fmt.Sprintf("%s%x", symbols.PrefixStubHelper, target))
|
|
}
|
|
}
|
|
}
|
|
|
|
if !i.Analysis.State.IsGotDone() && i.cache.IsArm64() {
|
|
log.Debugf("parsing %s global offset table", i.Name)
|
|
if err := i.ParseGOT(); err != nil {
|
|
return fmt.Errorf("failed to parse GOT for %s: %w", i.Name, err)
|
|
}
|
|
|
|
for entry, target := range i.Analysis.GotPointers {
|
|
if slide, ok := i.sinfo[entry]; ok {
|
|
target = slide
|
|
}
|
|
if symName, ok := i.cache.AddressToSymbol.Get(target); ok {
|
|
i.cache.AddressToSymbol.Set(entry, fmt.Sprintf("%s%s", symbols.PrefixGot, symName))
|
|
} else {
|
|
if img, err := i.cache.GetImageContainingVMAddr(target); err == nil {
|
|
if err := img.Analyze(); err != nil {
|
|
// FIXME: return fmt.Errorf("failed parse GOT target %#x: failed to analyze image %s: %w", target, img.Name, err)
|
|
log.Errorf("failed parse GOT target %#x: failed to analyze image %s: %w", target, img.Name, err)
|
|
}
|
|
if symName, ok := i.cache.AddressToSymbol.Get(target); ok {
|
|
i.cache.AddressToSymbol.Set(entry, fmt.Sprintf("%s%s", symbols.PrefixGot, symName))
|
|
} else if laptr, ok := i.Analysis.GotPointers[target]; ok {
|
|
if symName, ok := i.cache.AddressToSymbol.Get(laptr); ok {
|
|
i.cache.AddressToSymbol.Set(entry, fmt.Sprintf("%s%s", symbols.PrefixGot, symName))
|
|
}
|
|
} else {
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for GOT entry %#x => %#x in %s", entry, target, img.Name))
|
|
i.cache.AddressToSymbol.Set(entry, fmt.Sprintf("%s%x ; %s", symbols.PrefixGotFallback, target, filepath.Base(img.Name)))
|
|
}
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(entry, fmt.Sprintf("%s%x", symbols.PrefixGotFallback, target))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !i.Analysis.State.IsStubsDone() && i.cache.IsArm64() {
|
|
log.Debugf("parsing %s symbol stubs", i.Name)
|
|
if err := i.ParseStubs(); err != nil {
|
|
return fmt.Errorf("failed to parse stubs for %s: %w", i.Name, err)
|
|
}
|
|
|
|
for stub, target := range i.Analysis.SymbolStubs {
|
|
if slide, ok := i.sinfo[stub]; ok {
|
|
target = slide
|
|
}
|
|
if symName, ok := i.cache.AddressToSymbol.Get(target); ok {
|
|
if !strings.HasPrefix(symName, symbols.PrefixJump) {
|
|
i.cache.AddressToSymbol.Set(stub, symbols.PrefixJump+strings.TrimPrefix(symName, symbols.PrefixStubHelper))
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(stub, symName)
|
|
}
|
|
} else {
|
|
img, err := i.cache.GetImageContainingVMAddr(target)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find image containing stub target %#x: %w", target, err)
|
|
}
|
|
if err := img.Analyze(); err != nil {
|
|
return fmt.Errorf("failed to lookup symbol stub target %#x: failed to analyze image %s: %w", target, img.Name, err)
|
|
}
|
|
if symName, ok := i.cache.AddressToSymbol.Get(target); ok {
|
|
i.cache.AddressToSymbol.Set(stub, fmt.Sprintf("%s%s", symbols.PrefixJump, symName))
|
|
} else {
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for stub %#x => %#x in %s", stub, target, img.Name))
|
|
i.cache.AddressToSymbol.Set(stub, fmt.Sprintf("%s%x ; %s", symbols.PrefixStubFallback, target, filepath.Base(img.Name)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !i.Analysis.State.IsStartsDone() {
|
|
i.ParseStarts()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseSlideInfo parse the shared_cache slide info corresponding to the MachO
|
|
func (i *CacheImage) ParseSlideInfo() error {
|
|
|
|
i.sinfo = make(map[uint64]uint64)
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, seg := range m.Segments() {
|
|
uuid, mapping, err := i.cache.GetMappingForVMAddress(seg.Addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mapping.SlideInfoOffset == 0 {
|
|
continue
|
|
}
|
|
|
|
startAddr := seg.Addr - mapping.Address
|
|
endAddr := ((seg.Addr + seg.Memsz) - mapping.Address) + uint64(i.cache.SlideInfo.GetPageSize())
|
|
|
|
start := startAddr / uint64(i.cache.SlideInfo.GetPageSize())
|
|
end := endAddr / uint64(i.cache.SlideInfo.GetPageSize())
|
|
|
|
rs, err := i.cache.GetRebaseInfoForPages(uuid, mapping, start, end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, r := range rs {
|
|
i.sinfo[r.Pointer.Raw()] = r.Target
|
|
}
|
|
}
|
|
|
|
i.Analysis.State.SetSlideInfo(true)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSlideInfo returns a slide info map for the image
|
|
func (i *CacheImage) GetSlideInfo() (map[uint64]uint64, error) {
|
|
if !i.Analysis.State.IsSlideInfoDone() {
|
|
if err := i.ParseSlideInfo(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return i.sinfo, nil
|
|
}
|
|
|
|
// ParseStarts parse function starts in MachO
|
|
func (i *CacheImage) ParseStarts() {
|
|
if i.m != nil {
|
|
for _, fn := range i.m.GetFunctions() {
|
|
if ok := i.cache.AddressToSymbol.Has(fn.StartAddr); !ok {
|
|
i.cache.AddressToSymbol.Set(fn.StartAddr, fmt.Sprintf("sub_%x", fn.StartAddr))
|
|
}
|
|
}
|
|
}
|
|
i.Analysis.State.SetStarts(true)
|
|
}
|
|
|
|
// ParseObjC parse ObjC runtime for MachO image
|
|
func (i *CacheImage) ParseObjC() error {
|
|
if !i.Analysis.State.IsObjcDone() {
|
|
if err := i.cache.CFStringsForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc cfstrings for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
// TODO: add objc methods in the -[Class sel:] form
|
|
if err := i.cache.MethodsForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc methods for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
if strings.Contains(i.Name, "libobjc.A.dylib") {
|
|
if _, err := i.cache.GetAllObjCSelectors(false); err != nil {
|
|
return fmt.Errorf("failed to parse objc all selectors: %v", err)
|
|
}
|
|
} else {
|
|
if err := i.cache.SelectorsForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc selectors for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
}
|
|
if err := i.cache.ClassesForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc classes for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
if err := i.cache.CategoriesForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc categories for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
if err := i.cache.ProtocolsForImage(i.Name); err != nil {
|
|
return fmt.Errorf("failed to parse objc protocols for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
if err := i.cache.GetObjCStubsForImage(i.Name); err != nil && !errors.Is(err, macho.ErrObjcSectionNotFound) {
|
|
return fmt.Errorf("failed to parse objc stubs for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
i.Analysis.State.SetObjC(true)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ParseGOT parse global offset table in MachO
|
|
func (i *CacheImage) ParseGOT() error {
|
|
|
|
i.Analysis.GotPointers = make(map[uint64]uint64)
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get MachO for image %s; %v", i.Name, err)
|
|
}
|
|
defer m.Close()
|
|
|
|
for _, secName := range []string{"__got", "__auth_got", "__auth_ptr"} {
|
|
for _, sec := range m.Sections {
|
|
if (sec.Seg == "__AUTH_CONST" || sec.Seg == "__DATA_CONST") && sec.Name == secName {
|
|
dat := make([]byte, sec.Size)
|
|
if _, err := i.ReadAtAddr(dat, sec.Addr); err != nil {
|
|
return fmt.Errorf("failed to read GOT section %s: %v", secName, err)
|
|
}
|
|
ptrs := make([]uint64, sec.Size/8)
|
|
if err := binary.Read(bytes.NewReader(dat), binary.LittleEndian, &ptrs); err != nil {
|
|
return fmt.Errorf("failed to read %s.%s got pointers; %v", sec.Seg, sec.Name, err)
|
|
}
|
|
for idx, ptr := range ptrs {
|
|
if ptr != 0 {
|
|
i.Analysis.GotPointers[sec.Addr+uint64(idx*8)] = i.cache.SlideInfo.SlidePointer(ptr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
i.Analysis.State.SetGot(true)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseStubs parse symbol stubs in MachO
|
|
func (i *CacheImage) ParseStubs() error {
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get MachO for image %s; %v", i.Name, err)
|
|
}
|
|
|
|
i.Analysis.SymbolStubs = make(map[uint64]uint64)
|
|
for _, sec := range m.Sections {
|
|
if sec.Flags.IsSymbolStubs() {
|
|
dat := make([]byte, sec.Size)
|
|
if _, err := i.ReadAtAddr(dat, sec.Addr); err != nil {
|
|
return err
|
|
}
|
|
stubs, err := disass.ParseStubsASM(dat, sec.Addr, func(u uint64) (uint64, error) {
|
|
return i.cache.ReadPointerAtAddress(u)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range stubs {
|
|
i.Analysis.SymbolStubs[k] = i.cache.SlideInfo.SlidePointer(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
i.Analysis.State.SetStubs(true)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResolveStubAtAddr checks if addr is in a stub section (__stubs, __auth_stubs)
|
|
// and if so, uses ParseStubs to find the target, then resolves it to a symbol.
|
|
// Returns the target address and symbol name, or an error if not a stub or unresolvable.
|
|
func (i *CacheImage) ResolveStubAtAddr(addr uint64) (uint64, string, error) {
|
|
if !i.Analysis.State.IsStubsDone() {
|
|
if err := i.ParseStubs(); err != nil {
|
|
return 0, "", err
|
|
}
|
|
}
|
|
target, ok := i.Analysis.SymbolStubs[addr]
|
|
if !ok {
|
|
return 0, "", fmt.Errorf("address %#x is not a known stub", addr)
|
|
}
|
|
// Resolve target symbol: search the target image's symtab + local symbols
|
|
if img, err := i.cache.GetImageContainingTextAddr(target); err == nil {
|
|
tm, err := img.GetMacho()
|
|
if err == nil {
|
|
defer tm.Close()
|
|
if syms, err := tm.FindAddressSymbols(target); err == nil {
|
|
for _, s := range syms {
|
|
if s.Name != "<redacted>" && s.Name != "" {
|
|
return target, s.Name, nil
|
|
}
|
|
}
|
|
}
|
|
if name, err := img.FindLocalSymbolAtAddr(target); err == nil && name != "" {
|
|
return target, name, nil
|
|
}
|
|
}
|
|
}
|
|
return target, "", fmt.Errorf("stub at %#x -> %#x: target symbol not found", addr, target)
|
|
}
|
|
|
|
// ParseHelpers parse symbol stub helpers in MachO
|
|
func (i *CacheImage) ParseHelpers() error {
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get MachO for image %s; %v", i.Name, err)
|
|
}
|
|
|
|
i.Analysis.Helpers, err = disass.ParseHelpersASM(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
i.Analysis.State.SetHelpers(true)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *CacheImage) ParseSwiftStrings() error {
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get MachO for image %s; %v", i.Name, err)
|
|
}
|
|
|
|
text := m.Section("__TEXT", "__text")
|
|
if text == nil {
|
|
return fmt.Errorf("no __TEXT.__text section found")
|
|
}
|
|
|
|
data, err := text.Data()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get __TEXT.__text data: %v", err)
|
|
}
|
|
|
|
engine := disass.NewMachoDisass(m, &disass.Config{
|
|
Data: data,
|
|
StartAddress: text.Addr,
|
|
Middle: text.Addr + text.Size,
|
|
})
|
|
|
|
strs, err := engine.FindSwiftStrings()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find swift strings: %v", err)
|
|
}
|
|
for addr, str := range strs {
|
|
if len(str) > 0 {
|
|
i.cache.AddressToSymbol.Set(addr, fmt.Sprintf("%v", str))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FindLocalSymbolAtAddr searches only this image's DSC local symbol nlist entries
|
|
// for a symbol at the given address, without populating the full a2s cache.
|
|
// Returns the symbol name or an error if not found.
|
|
func (i *CacheImage) FindLocalSymbolAtAddr(addr uint64) (string, error) {
|
|
var uuid types.UUID
|
|
if i.cache.IsDyld4 {
|
|
uuid = i.cache.symUUID
|
|
} else {
|
|
uuid = i.cache.UUID
|
|
}
|
|
if i.cache.Headers[uuid].LocalSymbolsOffset == 0 {
|
|
return "", fmt.Errorf("no local symbols")
|
|
}
|
|
nlistCount := int(i.cache.Images[i.Index].NlistCount)
|
|
if nlistCount == 0 {
|
|
return "", fmt.Errorf("no local symbols for image")
|
|
}
|
|
// Compute direct offset to this image's nlist entries using NlistStartIndex
|
|
nlistOffset := int64(i.cache.LocalSymInfo.NListFileOffset) +
|
|
int64(i.cache.Images[i.Index].NlistStartIndex)*nlist64Size
|
|
// Bulk-read all nlist entries for this image
|
|
nlistBuf := make([]byte, nlistCount*nlist64Size)
|
|
if _, err := i.cache.r[uuid].ReadAt(nlistBuf, nlistOffset); err != nil {
|
|
return "", fmt.Errorf("failed to read nlist entries: %w", err)
|
|
}
|
|
// Scan for matching address
|
|
strPoolBase := int64(i.cache.LocalSymInfo.StringsFileOffset)
|
|
strPoolSize := int64(i.cache.LocalSymInfo.StringsSize)
|
|
for n := range nlistCount {
|
|
off := n * nlist64Size
|
|
nameIdx, value := parseNlist64(nlistBuf[off:])
|
|
if value == addr {
|
|
s, _, err := readStringPool(i.cache.r[uuid], strPoolBase, strPoolSize, int64(nameIdx), nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return s, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no local symbol at %#x", addr)
|
|
}
|
|
|
|
// ParseLocalSymbols parses and caches, with the option to dump, all the local/private symbols for an image
|
|
func (i *CacheImage) ParseLocalSymbols(dump bool) error {
|
|
|
|
if !i.Analysis.State.IsPrivatesDone() {
|
|
|
|
var uuid types.UUID
|
|
|
|
if i.cache.IsDyld4 {
|
|
uuid = i.cache.symUUID
|
|
} else {
|
|
uuid = i.cache.UUID
|
|
}
|
|
|
|
if i.cache.Headers[uuid].LocalSymbolsOffset == 0 {
|
|
i.Analysis.State.SetPrivates(true) // TODO: does this have any bad side-effects ?
|
|
return fmt.Errorf("failed to parse local syms for image %s: %w", filepath.Base(i.Name), ErrNoLocals)
|
|
}
|
|
|
|
nlistCount := int(i.cache.Images[i.Index].NlistCount)
|
|
if nlistCount == 0 {
|
|
i.Analysis.State.SetPrivates(true)
|
|
return nil
|
|
}
|
|
|
|
// Compute direct offset to this image's nlist entries using NlistStartIndex
|
|
nlistOffset := int64(i.cache.LocalSymInfo.NListFileOffset) +
|
|
int64(i.cache.Images[i.Index].NlistStartIndex)*nlist64Size
|
|
|
|
// Bulk-read all nlist entries for this image at once
|
|
nlistBuf := make([]byte, nlistCount*nlist64Size)
|
|
if _, err := i.cache.r[uuid].ReadAt(nlistBuf, nlistOffset); err != nil {
|
|
return fmt.Errorf("failed to read nlist entries for %s: %w", filepath.Base(i.Name), err)
|
|
}
|
|
|
|
// Read strings from string pool (reuse buffer across iterations)
|
|
strPoolBase := int64(i.cache.LocalSymInfo.StringsFileOffset)
|
|
strPoolSize := int64(i.cache.LocalSymInfo.StringsSize)
|
|
var (
|
|
strBuf = make([]byte, 512)
|
|
s string
|
|
readErr error
|
|
)
|
|
|
|
for n := range nlistCount {
|
|
off := n * nlist64Size
|
|
nameIdx, value := parseNlist64(nlistBuf[off:])
|
|
|
|
s, strBuf, readErr = readStringPool(i.cache.r[uuid], strPoolBase, strPoolSize, int64(nameIdx), strBuf)
|
|
if readErr != nil {
|
|
log.Errorf("failed to read local symbol name for image %s: %v", filepath.Base(i.Name), readErr)
|
|
continue
|
|
}
|
|
|
|
nlist := types.Nlist64{}
|
|
nlist.Name = nameIdx
|
|
nlist.Type = types.NType(nlistBuf[off+4])
|
|
nlist.Sect = nlistBuf[off+5]
|
|
nlist.Desc = types.NDescType(binary.LittleEndian.Uint16(nlistBuf[off+6:]))
|
|
nlist.Value = value
|
|
|
|
i.cache.AddressToSymbol.Set(value, s)
|
|
i.cache.Images[i.Index].LocalSymbols = append(i.cache.Images[i.Index].LocalSymbols, &CacheLocalSymbol64{
|
|
Name: s,
|
|
Nlist64: nlist,
|
|
FoundInDylib: i.Name,
|
|
})
|
|
|
|
if dump {
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(CacheLocalSymbol64{
|
|
Name: s,
|
|
Nlist64: nlist,
|
|
Macho: m,
|
|
FoundInDylib: filepath.Base(i.Name),
|
|
}.String(true))
|
|
}
|
|
}
|
|
|
|
sort.Slice(i.LocalSymbols, func(j, k int) bool {
|
|
return i.LocalSymbols[j].Name < i.LocalSymbols[k].Name
|
|
})
|
|
|
|
i.Analysis.State.SetPrivates(true)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLocalSymbol returns the local symbol matching the given name
|
|
func (i *CacheImage) GetLocalSymbol(name string) (*CacheLocalSymbol64, error) {
|
|
i.ParseLocalSymbols(false)
|
|
|
|
idx := sort.Search(len(i.LocalSymbols), func(idx int) bool { return i.LocalSymbols[idx].Name >= name })
|
|
if idx < len(i.LocalSymbols) && i.LocalSymbols[idx].Name == name {
|
|
return i.LocalSymbols[idx], nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("local symbol %s not found in image %s", name, filepath.Base(i.Name))
|
|
}
|
|
|
|
// GetLocalSymbolsAsMachoSymbols converts all the dylibs private symbols into MachO symtab public symbols
|
|
func (i *CacheImage) GetLocalSymbolsAsMachoSymbols() []macho.Symbol {
|
|
var syms []macho.Symbol
|
|
for _, lsym := range i.LocalSymbols {
|
|
syms = append(syms, macho.Symbol{
|
|
Name: lsym.Name,
|
|
Type: lsym.Type,
|
|
Sect: lsym.Sect,
|
|
Desc: lsym.Desc,
|
|
Value: lsym.Value,
|
|
})
|
|
}
|
|
return syms
|
|
}
|
|
|
|
// ParsePublicSymbols parses and caches, with the option to dump, all the exports, symtab and dyld_info symbols in the image/dylib
|
|
func (i *CacheImage) ParsePublicSymbols(dump bool) error {
|
|
|
|
if i.Analysis.State.BeginExports() {
|
|
defer func() {
|
|
if !i.Analysis.State.IsExportsDone() {
|
|
i.Analysis.State.FinishExports(false)
|
|
}
|
|
}()
|
|
|
|
var w *tabwriter.Writer
|
|
if dump {
|
|
w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
|
}
|
|
// try to parse exports from the cache's export trie or the dylib's LC_DYLD_EXPORTS_TRIE
|
|
if syms, err := i.cache.GetExportTrieSymbols(i); err == nil {
|
|
for _, sym := range syms {
|
|
if err := i.resolveReExport(&sym); err != nil {
|
|
return err
|
|
}
|
|
if dump {
|
|
fmt.Fprintf(w, "%s\n", sym)
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(sym.Address, sym.Name)
|
|
i.PublicSymbols = append(i.PublicSymbols, &Symbol{
|
|
Name: sym.Name,
|
|
Address: sym.Address,
|
|
Type: sym.Type(),
|
|
Kind: EXPORT,
|
|
})
|
|
}
|
|
}
|
|
if dump {
|
|
w.Flush()
|
|
}
|
|
}
|
|
// try to parse the dylib's symbol table
|
|
m, err := i.GetMacho()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, sym := range m.Symtab.Syms {
|
|
if sym.Name == "<redacted>" {
|
|
continue
|
|
}
|
|
// TODO: Handle ReExports
|
|
var sec string
|
|
if sym.Sect > 0 && int(sym.Sect) <= len(m.Sections) {
|
|
sec = fmt.Sprintf("%s.%s", m.Sections[sym.Sect-1].Seg, m.Sections[sym.Sect-1].Name)
|
|
}
|
|
if dump {
|
|
fmt.Fprintf(w, "%#09x:\t(%s)\t%s\n", sym.Value, sym.Type.String(sec), sym.Name)
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(sym.Value, sym.Name)
|
|
i.PublicSymbols = append(i.PublicSymbols, &Symbol{
|
|
Name: sym.Name,
|
|
Address: sym.Value,
|
|
Type: sym.Type.String(sec),
|
|
Kind: SYMTAB,
|
|
})
|
|
}
|
|
}
|
|
if dump {
|
|
w.Flush()
|
|
}
|
|
// try to parse LC_DYLD_INFO binds
|
|
if binds, err := m.GetBindInfo(); err == nil {
|
|
for _, bind := range binds {
|
|
if dump {
|
|
fmt.Fprintf(w, "%#09x:\t(%s.%s|from %s)\t%s\n", bind.Start+bind.SegOffset, bind.Segment, bind.Section, bind.Dylib, bind.Name)
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(bind.Start+bind.SegOffset, bind.Name)
|
|
i.PublicSymbols = append(i.PublicSymbols, &Symbol{
|
|
Name: bind.Name,
|
|
Address: bind.Start + bind.SegOffset,
|
|
Type: fmt.Sprintf("%s|%s", bind.Kind, bind.Dylib),
|
|
Kind: BIND,
|
|
})
|
|
}
|
|
}
|
|
if dump {
|
|
w.Flush()
|
|
}
|
|
}
|
|
// try to parse LC_DYLD_INFO rebases TODO: this is slide info and not sym info
|
|
// if rebases, err := m.GetRebaseInfo(); err == nil {
|
|
// for _, rebase := range rebases {
|
|
// rebase.
|
|
// }
|
|
// }
|
|
// try to parse LC_DYLD_INFO exports TODO: is this redundant???
|
|
if exports, err := m.GetExports(); err == nil {
|
|
for _, export := range exports {
|
|
if err := i.resolveReExport(&export); err != nil {
|
|
return err
|
|
}
|
|
if dump {
|
|
fmt.Fprintf(w, "%s\n", export)
|
|
} else {
|
|
i.cache.AddressToSymbol.Set(export.Address, export.Name)
|
|
i.PublicSymbols = append(i.PublicSymbols, &Symbol{
|
|
Name: export.Name,
|
|
Address: export.Address,
|
|
Type: export.Type(),
|
|
Kind: EXPORT,
|
|
})
|
|
}
|
|
}
|
|
if dump {
|
|
w.Flush()
|
|
}
|
|
}
|
|
|
|
sort.Slice(i.PublicSymbols, func(j, k int) bool {
|
|
return i.PublicSymbols[j].Name < i.PublicSymbols[k].Name
|
|
})
|
|
|
|
i.Analysis.State.FinishExports(true)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// returns the public symbol matching the given name
|
|
func (i *CacheImage) GetPublicSymbol(name string) (*Symbol, error) {
|
|
if err := i.ParsePublicSymbols(false); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !i.Analysis.State.IsExportsDone() {
|
|
for _, sym := range i.PublicSymbols {
|
|
if sym.Name == name {
|
|
return sym, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("public symbols for image %s are still being parsed", filepath.Base(i.Name))
|
|
}
|
|
|
|
idx := sort.Search(len(i.PublicSymbols), func(idx int) bool { return i.PublicSymbols[idx].Name >= name })
|
|
if idx < len(i.PublicSymbols) && i.PublicSymbols[idx].Name == name {
|
|
return i.PublicSymbols[idx], nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("public symbol %s not found in image %s", name, filepath.Base(i.Name))
|
|
}
|
|
|
|
func (i *CacheImage) resolveReExport(export *trie.TrieExport) error {
|
|
if !export.Flags.ReExport() {
|
|
return nil
|
|
}
|
|
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
export.FoundInDylib, err = reexportLibraryName(m.ImportedLibraries(), export.Other)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(export.ReExport) == 0 {
|
|
return nil
|
|
}
|
|
|
|
reimg, err := i.cache.Image(export.FoundInDylib)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resym, err := reimg.GetPublicSymbol(export.ReExport); err == nil {
|
|
export.Address = resym.Address
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func reexportLibraryName(importedLibraries []string, ordinal uint64) (string, error) {
|
|
if ordinal == 0 || ordinal > uint64(len(importedLibraries)) {
|
|
return "", fmt.Errorf("re-export ordinal %d outside imported library table with %d entries", ordinal, len(importedLibraries))
|
|
}
|
|
return importedLibraries[int(ordinal)-1], nil
|
|
}
|
|
|
|
// GetExport returns the trie export symbol matching the given name
|
|
func (i *CacheImage) GetExport(symbol string) (*trie.TrieExport, error) {
|
|
var eTrieAddr, eTrieSize uint64
|
|
|
|
if i.CacheImageInfoExtra.ExportsTrieAddr > 0 {
|
|
eTrieAddr = i.CacheImageInfoExtra.ExportsTrieAddr
|
|
eTrieSize = uint64(i.CacheImageInfoExtra.ExportsTrieSize)
|
|
} else {
|
|
m, err := i.GetMacho()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse MachO for image %s: %v", filepath.Base(i.Name), err)
|
|
}
|
|
if m.DyldExportsTrie() != nil {
|
|
return m.GetDyldExport(symbol)
|
|
} else if m.DyldInfo() != nil {
|
|
eTrieAddr, _ = i.GetVMAddress(uint64(m.DyldInfo().ExportOff))
|
|
eTrieSize = uint64(m.DyldInfo().ExportSize)
|
|
} else {
|
|
return nil, fmt.Errorf("failed to get export trie data for image %s: %w", filepath.Base(i.Name), ErrNoExportTrieInMachO)
|
|
}
|
|
}
|
|
|
|
uuid, eTrieOffset, err := i.cache.GetOffset(eTrieAddr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get offset of export trie addr")
|
|
}
|
|
|
|
sr := io.NewSectionReader(i.cache.r[uuid], 0, 1<<63-1)
|
|
|
|
if _, err := sr.Seek(int64(eTrieOffset), io.SeekStart); err != nil {
|
|
return nil, fmt.Errorf("failed to seek to export trie offset in cache: %v", err)
|
|
}
|
|
|
|
exportTrie := make([]byte, eTrieSize)
|
|
if err := binary.Read(sr, i.cache.ByteOrder, &exportTrie); err != nil {
|
|
return nil, fmt.Errorf("failed to read export trie data: %v", err)
|
|
}
|
|
|
|
r := bytes.NewReader(exportTrie)
|
|
|
|
if _, err = trie.WalkTrie(r, symbol); err != nil {
|
|
return nil, err
|
|
}
|
|
return trie.ReadExport(r, symbol, i.LoadAddress)
|
|
}
|
|
|
|
// GetSymbol retuns a Symbol private or public matching a given name
|
|
func (i *CacheImage) GetSymbol(name string) (*Symbol, error) {
|
|
// check local symbols
|
|
if lsym, err := i.GetLocalSymbol(name); err == nil {
|
|
m, err := i.GetPartialMacho()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sec string
|
|
if lsym.Sect > 0 && int(lsym.Sect) <= len(m.Sections) {
|
|
sec = fmt.Sprintf("%s.%s", m.Sections[lsym.Sect-1].Seg, m.Sections[lsym.Sect-1].Name)
|
|
}
|
|
return &Symbol{
|
|
Name: lsym.Name,
|
|
Address: lsym.Value,
|
|
Type: lsym.Type.String(sec),
|
|
Image: i.Name,
|
|
Kind: LOCAL,
|
|
}, nil
|
|
}
|
|
// check public symbols
|
|
if sym, err := i.GetPublicSymbol(name); err == nil {
|
|
sym.Image = i.Name
|
|
return sym, nil
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|