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.
1572 lines
47 KiB
Go
1572 lines
47 KiB
Go
package dyld
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"io"
|
||
"path/filepath"
|
||
"strings"
|
||
"unsafe"
|
||
|
||
"github.com/apex/log"
|
||
"github.com/blacktop/go-macho"
|
||
"github.com/blacktop/go-macho/types"
|
||
"github.com/blacktop/go-macho/types/objc"
|
||
"github.com/blacktop/ipsw/internal/utils"
|
||
"github.com/blacktop/ipsw/pkg/disass"
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
const (
|
||
mask uint64 = (1 << 40) - 1 // 40bit mask
|
||
)
|
||
|
||
type optFlags uint32
|
||
|
||
const (
|
||
IsProduction optFlags = (1 << 0) // never set in development cache
|
||
NoMissingWeakSuperclasses optFlags = (1 << 1) // set in development cache and customer
|
||
LargeSharedCache optFlags = (1 << 2) // Shared cache was built with the new Large format
|
||
)
|
||
|
||
// objcInfo is the dyld_shared_cache dylib objc object
|
||
type objcInfo struct {
|
||
Methods []objc.Method
|
||
ClassRefs map[uint64]*objc.Class
|
||
SuperRefs map[uint64]*objc.Class
|
||
SelRefs map[uint64]*objc.Selector
|
||
ProtoRefs map[uint64]*objc.Protocol
|
||
CatRefs map[uint64]*objc.Category
|
||
CFStrings []objc.CFString
|
||
Stubs map[uint64]*objc.Stub
|
||
}
|
||
|
||
type ObjcClassheaderT struct {
|
||
ClsOffset int32
|
||
HiOffset int32
|
||
}
|
||
|
||
type ClassHeaderV16T uint64
|
||
|
||
func (h ClassHeaderV16T) IsDuplicate() bool {
|
||
return types.ExtractBits(uint64(h), 0, 1) == 1
|
||
}
|
||
|
||
// ObjectCacheOffset returns offset from the shared cache base
|
||
func (h ClassHeaderV16T) ObjectCacheOffset() uint64 {
|
||
return types.ExtractBits(uint64(h), 1, 47)
|
||
}
|
||
func (h ClassHeaderV16T) DylibObjCIndex() uint16 {
|
||
return uint16(types.ExtractBits(uint64(h), 48, 16))
|
||
}
|
||
|
||
type Optimization interface {
|
||
GetVersion() uint32
|
||
GetFlags() optFlags
|
||
// HeaderInfoRO() uint64
|
||
// HeaderInfoRW() uint64
|
||
SelectorHashTableOffset(uint64) uint64
|
||
ClassHashTableOffset(uint64) uint64
|
||
ProtocolHashTableOffset(uint64) uint64
|
||
RelativeMethodListsBaseAddress(uint64) uint64
|
||
}
|
||
|
||
/*
|
||
* objc_opt_t - op-level optimization structure. <dyld/include/objc-shared-cache.h>
|
||
*/
|
||
|
||
// ObjcOptT is a objc_opt_t structure
|
||
type ObjcOptT struct {
|
||
Version uint32
|
||
Flags optFlags
|
||
SelectorOptOffset int32
|
||
HeaderOptRoOffset int32
|
||
UnusedClassOptOffset int32
|
||
UnusedProtocolOptOffset int32
|
||
HeaderoptRwOffset int32
|
||
UnusedProtocolOpt2Offset int32
|
||
LargeSharedCachesClassOffset int32
|
||
LargeSharedCachesProtocolOffset int32
|
||
RelativeMethodSelectorBaseAddressCacheOffset int64
|
||
}
|
||
|
||
func (o *ObjcOptT) GetVersion() uint32 {
|
||
return o.Version
|
||
}
|
||
func (o *ObjcOptT) GetFlags() optFlags {
|
||
return o.Flags
|
||
}
|
||
func (o *ObjcOptT) SelectorHashTableOffset(base uint64) uint64 {
|
||
return uint64(int64(base) + int64(o.SelectorOptOffset))
|
||
}
|
||
func (o *ObjcOptT) ClassHashTableOffset(base uint64) uint64 {
|
||
switch o.Version {
|
||
case 16:
|
||
return uint64(int64(base) + int64(o.LargeSharedCachesClassOffset))
|
||
default:
|
||
return uint64(int64(base) + int64(o.UnusedClassOptOffset))
|
||
}
|
||
}
|
||
func (o *ObjcOptT) ProtocolHashTableOffset(base uint64) uint64 {
|
||
switch o.Version {
|
||
case 16:
|
||
return uint64(int64(base) + int64(o.LargeSharedCachesProtocolOffset))
|
||
case 15:
|
||
return uint64(int64(base) + int64(o.UnusedProtocolOpt2Offset))
|
||
default:
|
||
return uint64(int64(base) + int64(o.UnusedProtocolOptOffset))
|
||
}
|
||
}
|
||
func (o *ObjcOptT) RelativeMethodListsBaseAddress(base uint64) uint64 {
|
||
return uint64(int64(base) + int64(o.RelativeMethodSelectorBaseAddressCacheOffset))
|
||
}
|
||
|
||
func (o *ObjcOptT) isPointerAligned() bool {
|
||
return (binary.Size(o) % 8) == 0
|
||
}
|
||
|
||
func (o ObjcOptT) GetClassOffset() int32 {
|
||
if o.Version >= 16 {
|
||
return o.LargeSharedCachesClassOffset
|
||
} else {
|
||
return o.UnusedClassOptOffset
|
||
}
|
||
}
|
||
|
||
// ObjCOptimizationHeader is the NEW LargeSharedCache objc optimization header
|
||
type ObjCOptimizationHeader struct {
|
||
Version uint32
|
||
Flags optFlags
|
||
HeaderInfoRoCacheOffset uint64
|
||
HeaderInfoRwCacheOffset uint64
|
||
SelectorHashTableCacheOffset uint64
|
||
ClassHashTableCacheOffset uint64
|
||
ProtocolHashTableCacheOffset uint64
|
||
RelativeMethodSelectorBaseAddressOffset uint64
|
||
// v2 fields (iOS/macOS 26.4+)
|
||
// TODO: validate field names when Apple releases 26.4 dyld sources
|
||
SelectorStringsSize uint64 // byte size of relative method selector strings pool
|
||
Reserved uint64
|
||
}
|
||
|
||
func (o *ObjCOptimizationHeader) GetVersion() uint32 {
|
||
// Known header versions (1, 2) use stringHashV16 layout.
|
||
if o.Version <= 2 {
|
||
return 16
|
||
}
|
||
return o.Version
|
||
}
|
||
func (o *ObjCOptimizationHeader) GetFlags() optFlags {
|
||
return o.Flags
|
||
}
|
||
func (o *ObjCOptimizationHeader) GetHeaderInfoRoCacheOffset() uint64 {
|
||
return o.HeaderInfoRoCacheOffset
|
||
}
|
||
func (o *ObjCOptimizationHeader) GetHeaderInfoRwCacheOffset() uint64 {
|
||
return o.HeaderInfoRwCacheOffset
|
||
}
|
||
func (o *ObjCOptimizationHeader) SelectorHashTableOffset(base uint64) uint64 {
|
||
return o.SelectorHashTableCacheOffset
|
||
}
|
||
func (o *ObjCOptimizationHeader) ClassHashTableOffset(base uint64) uint64 {
|
||
return o.ClassHashTableCacheOffset
|
||
}
|
||
func (o *ObjCOptimizationHeader) ProtocolHashTableOffset(base uint64) uint64 {
|
||
return o.ProtocolHashTableCacheOffset
|
||
}
|
||
func (o *ObjCOptimizationHeader) RelativeMethodListsBaseAddress(base uint64) uint64 {
|
||
return o.RelativeMethodSelectorBaseAddressOffset
|
||
}
|
||
|
||
func (f *File) getLibObjC() (*macho.File, error) {
|
||
image, err := f.Image("/usr/lib/libobjc.A.dylib")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if image.pm == nil {
|
||
image.pm, err = image.GetPartialMacho()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
return image.pm, nil
|
||
}
|
||
|
||
func (f *File) getOptimizationsOld() (Optimization, error) {
|
||
|
||
libObjC, err := f.getLibObjC()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if s := libObjC.Section("__TEXT", "__objc_opt_ro"); s != nil {
|
||
f.objcOptRoAddr = s.Addr
|
||
uuid, off, err := f.GetOffset(s.Addr)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
dat, err := f.ReadBytesForUUID(uuid, int64(off), s.Size)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read __TEXT.__objc_opt_ro data")
|
||
}
|
||
opt := ObjcOptT{}
|
||
if err := binary.Read(bytes.NewReader(dat), f.ByteOrder, &opt); err != nil {
|
||
return nil, err
|
||
}
|
||
if opt.Version > 16 {
|
||
return nil, fmt.Errorf("objc optimization version should be 16 or less, but found %d", opt.Version)
|
||
}
|
||
// log.Debugf("Objective-C Optimizations:\n%s", opt)
|
||
return &opt, nil
|
||
}
|
||
|
||
return nil, fmt.Errorf("unable to find section __TEXT.__objc_opt_ro in /usr/lib/libobjc.A.dylib")
|
||
}
|
||
|
||
func (f *File) GetOptimizations() (Optimization, error) {
|
||
f.objcOptOnce.Do(func() {
|
||
f.objcOpt, f.objcOptErr = f.getOptimizations()
|
||
})
|
||
return f.objcOpt, f.objcOptErr
|
||
}
|
||
|
||
func (f *File) getOptimizations() (Optimization, error) {
|
||
if f.Headers[f.UUID].MappingOffset > uint32(unsafe.Offsetof(f.Headers[f.UUID].ObjcOptsSize)) { // check for NEW objc optimizations
|
||
const (
|
||
objcOptHeaderV1Size = 56 // Version(4) + Flags(4) + 6×uint64(48)
|
||
objcOptHeaderV2Size = 72 // v1 + SelectorStringsSize(8) + Reserved(8)
|
||
)
|
||
if f.Headers[f.UUID].ObjcOptsOffset > 0 && f.Headers[f.UUID].ObjcOptsSize >= objcOptHeaderV1Size {
|
||
uuid, off, err := f.GetOffset(f.Headers[f.UUID].SharedRegionStart + f.Headers[f.UUID].ObjcOptsOffset)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get offset for NEW objc optimization header at addr %#x: %v",
|
||
f.Headers[f.UUID].SharedRegionStart+f.Headers[f.UUID].ObjcOptsOffset, err)
|
||
}
|
||
|
||
sr := io.NewSectionReader(f.r[uuid], int64(off), int64(f.Headers[f.UUID].ObjcOptsSize))
|
||
var o ObjCOptimizationHeader
|
||
var readErr error
|
||
read := func(field any) {
|
||
if readErr != nil {
|
||
return
|
||
}
|
||
readErr = binary.Read(sr, f.ByteOrder, field)
|
||
}
|
||
read(&o.Version)
|
||
read(&o.Flags)
|
||
read(&o.HeaderInfoRoCacheOffset)
|
||
read(&o.HeaderInfoRwCacheOffset)
|
||
read(&o.SelectorHashTableCacheOffset)
|
||
read(&o.ClassHashTableCacheOffset)
|
||
read(&o.ProtocolHashTableCacheOffset)
|
||
read(&o.RelativeMethodSelectorBaseAddressOffset)
|
||
if readErr != nil {
|
||
return nil, fmt.Errorf("failed to read NEW objc optimization header: %v", readErr)
|
||
}
|
||
if o.Version >= 2 {
|
||
read(&o.SelectorStringsSize)
|
||
read(&o.Reserved)
|
||
if readErr != nil {
|
||
return nil, fmt.Errorf("failed to read v2 objc optimization header fields: %v", readErr)
|
||
}
|
||
}
|
||
// Warn on unrecognized versions or unexpected sizes
|
||
if o.Version > 2 {
|
||
log.Warnf("objc optimization header version %d is newer than supported (max 2); parsing may be incomplete", o.Version)
|
||
}
|
||
if o.Version == 1 && f.Headers[f.UUID].ObjcOptsSize != objcOptHeaderV1Size {
|
||
log.Warnf("objc optimization header v1 expected %d bytes, got %d", objcOptHeaderV1Size, f.Headers[f.UUID].ObjcOptsSize)
|
||
}
|
||
if o.Version == 2 && f.Headers[f.UUID].ObjcOptsSize != objcOptHeaderV2Size {
|
||
log.Warnf("objc optimization header v2 expected %d bytes, got %d", objcOptHeaderV2Size, f.Headers[f.UUID].ObjcOptsSize)
|
||
}
|
||
|
||
return &o, nil
|
||
}
|
||
}
|
||
// check for OLD objc optimizations
|
||
return f.getOptimizationsOld()
|
||
}
|
||
|
||
// GetAllHeaderRO dumps the header_ro from the optimized string hash
|
||
func (f *File) getHeaderInfoRO() (*objc_headeropt_ro_t, error) {
|
||
|
||
var off uint64
|
||
var u types.UUID
|
||
var hdr objc_headeropt_ro_t
|
||
|
||
opt, err := f.GetOptimizations()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
switch o := opt.(type) {
|
||
case *ObjCOptimizationHeader:
|
||
hdr.offset = o.GetHeaderInfoRoCacheOffset()
|
||
u, off, err = f.GetOffset(f.Headers[f.UUID].SharedRegionStart + o.GetHeaderInfoRoCacheOffset())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
sr := io.NewSectionReader(f.r[u], int64(off), 1<<63-1)
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Count); err != nil {
|
||
return nil, err
|
||
}
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Entsize); err != nil {
|
||
return nil, err
|
||
}
|
||
hdr.Headers = make([]header_info_ro, hdr.Count)
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Headers); err != nil {
|
||
return nil, err
|
||
}
|
||
case *ObjcOptT:
|
||
u, off, err = f.GetOffset(f.Headers[f.UUID].SharedRegionStart + f.Headers[f.UUID].ObjcOptsOffset + uint64(o.HeaderOptRoOffset))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
hdr.offset = off
|
||
sr := io.NewSectionReader(f.r[u], 0, 1<<63-1)
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Count); err != nil {
|
||
return nil, err
|
||
}
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Entsize); err != nil {
|
||
return nil, err
|
||
}
|
||
hdr.Headers = make([]header_info_ro, hdr.Count)
|
||
if err := binary.Read(sr, f.ByteOrder, &hdr.Headers); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
return &hdr, nil
|
||
}
|
||
|
||
func (f *File) getSelectorStringHash() (*StringHash, *types.UUID, error) {
|
||
|
||
var off uint64
|
||
var u types.UUID
|
||
var shash StringHash
|
||
|
||
opt, err := f.GetOptimizations()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
hdr, err := f.getHeaderInfoRO()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
switch o := opt.(type) {
|
||
case *ObjCOptimizationHeader:
|
||
u, off, err = f.GetOffset(f.Headers[f.UUID].SharedRegionStart + o.SelectorHashTableOffset(0))
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
shash = StringHash{Type: selopt, FileOffset: int64(off), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(off), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
case *ObjcOptT:
|
||
shash = StringHash{Type: selopt, FileOffset: int64(opt.SelectorHashTableOffset(0)), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(opt.SelectorHashTableOffset(0)), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
}
|
||
|
||
return &shash, &u, nil
|
||
}
|
||
|
||
func (f *File) getClassStringHash() (*StringHash, *types.UUID, error) {
|
||
|
||
var off uint64
|
||
var u types.UUID
|
||
var shash StringHash
|
||
|
||
opt, err := f.GetOptimizations()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
hdr, err := f.getHeaderInfoRO()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
switch o := opt.(type) {
|
||
case *ObjCOptimizationHeader:
|
||
u, off, err = f.GetOffset(f.Headers[f.UUID].SharedRegionStart + o.ClassHashTableOffset(0))
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
shash = StringHash{Type: clsopt, FileOffset: int64(off), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(off), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
case *ObjcOptT:
|
||
shash = StringHash{Type: clsopt, FileOffset: int64(opt.ClassHashTableOffset(0)), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(opt.ClassHashTableOffset(0)), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
}
|
||
|
||
return &shash, &u, nil
|
||
}
|
||
|
||
func (f *File) getProtocolStringHash() (*StringHash, *types.UUID, error) {
|
||
|
||
var off uint64
|
||
var u types.UUID
|
||
var shash StringHash
|
||
|
||
opt, err := f.GetOptimizations()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
hdr, err := f.getHeaderInfoRO()
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
|
||
switch o := opt.(type) {
|
||
case *ObjCOptimizationHeader:
|
||
u, off, err = f.GetOffset(f.Headers[f.UUID].SharedRegionStart + o.ProtocolHashTableOffset(0))
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
shash = StringHash{Type: clsopt, FileOffset: int64(off), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(off), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
case *ObjcOptT:
|
||
shash = StringHash{Type: clsopt, FileOffset: int64(opt.ProtocolHashTableOffset(0)), hdrRO: hdr, opt: opt}
|
||
if err = shash.Read(io.NewSectionReader(f.r[u], int64(opt.ProtocolHashTableOffset(0)), 1<<63-1)); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
}
|
||
|
||
return &shash, &u, nil
|
||
}
|
||
|
||
func (f *File) getObjcDylibMap(shash *StringHash) {
|
||
if shash.dylibMap == nil {
|
||
shash.dylibMap = make(map[uint16]string)
|
||
for _, image := range f.Images {
|
||
if idx, err := shash.hdrRO.FindElement(image.LoadAddress); err == nil {
|
||
shash.dylibMap[idx] = image.Name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (f *File) dumpOffsets(shash *StringHash, uuid types.UUID) {
|
||
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
for idx, ptr := range shash.Offsets {
|
||
if ptr != 0 {
|
||
sr.Seek(int64(int32(shash.FileOffset)+ptr), io.SeekStart)
|
||
s, err := bufio.NewReader(sr).ReadString('\x00')
|
||
if err != nil {
|
||
log.Errorf("failed to read selector name at %#x: %v", int32(shash.FileOffset)+ptr, err)
|
||
}
|
||
if len(shash.ObjectOffsets) > 0 {
|
||
log.Debug(shash.ObjectOffsets[idx].String())
|
||
if !shash.ObjectOffsets[idx].IsDuplicate() {
|
||
_, addr, err := f.GetCacheVMAddress(shash.ObjectOffsets[idx].ObjectCacheOffset())
|
||
if err != nil {
|
||
log.Errorf("failed to get cache vmaddr for object at cache vmoffset %#x: %v", shash.ObjectOffsets[idx].ObjectCacheOffset(), err)
|
||
}
|
||
if img, ok := shash.dylibMap[shash.ObjectOffsets[idx].DylibObjCIndex()]; ok {
|
||
fmt.Printf("%s: %s\t%s\n", symAddrColor("%#09x", addr), strings.Trim(s, "\x00"), symImageColor(filepath.Base(img)))
|
||
} else {
|
||
fmt.Printf("%s: %s\n", symAddrColor("%#09x", addr), strings.Trim(s, "\x00"))
|
||
}
|
||
} else {
|
||
for i := uint16(0); i < shash.ObjectOffsets[idx].DuplicateCount(); i++ {
|
||
_, addr, err := f.GetCacheVMAddress(shash.DuplicateOffsets[shash.ObjectOffsets[idx].DuplicateIndex()+uint64(i)].ObjectCacheOffset())
|
||
if err != nil {
|
||
log.Errorf("failed to get cache vmaddr for object at cache vmoffset %#x: %v", shash.ObjectOffsets[idx].ObjectCacheOffset(), err)
|
||
}
|
||
if img, ok := shash.dylibMap[shash.DuplicateOffsets[shash.ObjectOffsets[idx].DuplicateIndex()+uint64(i)].DylibObjCIndex()]; ok {
|
||
fmt.Printf(" %s: %s\t%s\n", symAddrColor("%#09x", addr), strings.Trim(s, "\x00"), symImageColor(filepath.Base(img)))
|
||
} else {
|
||
fmt.Printf(" %s: %s\n", symAddrColor("%#09x", addr), strings.Trim(s, "\x00"))
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
addr, _ := f.GetVMAddressForUUID(uuid, uint64(int32(shash.FileOffset)+ptr))
|
||
fmt.Printf("%s: %s\n", symAddrColor("%#09x", addr), strings.Trim(s, "\x00"))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
type objc_headeropt_ro_t struct {
|
||
offset uint64
|
||
Count uint32
|
||
Entsize uint32
|
||
Headers []header_info_ro
|
||
}
|
||
type header_info_ro struct {
|
||
// Note, this is no longer a pointer, but instead an offset to a pointer
|
||
// from this location.
|
||
MhdrOffset int64
|
||
|
||
// Note, this is no longer a pointer, but instead an offset to a pointer
|
||
// from this location.
|
||
InfoOffset int64
|
||
|
||
// Note, this is no longer a pointer, but instead an offset to a pointer
|
||
// from this location.
|
||
// This may not be present in old shared caches
|
||
DyldInfoOffset int64
|
||
|
||
// // Offset from this location to the non-lazy class list
|
||
// NlclslistOffset int64
|
||
// NlclslistCount uint64
|
||
|
||
// // Offset from this location to the non-lazy category list
|
||
// NlcatlistOffset int64
|
||
// NlcatlistCount uint64
|
||
|
||
// // Offset from this location to the category list
|
||
// CatlistOffset int64
|
||
// CatlistCount uint64
|
||
|
||
// // Offset from this location to the category list 2
|
||
// Catlist2Offset int64
|
||
// Catlist2Count uint64
|
||
}
|
||
|
||
func (h *objc_headeropt_ro_t) GetMachoHdrOffset(index int) (uint64, error) {
|
||
if h == nil {
|
||
return 0, fmt.Errorf("objc_headeropt_ro_t is nil")
|
||
}
|
||
if index > len(h.Headers) {
|
||
return 0, fmt.Errorf("index out of range")
|
||
}
|
||
return uint64(int64(h.offset) + 8 + int64(index*binary.Size(header_info_ro{})) + h.Headers[index].MhdrOffset), nil
|
||
}
|
||
func (h *objc_headeropt_ro_t) FindElement(addr uint64) (uint16, error) {
|
||
if h == nil {
|
||
return 0, fmt.Errorf("objc_headeropt_ro_t is nil")
|
||
}
|
||
for idx, hdr := range h.Headers {
|
||
calcAddr := uint64(int64(h.offset) + 8 + int64(idx*binary.Size(header_info_ro{})) + hdr.MhdrOffset)
|
||
if (0x180000000 + calcAddr) == addr { // FIXME: this is for arm64 and shouldn't be hardcoded
|
||
return uint16(idx), nil
|
||
}
|
||
}
|
||
return 0, fmt.Errorf("failed to find element for address %#x", addr)
|
||
}
|
||
func (h *objc_headeropt_ro_t) GetObjcInfoOffset(index int) (uint64, error) {
|
||
if h == nil {
|
||
return 0, fmt.Errorf("objc_headeropt_ro_t is nil")
|
||
}
|
||
if index > len(h.Headers) {
|
||
return 0, fmt.Errorf("index out of range")
|
||
}
|
||
return uint64(int64(h.offset) + 8 + int64(unsafe.Offsetof(h.Headers[index].InfoOffset)) + int64(index*binary.Size(header_info_ro{})) + h.Headers[index].InfoOffset), nil
|
||
}
|
||
func (h *objc_headeropt_ro_t) GetDyldInfoOffset(index int) (uint64, error) {
|
||
if h == nil {
|
||
return 0, fmt.Errorf("objc_headeropt_ro_t is nil")
|
||
}
|
||
if index > len(h.Headers) {
|
||
return 0, fmt.Errorf("index out of range")
|
||
}
|
||
return uint64(int64(h.offset) + 8 + int64(unsafe.Offsetof(h.Headers[index].DyldInfoOffset)) + int64(index*binary.Size(header_info_ro{})) + h.Headers[index].DyldInfoOffset), nil
|
||
}
|
||
|
||
type objc_headeropt_rw_t struct {
|
||
Count uint32
|
||
Entsize uint32
|
||
Headers []header_info_rw
|
||
}
|
||
type header_info_rw uint64
|
||
|
||
func (rw header_info_rw) IsLoaded() bool {
|
||
return types.ExtractBits(uint64(rw), 0, 1) == 1
|
||
}
|
||
func (rw header_info_rw) AllClassesRealized() bool {
|
||
return types.ExtractBits(uint64(rw), 1, 1) == 1
|
||
}
|
||
func (rw header_info_rw) Next() uint64 {
|
||
return types.ExtractBits(uint64(rw), 62, 2)
|
||
}
|
||
|
||
// func (f *File) dumpHeaderROOffsets(offsets []int32, fileOffset int64) {
|
||
// sort.Slice(offsets, func(i, j int) bool { return offsets[i] < offsets[j] })
|
||
// sr := io.NewSectionReader(f.r[f.UUID], 0, 1<<63-1)
|
||
// for _, ptr := range offsets {
|
||
// if ptr != 0 {
|
||
// // sr.Seek(int64(int32(fileOffset)+ptr), io.SeekStart)
|
||
// sr.Seek(int64(int32(fileOffset)), io.SeekStart)
|
||
// var opt objc_headeropt_ro_t
|
||
|
||
// // off, err = f.GetOffset(f.SlideInfo.SlidePointer(classPtr.DataVMAddrAndFastFlags) & objc.FAST_DATA_MASK64)
|
||
// // if err != nil {
|
||
// // return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
// // }
|
||
|
||
// // sr.Seek(int64(off), io.SeekStart)
|
||
// if err := binary.Read(sr, f.ByteOrder, &opt.Count); err != nil {
|
||
// log.Errorf("failed to read objc_headeropt_ro_t: %v", err)
|
||
// }
|
||
// if err := binary.Read(sr, f.ByteOrder, &opt.Entsize); err != nil {
|
||
// log.Errorf("failed to read objc_headeropt_ro_t: %v", err)
|
||
// }
|
||
// opt.Headers = make([]header_info, int(opt.Count))
|
||
// if err := binary.Read(sr, f.ByteOrder, &opt.Headers); err != nil {
|
||
// log.Errorf("failed to read objc_headeropt_ro_t: %v", err)
|
||
// }
|
||
|
||
// addr, _ := f.GetVMAddressForUUID(f.UUID,uint64(int32(fileOffset) + ptr))
|
||
// fmt.Printf(" 0x%x: %#v\n", addr, opt)
|
||
// }
|
||
|
||
// }
|
||
// }
|
||
|
||
type objHashMap struct {
|
||
Dylib string
|
||
Name string
|
||
}
|
||
|
||
func (f *File) offsetsToMap(shash *StringHash, uuid types.UUID) map[uint64]objHashMap {
|
||
|
||
objcMap := make(map[uint64]objHashMap)
|
||
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
|
||
for idx, ptr := range shash.Offsets {
|
||
if ptr != 0 {
|
||
sr.Seek(int64(int32(shash.FileOffset)+ptr), io.SeekStart)
|
||
s, err := bufio.NewReader(sr).ReadString('\x00')
|
||
if err != nil {
|
||
log.Errorf("failed to read objc name at %#x: %v", int32(shash.FileOffset)+ptr, err)
|
||
}
|
||
addr, err := f.GetVMAddressForUUID(uuid, uint64(int32(shash.FileOffset)+ptr))
|
||
if err != nil {
|
||
log.Errorf("failed to get vmaddr for objc object at %#x: %v", int32(shash.FileOffset)+ptr, err)
|
||
}
|
||
s = strings.Trim(s, "\x00")
|
||
f.AddressToSymbol.Set(addr, s)
|
||
if len(shash.ObjectOffsets) > 0 {
|
||
if !shash.ObjectOffsets[idx].IsDuplicate() {
|
||
_, addr, err := f.GetCacheVMAddress(shash.ObjectOffsets[idx].ObjectCacheOffset())
|
||
if err != nil {
|
||
log.Errorf("failed to get cache vmaddr for object at cache vmoffset %#x: %v", shash.ObjectOffsets[idx].ObjectCacheOffset(), err)
|
||
} else {
|
||
if len(shash.dylibMap) > 0 {
|
||
objcMap[addr] = objHashMap{
|
||
Dylib: filepath.Base(shash.dylibMap[shash.ObjectOffsets[idx].DylibObjCIndex()]),
|
||
Name: s,
|
||
}
|
||
} else {
|
||
objcMap[addr] = objHashMap{Name: s}
|
||
}
|
||
f.AddressToSymbol.Set(addr, s)
|
||
}
|
||
} else {
|
||
for i := uint16(0); i < shash.ObjectOffsets[idx].DuplicateCount(); i++ {
|
||
_, addr, err := f.GetCacheVMAddress(shash.DuplicateOffsets[shash.ObjectOffsets[idx].DuplicateIndex()+uint64(i)].ObjectCacheOffset())
|
||
if err != nil {
|
||
log.Errorf("failed to get cache vmaddr for object at cache vmoffset %#x: %v", shash.ObjectOffsets[idx].ObjectCacheOffset(), err)
|
||
} else {
|
||
if len(shash.dylibMap) > 0 {
|
||
objcMap[addr] = objHashMap{
|
||
Dylib: filepath.Base(shash.dylibMap[shash.DuplicateOffsets[shash.ObjectOffsets[idx].DuplicateIndex()+uint64(i)].DylibObjCIndex()]),
|
||
Name: s,
|
||
}
|
||
} else {
|
||
objcMap[addr] = objHashMap{Name: s}
|
||
}
|
||
f.AddressToSymbol.Set(addr, s)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
objcMap[addr] = objHashMap{Name: s}
|
||
}
|
||
}
|
||
}
|
||
|
||
return objcMap
|
||
}
|
||
|
||
// GetAllSelectors is a dumb brute force way to get all the ObjC selector/class etc address
|
||
// by just dumping all the strings in the __OBJC_RO segment
|
||
// returns: map[sym]addr
|
||
func (f *File) GetAllObjCSelectors(print bool) (map[uint64]objHashMap, error) {
|
||
shash, uuid, err := f.getSelectorStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed read selector objc_stringhash_t: %v", err)
|
||
}
|
||
|
||
f.getObjcDylibMap(shash)
|
||
|
||
if print {
|
||
f.dumpOffsets(shash, *uuid)
|
||
}
|
||
|
||
return f.offsetsToMap(shash, *uuid), nil
|
||
}
|
||
|
||
func (f *File) getStringHashAddresses(shash *StringHash, index uint32, uuid *types.UUID) ([]uint64, error) {
|
||
if len(shash.ObjectOffsets) > 0 {
|
||
if !shash.ObjectOffsets[index].IsDuplicate() {
|
||
_, addr, err := f.GetCacheVMAddress(shash.ObjectOffsets[index].ObjectCacheOffset())
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get cache vmaddr for objc type at cache vmoffset %#x: %v", shash.ObjectOffsets[index].ObjectCacheOffset(), err)
|
||
}
|
||
return []uint64{addr}, nil
|
||
} else {
|
||
var addrs []uint64
|
||
for i := uint16(0); i < shash.ObjectOffsets[index].DuplicateCount(); i++ {
|
||
_, addr, err := f.GetCacheVMAddress(shash.DuplicateOffsets[shash.ObjectOffsets[index].DuplicateIndex()+uint64(i)].ObjectCacheOffset())
|
||
if err != nil {
|
||
log.Errorf("failed to get cache vmaddr for objc type at cache vmoffset %#x: %v", shash.ObjectOffsets[index].ObjectCacheOffset(), err)
|
||
} else {
|
||
addrs = append(addrs, addr)
|
||
}
|
||
}
|
||
addrs = utils.Unique(addrs)
|
||
return addrs, nil
|
||
}
|
||
}
|
||
|
||
addr, err := f.GetVMAddressForUUID(*uuid, uint64(shash.FileOffset+int64(shash.Offsets[index])))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed get address for objc type: %w", err)
|
||
}
|
||
|
||
return []uint64{addr}, nil
|
||
}
|
||
|
||
// GetSelectorAddress returns a selector addresses
|
||
func (f *File) GetSelectorAddresses(selector string) ([]uint64, error) {
|
||
shash, uuid, err := f.getSelectorStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed get selector objc_stringhash_t for %s: %v", selector, err)
|
||
}
|
||
|
||
idx, err := shash.getIndex(selector)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed get selector address for %s", selector)
|
||
}
|
||
|
||
return f.getStringHashAddresses(shash, idx, uuid)
|
||
}
|
||
|
||
// GetAllClasses dumps the classes from the optimized string hash
|
||
func (f *File) GetAllObjCClasses(print bool) (map[uint64]objHashMap, error) {
|
||
shash, uuid, err := f.getClassStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed read class objc_stringhash_t: %v", err)
|
||
}
|
||
|
||
f.getObjcDylibMap(shash)
|
||
|
||
if print {
|
||
f.dumpOffsets(shash, *uuid)
|
||
}
|
||
|
||
return f.offsetsToMap(shash, *uuid), nil
|
||
}
|
||
|
||
// GetClassAddress returns a class addresses
|
||
func (f *File) GetClassAddresses(class string) ([]uint64, error) {
|
||
shash, uuid, err := f.getClassStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed read class objc_stringhash_t: %v", err)
|
||
}
|
||
|
||
idx, err := shash.getIndex(class)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed get class address for %s: %v", class, err)
|
||
}
|
||
|
||
return f.getStringHashAddresses(shash, idx, uuid)
|
||
}
|
||
|
||
// GetAllProtocols dumps the protols from the optimized string hash
|
||
func (f *File) GetAllObjCProtocols(print bool) (map[uint64]objHashMap, error) {
|
||
shash, uuid, err := f.getProtocolStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed read protocol objc_stringhash_t: %v", err)
|
||
}
|
||
|
||
f.getObjcDylibMap(shash)
|
||
|
||
if print {
|
||
f.dumpOffsets(shash, *uuid)
|
||
}
|
||
|
||
return f.offsetsToMap(shash, *uuid), nil
|
||
}
|
||
|
||
// GetProtocolAddress returns a protocol addresses
|
||
func (f *File) GetProtocolAddresses(protocol string) ([]uint64, error) {
|
||
shash, uuid, err := f.getProtocolStringHash()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed read protocol objc_stringhash_t: %v", err)
|
||
}
|
||
|
||
idx, err := shash.getIndex(protocol)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed get protocol index for %s: %v", protocol, err)
|
||
}
|
||
|
||
return f.getStringHashAddresses(shash, idx, uuid)
|
||
}
|
||
|
||
// ClassesForImage returns all of the Objective-C classes for a given image
|
||
func (f *File) ClassesForImage(imageNames ...string) error {
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
image.ObjC.ClassRefs, err = m.GetObjCClassReferences()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for ptr, class := range image.ObjC.ClassRefs {
|
||
if len(class.Name) > 0 {
|
||
f.AddressToSymbol.Set(class.ClassPtr, fmt.Sprintf("class_%s", class.Name))
|
||
if sym, ok := f.AddressToSymbol.Get(ptr); ok {
|
||
if len(sym) < len(class.Name) {
|
||
f.AddressToSymbol.Set(ptr, class.Name)
|
||
}
|
||
} else {
|
||
f.AddressToSymbol.Set(ptr, class.Name)
|
||
}
|
||
}
|
||
}
|
||
|
||
image.ObjC.SuperRefs, err = m.GetObjCSuperReferences()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for ptr, class := range image.ObjC.SuperRefs {
|
||
if len(class.Name) > 0 {
|
||
f.AddressToSymbol.Set(class.ClassPtr, fmt.Sprintf("class_%s", class.Name))
|
||
if sym, ok := f.AddressToSymbol.Get(ptr); ok {
|
||
if len(sym) < len(class.Name) {
|
||
f.AddressToSymbol.Set(ptr, class.Name)
|
||
}
|
||
} else {
|
||
f.AddressToSymbol.Set(ptr, class.Name)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CategoriesForImage returns all of the Objective-C categories for a given image
|
||
func (f *File) CategoriesForImage(imageNames ...string) error {
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
cats, err := m.GetObjCCategories()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, cat := range cats {
|
||
if len(cat.Name) > 0 {
|
||
f.AddressToSymbol.Set(cat.VMAddr, fmt.Sprintf("cat_%s", cat.Name))
|
||
if sym, ok := f.AddressToSymbol.Get(cat.VMAddr); ok {
|
||
if len(sym) < len(cat.Name) {
|
||
f.AddressToSymbol.Set(cat.VMAddr, cat.Name)
|
||
}
|
||
} else {
|
||
f.AddressToSymbol.Set(cat.VMAddr, cat.Name)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ProtocolsForImage returns all of the Objective-C protocols for a given image
|
||
func (f *File) ProtocolsForImage(imageNames ...string) error {
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetPartialMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO %v", image.Name, err)
|
||
}
|
||
image.ObjC.ProtoRefs, err = m.GetObjCProtoReferences()
|
||
if err != nil {
|
||
if !errors.Is(err, macho.ErrObjcSectionNotFound) {
|
||
return fmt.Errorf("failed to get protocol references for image %s: %v", image.Name, err)
|
||
}
|
||
}
|
||
for k, v := range image.ObjC.ProtoRefs {
|
||
f.AddressToSymbol.Set(v.Ptr, v.Name)
|
||
f.AddressToSymbol.Set(k, fmt.Sprintf("proto_%s", v.Name))
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SelectorsForImage returns all of the Objective-C selectors for a given image
|
||
func (f *File) SelectorsForImage(imageNames ...string) error {
|
||
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
defer m.Close()
|
||
|
||
image.ObjC.SelRefs, err = m.GetObjCSelectorReferences()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for ptr, sel := range image.ObjC.SelRefs {
|
||
if len(sel.Name) > 0 {
|
||
f.AddressToSymbol.Set(ptr, fmt.Sprintf("sel_%s", sel.Name))
|
||
if sym, ok := f.AddressToSymbol.Get(sel.VMAddr); ok {
|
||
if len(sym) < len(sel.Name) {
|
||
f.AddressToSymbol.Set(sel.VMAddr, sel.Name)
|
||
}
|
||
} else {
|
||
f.AddressToSymbol.Set(sel.VMAddr, sel.Name)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetAllObjcMethods parses all the ObjC method lists in the cache dylibs
|
||
func (f *File) GetAllObjcMethods() error {
|
||
return f.MethodsForImage()
|
||
}
|
||
|
||
// MethodsForImage returns all of the Objective-C methods for a given image
|
||
func (f *File) MethodsForImage(imageNames ...string) error {
|
||
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
image.ObjC.Methods, err = m.GetObjCMethodLists()
|
||
if err != nil {
|
||
if errors.Is(err, macho.ErrObjcSectionNotFound) {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("failed to get objc methods for %s: %v", image.Name, err)
|
||
}
|
||
|
||
for _, meth := range image.ObjC.Methods {
|
||
if len(meth.Name) > 0 {
|
||
if sym, ok := f.AddressToSymbol.Get(meth.ImpVMAddr); ok {
|
||
if len(sym) < len(meth.Name) {
|
||
f.AddressToSymbol.Set(meth.ImpVMAddr, meth.Name)
|
||
}
|
||
} else {
|
||
f.AddressToSymbol.Set(meth.ImpVMAddr, meth.Name)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ImpCachesForImage dumps all of the Objective-C imp caches for a given image
|
||
func (f *File) ImpCachesForImage(imageNames ...string) error {
|
||
var selectorStringVMAddrStart uint64
|
||
var selectorStringVMAddrEnd uint64
|
||
|
||
image, err := f.Image("/usr/lib/libobjc.A.dylib")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
libObjC, err := image.GetMacho()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
symaddr, err := libObjC.FindSymbolAddress("_objc_opt_preopt_caches_version")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
impCachesVersion, err := f.ReadPointerAtAddress(symaddr)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if sec := libObjC.Section("__DATA_CONST", "__objc_scoffs"); sec != nil {
|
||
uuid, off, err := f.GetOffset(sec.Addr)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
|
||
dat := make([]byte, sec.Size)
|
||
if _, err := f.r[uuid].ReadAt(dat, int64(off)); err != nil {
|
||
return fmt.Errorf("failed to read %s.%s data: %v", sec.Seg, sec.Name, err)
|
||
}
|
||
|
||
optOffsets := make([]uint64, sec.Size/uint64(binary.Size(uint64(0))))
|
||
if err := binary.Read(bytes.NewReader(dat), f.ByteOrder, &optOffsets); err != nil {
|
||
return err
|
||
}
|
||
|
||
selectorStringIndex := 0
|
||
if impCachesVersion > 1 {
|
||
selectorStringIndex = 1
|
||
}
|
||
|
||
selectorStringVMAddrStart = f.SlideInfo.SlidePointer(optOffsets[selectorStringIndex])
|
||
selectorStringVMAddrEnd = f.SlideInfo.SlidePointer(optOffsets[selectorStringIndex+1])
|
||
// inlinedSelectorsVMAddrStart = scoffs[2]
|
||
// inlinedSelectorsVMAddrEnd = scoffs[3]
|
||
} else {
|
||
return fmt.Errorf("unable to find __DATA_CONST.__objc_scoffs")
|
||
}
|
||
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
image.ObjC.ClassRefs, err = m.GetObjCClassReferences()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, c := range image.ObjC.ClassRefs {
|
||
|
||
if f.SlideInfo.SlidePointer(c.MethodCacheProperties) > 0 {
|
||
uuid, off, err := f.GetOffset(c.MethodCacheProperties)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
|
||
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
|
||
if impCachesVersion < 3 {
|
||
var impCache objc.ImpCacheV1
|
||
if err := binary.Read(sr, f.ByteOrder, &impCache.ImpCacheHeaderV1); err != nil {
|
||
return fmt.Errorf("failed to read preopt_cache_t: %v", err)
|
||
}
|
||
|
||
impCache.Entries = make([]objc.ImpCacheEntryV1, impCache.Capacity())
|
||
if err := binary.Read(sr, f.ByteOrder, &impCache.Entries); err != nil {
|
||
return fmt.Errorf("failed to read []preopt_cache_entry_t: %v", err)
|
||
}
|
||
|
||
fmt.Printf("%s: (%s, buckets: %d)\n", c.Name, impCache.ImpCacheHeaderV1, impCache.Capacity())
|
||
|
||
for _, bucket := range impCache.Entries {
|
||
if bucket.SelOffset == 0xFFFFFFFF {
|
||
fmt.Printf(" - %#09x:\n", 0)
|
||
} else {
|
||
if selectorStringVMAddrStart+uint64(bucket.SelOffset) < selectorStringVMAddrEnd {
|
||
sel, err := f.GetCString(selectorStringVMAddrStart + uint64(bucket.SelOffset))
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get cstring for selector in imp-cache bucket")
|
||
}
|
||
fmt.Printf(" - %#09x: %s\n", c.ClassPtr-uint64(bucket.ImpOffset), sel)
|
||
} // TODO: handle the error case warn or crash?
|
||
}
|
||
}
|
||
} else {
|
||
var impCache objc.ImpCacheV2
|
||
if err := binary.Read(sr, f.ByteOrder, &impCache.ImpCacheHeaderV2); err != nil {
|
||
return fmt.Errorf("failed to read preopt_cache_t: %v", err)
|
||
}
|
||
|
||
impCache.Entries = make([]objc.ImpCacheEntryV2, impCache.Capacity())
|
||
if err := binary.Read(sr, f.ByteOrder, &impCache.Entries); err != nil {
|
||
return fmt.Errorf("failed to read []preopt_cache_entry_t: %v", err)
|
||
}
|
||
|
||
fmt.Printf("%s: (%s, buckets: %d)\n", c.Name, impCache.ImpCacheHeaderV2, impCache.Capacity())
|
||
|
||
for _, bucket := range impCache.Entries {
|
||
if bucket.GetSelOffset() == 0x3FFFFFF {
|
||
fmt.Printf(" - %#09x:\n", 0)
|
||
} else {
|
||
if selectorStringVMAddrStart+uint64(bucket.GetSelOffset()) < selectorStringVMAddrEnd {
|
||
sel, err := f.GetCString(selectorStringVMAddrStart + uint64(bucket.GetSelOffset()))
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get cstring for selector in imp-cache bucket")
|
||
}
|
||
fmt.Printf(" - %#09x: %s\n", c.ClassPtr-uint64(bucket.GetImpOffset()<<2), sel)
|
||
} // TODO: handle the error case warn or crash?
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
fmt.Printf("%s: empty\n", c.Name)
|
||
}
|
||
}
|
||
|
||
m.Close()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CFStringsForImage returns all of the Objective-C cfstrings for a given image
|
||
func (f *File) CFStringsForImage(imageNames ...string) error {
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
image.ObjC.CFStrings, err = m.GetCFStrings()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, cfstr := range image.ObjC.CFStrings {
|
||
if len(cfstr.Name) > 0 {
|
||
f.AddressToSymbol.Set(cfstr.Address, fmt.Sprintf("\"%s\"", cfstr.Name))
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetObjCClass parses an ObjC class at a given virtual memory address
|
||
func (f *File) GetObjCClass(vmaddr uint64) (*objc.Class, error) {
|
||
var classPtr objc.SwiftClassMetadata64
|
||
|
||
uuid, off, err := f.GetOffset(vmaddr)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
if err := binary.Read(sr, f.ByteOrder, &classPtr); err != nil {
|
||
return nil, fmt.Errorf("failed to read swift_class_metadata_t: %v", err)
|
||
}
|
||
|
||
// info, err := f.GetObjCClassInfo(f.SlideInfo.SlidePointer(classPtr.DataVMAddrAndFastFlags) & objc.FAST_DATA_MASK64)
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("failed to get class info at vmaddr: 0x%x; %v", classPtr.DataVMAddrAndFastFlags&objc.FAST_DATA_MASK64, err)
|
||
// }
|
||
|
||
var info objc.ClassRO64
|
||
|
||
off, err = f.GetOffsetForUUID(uuid, f.SlideInfo.SlidePointer(classPtr.DataVMAddrAndFastFlags)&objc.FAST_DATA_MASK64)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
if err := binary.Read(sr, f.ByteOrder, &info); err != nil {
|
||
return nil, fmt.Errorf("failed to read class_ro_t: %v", err)
|
||
}
|
||
|
||
name, err := f.GetCString(f.SlideInfo.SlidePointer(info.NameVMAddr))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read cstring: %v", err)
|
||
}
|
||
|
||
// var methods []objc.Method
|
||
// if info.BaseMethodsVMAddr > 0 {
|
||
// methods, err = f.GetObjCMethods(f.SlideInfo.SlidePointer(info.BaseMethodsVMAddr))
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("failed to get methods at vmaddr: 0x%x; %v", info.BaseMethodsVMAddr, err)
|
||
// }
|
||
// }
|
||
|
||
// var prots []objc.Protocol
|
||
// if info.BaseProtocolsVMAddr > 0 {
|
||
// prots, err = f.parseObjcProtocolList(info.BaseProtocolsVMAddr)
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("failed to read protocols vmaddr: %v", err)
|
||
// }
|
||
// }
|
||
|
||
// var ivars []objc.Ivar
|
||
// if info.IvarsVMAddr > 0 {
|
||
// ivars, err = f.GetObjCIvars(f.SlideInfo.SlidePointer(info.IvarsVMAddr))
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("failed to get ivars at vmaddr: 0x%x; %v", info.IvarsVMAddr, err)
|
||
// }
|
||
// }
|
||
|
||
// var props []objc.Property
|
||
// if info.BasePropertiesVMAddr > 0 {
|
||
// props, err = f.GetObjCProperties(f.SlideInfo.SlidePointer(info.BasePropertiesVMAddr))
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("failed to get props at vmaddr: 0x%x; %v", info.BasePropertiesVMAddr, err)
|
||
// }
|
||
// }
|
||
|
||
// superClass := &objc.Class{Name: "<ROOT>"}
|
||
// if classPtr.SuperclassVMAddr > 0 {
|
||
// if !info.Flags.IsRoot() {
|
||
// superClass, err = f.GetObjCClass(f.SlideInfo.SlidePointer(classPtr.SuperclassVMAddr))
|
||
// if err != nil {
|
||
// bindName, err := f.GetBindName(classPtr.SuperclassVMAddr)
|
||
// if err == nil {
|
||
// superClass = &objc.Class{Name: strings.TrimPrefix(bindName, "_OBJC_CLASS_$_")}
|
||
// } else {
|
||
// return nil, fmt.Errorf("failed to read super class objc_class_t at vmaddr: 0x%x; %v", vmaddr, err)
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
isaClass := &objc.Class{}
|
||
var cMethods []objc.Method
|
||
if classPtr.IsaVMAddr > 0 {
|
||
if !info.Flags.IsMeta() {
|
||
isaClass, err = f.GetObjCClass(f.SlideInfo.SlidePointer(classPtr.IsaVMAddr))
|
||
if err != nil {
|
||
// bindName, err := f.GetBindName(classPtr.IsaVMAddr)
|
||
// if err == nil {
|
||
// isaClass = &objc.Class{Name: strings.TrimPrefix(bindName, "_OBJC_CLASS_$_")}
|
||
// } else {
|
||
// return nil, fmt.Errorf("failed to read super class objc_class_t at vmaddr: 0x%x; %v", vmaddr, err)
|
||
// }
|
||
} else {
|
||
if isaClass.ReadOnlyData.Flags.IsMeta() {
|
||
cMethods = isaClass.InstanceMethods
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return &objc.Class{
|
||
Name: name,
|
||
// SuperClass: superClass.Name,
|
||
Isa: isaClass.Name,
|
||
// InstanceMethods: methods,
|
||
ClassMethods: cMethods,
|
||
// Ivars: ivars,
|
||
// Props: props,
|
||
// Prots: prots,
|
||
ClassPtr: vmaddr,
|
||
IsaVMAddr: f.SlideInfo.SlidePointer(classPtr.IsaVMAddr),
|
||
SuperclassVMAddr: f.SlideInfo.SlidePointer(classPtr.SuperclassVMAddr),
|
||
MethodCacheBuckets: classPtr.MethodCacheBuckets,
|
||
MethodCacheProperties: classPtr.MethodCacheProperties,
|
||
DataVMAddr: f.SlideInfo.SlidePointer(classPtr.DataVMAddrAndFastFlags) & objc.FAST_DATA_MASK64,
|
||
IsSwiftLegacy: (classPtr.DataVMAddrAndFastFlags&objc.FAST_IS_SWIFT_LEGACY == 1),
|
||
IsSwiftStable: (classPtr.DataVMAddrAndFastFlags&objc.FAST_IS_SWIFT_STABLE == 1),
|
||
ReadOnlyData: info,
|
||
}, nil
|
||
}
|
||
|
||
func (f *File) GetObjCClassProtocolsAddrs(vmaddr uint64) ([]uint64, error) {
|
||
var addrs []uint64
|
||
|
||
if vmaddr == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
uuid, off, err := f.GetOffset(vmaddr)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
sr := io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
|
||
var classPtr objc.SwiftClassMetadata64
|
||
if err := binary.Read(sr, f.ByteOrder, &classPtr); err != nil {
|
||
return nil, fmt.Errorf("failed to read swift_class_metadata_t: %v", err)
|
||
}
|
||
|
||
uuid, off, err = f.GetOffset(f.SlideInfo.SlidePointer(classPtr.DataVMAddrAndFastFlags) & objc.FAST_DATA_MASK64)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
sr = io.NewSectionReader(f.r[uuid], 0, 1<<63-1)
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
|
||
var info objc.ClassRO64
|
||
if err := binary.Read(sr, f.ByteOrder, &info); err != nil {
|
||
return nil, fmt.Errorf("failed to read class_ro_t: %v", err)
|
||
}
|
||
|
||
if info.BaseProtocolsVMAddr == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
off, err = f.GetOffsetForUUID(uuid, f.SlideInfo.SlidePointer(info.BaseProtocolsVMAddr))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to convert vmaddr: %v", err)
|
||
}
|
||
|
||
sr.Seek(int64(off), io.SeekStart)
|
||
|
||
var protList objc.ProtocolList
|
||
if err := binary.Read(sr, f.ByteOrder, &protList.Count); err != nil {
|
||
return nil, fmt.Errorf("failed to read protocol_list_t count: %v", err)
|
||
}
|
||
|
||
protList.Protocols = make([]uint64, protList.Count)
|
||
if err := binary.Read(sr, f.ByteOrder, &protList.Protocols); err != nil {
|
||
return nil, fmt.Errorf("failed to read protocol_list_t prots: %v", err)
|
||
}
|
||
|
||
for _, prot := range protList.Protocols {
|
||
addrs = append(addrs, f.SlideInfo.SlidePointer(prot))
|
||
}
|
||
|
||
return addrs, nil
|
||
}
|
||
|
||
func (f *File) GetAllObjCStubs() error {
|
||
return f.GetObjCStubsForImage()
|
||
}
|
||
|
||
func (f *File) GetObjCStubsForImage(imageNames ...string) error {
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
m, err := image.GetMacho()
|
||
if err != nil {
|
||
return fmt.Errorf("failed get image %s as MachO: %v", image.Name, err)
|
||
}
|
||
|
||
image.ObjC.Stubs, err = m.GetObjCStubs(func(addr uint64, data []byte) (map[uint64]*objc.Stub, error) {
|
||
stubs := make(map[uint64]*objc.Stub)
|
||
addr2sel, err := disass.ParseStubsASM(data, addr, func(u uint64) (uint64, error) {
|
||
ptr, err := f.ReadPointerAtAddress(u)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return f.SlideInfo.SlidePointer(ptr), nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for addr, sel := range addr2sel {
|
||
if f.AddressToSymbol.GetValue(sel) != "_objc_msgSend" {
|
||
stubs[addr] = &objc.Stub{
|
||
Name: f.AddressToSymbol.GetValue(sel),
|
||
SelectorRef: sel,
|
||
}
|
||
}
|
||
}
|
||
return stubs, nil
|
||
})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for addr, stub := range image.ObjC.Stubs {
|
||
if len(stub.Name) > 0 {
|
||
f.AddressToSymbol.Set(addr, fmt.Sprintf("j__objc_msgSend(x0, \"%s\")", stub.Name))
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ParseAllObjc parses all the ObjC data in the cache and loads it into the symbol table
|
||
func (f *File) ParseAllObjc() error {
|
||
if _, err := f.GetAllObjCClasses(false); err != nil {
|
||
return fmt.Errorf("failed to parse objc classes: %v", err)
|
||
}
|
||
if err := f.GetAllObjcMethods(); err != nil { // TODO: should I put this back in? The same info is in the symbols
|
||
return fmt.Errorf("failed to parse objc methods: %v", err)
|
||
}
|
||
if _, err := f.GetAllObjCSelectors(false); err != nil {
|
||
return fmt.Errorf("failed to parse objc selectors: %v", err)
|
||
}
|
||
if _, err := f.GetAllObjCProtocols(false); err != nil {
|
||
return fmt.Errorf("failed to parse objc protocols: %v", err)
|
||
}
|
||
if err := f.GetAllObjCStubs(); err != nil {
|
||
return fmt.Errorf("failed to parse objc stubs: %v", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (f *File) ParseObjcForImage(imageNames ...string) error {
|
||
|
||
var images []*CacheImage
|
||
|
||
if len(imageNames) > 0 && len(imageNames[0]) > 0 {
|
||
for _, imageName := range imageNames {
|
||
image, err := f.Image(imageName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
images = append(images, image)
|
||
}
|
||
} else {
|
||
images = f.Images
|
||
}
|
||
|
||
for _, image := range images {
|
||
if err := f.CFStringsForImage(image.Name); err != nil {
|
||
return fmt.Errorf("failed to parse objc cfstrings for image %s: %v", filepath.Base(image.Name), err)
|
||
}
|
||
// TODO: add objc methods in the -[Class sel:] form
|
||
if err := f.MethodsForImage(image.Name); err != nil {
|
||
if !errors.Is(err, macho.ErrObjcSectionNotFound) {
|
||
return fmt.Errorf("failed to parse objc methods for image %s: %v", filepath.Base(image.Name), err)
|
||
}
|
||
}
|
||
if strings.Contains(image.Name, "libobjc.A.dylib") {
|
||
if _, err := f.GetAllObjCSelectors(false); err != nil {
|
||
return fmt.Errorf("failed to parse objc all selectors: %v", err)
|
||
}
|
||
} else {
|
||
if err := f.SelectorsForImage(image.Name); err != nil {
|
||
return fmt.Errorf("failed to parse objc selectors for image %s: %v", filepath.Base(image.Name), err)
|
||
}
|
||
}
|
||
if err := f.ClassesForImage(image.Name); err != nil {
|
||
return fmt.Errorf("failed to parse objc classes for image %s: %v", filepath.Base(image.Name), err)
|
||
}
|
||
if err := f.ProtocolsForImage(image.Name); err != nil {
|
||
return fmt.Errorf("failed to parse objc protocols for image %s: %v", filepath.Base(image.Name), err)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|