mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
826 lines
21 KiB
Go
826 lines
21 KiB
Go
package cpp
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/types"
|
|
)
|
|
|
|
var osMetaClassCtorNames = []string{
|
|
"__ZN11OSMetaClassC2EPKcPKS_j",
|
|
"__ZN11OSMetaClassC1EPKcPKS_j",
|
|
"__ZN11OSMetaClassC2EPKcPKS_jPP4zoneS1_19zone_create_flags_t",
|
|
"__ZN11OSMetaClassC1EPKcPKS_jPP4zoneS1_19zone_create_flags_t",
|
|
}
|
|
|
|
var cxaPureVirtualNames = []string{
|
|
"__cxa_pure_virtual",
|
|
"___cxa_pure_virtual",
|
|
}
|
|
|
|
var osMetaClassSeedNames = []string{
|
|
"IORegistryEntry",
|
|
"IOService",
|
|
"IOUserClient",
|
|
}
|
|
|
|
const (
|
|
errorLogMessage = "OSMetaClass: preModLoad() wasn't called for class %s (runtime internal error)."
|
|
cxaPureVirtualPanic = "__cxa_pure_virtual"
|
|
)
|
|
|
|
func (s *Scanner) resolveAnchors() error {
|
|
files := s.anchorFiles()
|
|
preferredFiles := s.preferredAnchorFiles()
|
|
|
|
for _, file := range files {
|
|
s.addCtorVariantsFromFile(file)
|
|
if s.cxaPureVirtual == 0 {
|
|
s.setPureVirtualFromSymbols(file)
|
|
}
|
|
if s.cxaPureVirtual == 0 {
|
|
s.setPureVirtualFromExports(file)
|
|
}
|
|
}
|
|
|
|
if s.cxaPureVirtual == 0 {
|
|
s.setPureVirtualFromExports(s.root)
|
|
}
|
|
if len(s.osMetaClassVariants) == 0 {
|
|
if err := s.findAnchorsViaIntersection(preferredFiles); err == nil && len(s.osMetaClassVariants) > 0 {
|
|
s.stats.setAnchorMode(anchorModePreferredFileStringFallback)
|
|
} else if err := s.findAnchorsViaIntersection(files); err == nil && len(s.osMetaClassVariants) > 0 {
|
|
s.stats.setAnchorMode(anchorModeGlobalStringFallback)
|
|
}
|
|
}
|
|
if s.cxaPureVirtual == 0 {
|
|
_ = s.findPureVirtualViaStrings(preferredFiles)
|
|
}
|
|
if s.cxaPureVirtual == 0 {
|
|
_ = s.findPureVirtualViaStrings(files)
|
|
}
|
|
if len(s.osMetaClassVariants) > 0 && s.cxaPureVirtual != 0 {
|
|
if s.stats.anchorMode == anchorModeUnknown {
|
|
s.stats.setAnchorMode(anchorModeSymbolExportSymtab)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if len(s.osMetaClassVariants) == 0 || s.cxaPureVirtual == 0 {
|
|
if err := s.findAnchorsViaLegacyStrings(preferredFiles); err == nil && len(s.osMetaClassVariants) > 0 && s.cxaPureVirtual != 0 {
|
|
s.stats.setAnchorMode(anchorModePreferredFileStringFallback)
|
|
}
|
|
}
|
|
if len(s.osMetaClassVariants) == 0 || s.cxaPureVirtual == 0 {
|
|
if err := s.findAnchorsViaLegacyStrings(files); err == nil && len(s.osMetaClassVariants) > 0 && s.cxaPureVirtual != 0 {
|
|
s.stats.setAnchorMode(anchorModeGlobalStringFallback)
|
|
}
|
|
}
|
|
if len(s.osMetaClassVariants) == 0 {
|
|
return fmt.Errorf("failed to resolve OSMetaClass constructor variants")
|
|
}
|
|
if s.cxaPureVirtual == 0 {
|
|
return fmt.Errorf("failed to resolve __cxa_pure_virtual")
|
|
}
|
|
if s.stats.anchorMode == anchorModeUnknown {
|
|
s.stats.setAnchorMode(anchorModeSymbolExportSymtab)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) addCtorVariantsFromFile(file *macho.File) {
|
|
for _, name := range osMetaClassCtorNames {
|
|
if addr, err := file.FindSymbolAddress(name); err == nil {
|
|
s.osMetaClassVariants[addr] = struct{}{}
|
|
}
|
|
if addr, err := file.FindSymbolAddress(name + ".stub"); err == nil {
|
|
s.osMetaClassVariants[addr] = struct{}{}
|
|
}
|
|
}
|
|
if exports, err := file.DyldExports(); err == nil {
|
|
for _, export := range exports {
|
|
if isOSMetaClassCtorName(export.Name) {
|
|
s.osMetaClassVariants[export.Address] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
if file.Symtab != nil {
|
|
for _, sym := range file.Symtab.Syms {
|
|
if isOSMetaClassCtorName(sym.Name) {
|
|
s.osMetaClassVariants[sym.Value] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) setPureVirtualFromSymbols(file *macho.File) {
|
|
for _, name := range cxaPureVirtualNames {
|
|
if addr, err := file.FindSymbolAddress(name); err == nil {
|
|
s.cxaPureVirtual = addr
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) setPureVirtualFromExports(file *macho.File) {
|
|
if file == nil {
|
|
return
|
|
}
|
|
if exports, err := file.DyldExports(); err == nil {
|
|
for _, export := range exports {
|
|
if strings.Contains(export.Name, "cxa_pure_virtual") {
|
|
s.cxaPureVirtual = export.Address
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) preferredAnchorFiles() []*macho.File {
|
|
files := make([]*macho.File, 0, 2)
|
|
seen := make(map[*macho.File]bool, 2)
|
|
if s.root != nil {
|
|
seen[s.root] = true
|
|
files = append(files, s.root)
|
|
}
|
|
if s.root != nil && s.root.FileHeader.Type == types.MH_FILESET {
|
|
for _, fs := range s.root.FileSets() {
|
|
if normalizeEntryID(fs.EntryID) != kernelBundleName {
|
|
continue
|
|
}
|
|
kernelFile, err := s.root.GetFileSetFileByName(fs.EntryID)
|
|
if err != nil || seen[kernelFile] {
|
|
break
|
|
}
|
|
seen[kernelFile] = true
|
|
files = append(files, kernelFile)
|
|
break
|
|
}
|
|
}
|
|
return files
|
|
}
|
|
|
|
func (s *Scanner) anchorFiles() []*macho.File {
|
|
files := make([]*macho.File, 0, len(s.targets)+1)
|
|
seen := make(map[*macho.File]bool, len(s.targets)+1)
|
|
if s.root.FileHeader.Type == types.MH_FILESET {
|
|
for _, fs := range s.root.FileSets() {
|
|
if normalizeEntryID(fs.EntryID) != kernelBundleName {
|
|
continue
|
|
}
|
|
kernelFile, err := s.root.GetFileSetFileByName(fs.EntryID)
|
|
if err != nil {
|
|
break
|
|
}
|
|
seen[kernelFile] = true
|
|
files = append(files, kernelFile)
|
|
break
|
|
}
|
|
}
|
|
if !seen[s.root] {
|
|
seen[s.root] = true
|
|
files = append(files, s.root)
|
|
}
|
|
for _, target := range s.targets {
|
|
if seen[target.file] {
|
|
continue
|
|
}
|
|
seen[target.file] = true
|
|
files = append(files, target.file)
|
|
}
|
|
return files
|
|
}
|
|
|
|
func (s *Scanner) findAnchorsViaLegacyStrings(files []*macho.File) error {
|
|
var lastErr error
|
|
for _, file := range files {
|
|
if err := s.findAnchorsInFileViaStrings(file); err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
if len(s.osMetaClassVariants) > 0 && s.cxaPureVirtual != 0 {
|
|
return nil
|
|
}
|
|
}
|
|
if lastErr == nil {
|
|
lastErr = fmt.Errorf("string-xref anchor fallback did not resolve required anchors")
|
|
}
|
|
return lastErr
|
|
}
|
|
|
|
func (s *Scanner) findAnchorsInFileViaStrings(file *macho.File) error {
|
|
strs, err := file.GetCStrings()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errorStrAddr uint64
|
|
var panicStrAddr uint64
|
|
for _, str2addr := range strs {
|
|
for str, addr := range str2addr {
|
|
switch str {
|
|
case errorLogMessage:
|
|
errorStrAddr = addr
|
|
case cxaPureVirtualPanic:
|
|
panicStrAddr = addr
|
|
}
|
|
}
|
|
if errorStrAddr != 0 && panicStrAddr != 0 {
|
|
break
|
|
}
|
|
}
|
|
if errorStrAddr == 0 && panicStrAddr == 0 {
|
|
return fmt.Errorf("required anchor strings not present in file")
|
|
}
|
|
|
|
funcs, err := s.functionsForFile(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
foundVariant := false
|
|
foundPure := s.cxaPureVirtual != 0
|
|
for _, fn := range funcs {
|
|
if errorStrAddr != 0 {
|
|
if referenced, err := s.functionReferencesAddressNoResolve(file, fn, errorStrAddr); err == nil && referenced {
|
|
s.osMetaClassVariants[fn.StartAddr] = struct{}{}
|
|
foundVariant = true
|
|
}
|
|
}
|
|
if panicStrAddr != 0 && s.cxaPureVirtual == 0 {
|
|
if referenced, err := s.functionReferencesAddressNoResolve(file, fn, panicStrAddr); err == nil && referenced {
|
|
s.cxaPureVirtual = fn.StartAddr
|
|
foundPure = true
|
|
}
|
|
}
|
|
}
|
|
if foundVariant || foundPure {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("no anchor xrefs found in file")
|
|
}
|
|
|
|
func (s *Scanner) findPureVirtualViaStrings(files []*macho.File) error {
|
|
for _, file := range files {
|
|
strs, err := file.GetCStrings()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var panicStrAddr uint64
|
|
for _, str2addr := range strs {
|
|
for str, addr := range str2addr {
|
|
if str == cxaPureVirtualPanic {
|
|
panicStrAddr = addr
|
|
break
|
|
}
|
|
}
|
|
if panicStrAddr != 0 {
|
|
break
|
|
}
|
|
}
|
|
if panicStrAddr == 0 {
|
|
continue
|
|
}
|
|
funcs, err := s.functionsForFile(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, fn := range funcs {
|
|
if referenced, err := s.functionReferencesAddressNoResolve(file, fn, panicStrAddr); err == nil && referenced {
|
|
s.cxaPureVirtual = fn.StartAddr
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("no pure virtual anchor xrefs found")
|
|
}
|
|
|
|
func collectSeedStringRefs(files []*macho.File) map[string]uint64Set {
|
|
refs := make(map[string]uint64Set, len(osMetaClassSeedNames))
|
|
for _, name := range osMetaClassSeedNames {
|
|
refs[name] = make(uint64Set)
|
|
}
|
|
for _, file := range files {
|
|
strs, err := file.GetCStrings()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, str2addr := range strs {
|
|
for str, addr := range str2addr {
|
|
if set, ok := refs[str]; ok {
|
|
set[addr] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return refs
|
|
}
|
|
|
|
func (s *Scanner) findAnchorsViaIntersection(files []*macho.File) error {
|
|
refsByName := collectSeedStringRefs(files)
|
|
var candidates uint64Set
|
|
for _, name := range osMetaClassSeedNames {
|
|
refs := refsByName[name]
|
|
if len(refs) == 0 {
|
|
return fmt.Errorf("failed to find string: %s", name)
|
|
}
|
|
current := make(uint64Set)
|
|
for _, file := range files {
|
|
s.collectConstructorTargetsForStringRefs(file, refs, candidates, current)
|
|
}
|
|
if len(current) == 0 {
|
|
return fmt.Errorf("no constructor candidates found for %s", name)
|
|
}
|
|
candidates = current
|
|
}
|
|
if len(candidates) == 0 {
|
|
return fmt.Errorf("no common constructor candidates found")
|
|
}
|
|
for addr := range candidates {
|
|
s.osMetaClassVariants[addr] = struct{}{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) collectConstructorTargetsForStringRefs(file *macho.File, refs uint64Set, prev uint64Set, out uint64Set) {
|
|
for _, sec := range file.Sections {
|
|
if sec == nil || sec.Size < 8 {
|
|
continue
|
|
}
|
|
if sec.Seg != "__TEXT_EXEC" && sec.Seg != "__TEXT" {
|
|
continue
|
|
}
|
|
data, err := s.readSectionData(file, sec)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
targets := collectConstructorTargetsForStringRefs(sec.Addr, data, refs, prev)
|
|
for target := range targets {
|
|
out[target] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Scanner) discoverAltConstructors(files []*macho.File) error {
|
|
if len(s.osMetaClassVariants) == 0 {
|
|
return nil
|
|
}
|
|
known := func() uint64Set {
|
|
out := make(uint64Set, len(s.osMetaClassVariants))
|
|
for addr := range s.osMetaClassVariants {
|
|
out[addr] = struct{}{}
|
|
}
|
|
return out
|
|
}
|
|
|
|
const maxRounds = 8
|
|
changed := true
|
|
for round := 0; changed && round < maxRounds; round++ {
|
|
changed = false
|
|
current := known()
|
|
for _, file := range files {
|
|
funcs, err := s.functionsForFile(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, fn := range funcs {
|
|
if s.isOSMetaClassVariant(fn.StartAddr) {
|
|
continue
|
|
}
|
|
data, err := s.functionDataFor(file, fn)
|
|
if err != nil || len(data) == 0 {
|
|
continue
|
|
}
|
|
if !functionCallsAnyAnchor(fn.StartAddr, data, func(addr uint64) bool { return hasUint64Set(current, addr) }) {
|
|
continue
|
|
}
|
|
if _, ok := findPassThroughConstructorTarget(fn.StartAddr, data, current); ok {
|
|
s.osMetaClassVariants[fn.StartAddr] = struct{}{}
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) expandBoundedOSMetaClassAliases(files []*macho.File) error {
|
|
if len(s.osMetaClassVariants) == 0 {
|
|
return nil
|
|
}
|
|
refs := make(uint64Set)
|
|
for _, file := range files {
|
|
index := s.buildPointerIndex(file)
|
|
for target := range s.osMetaClassVariants {
|
|
for _, slot := range index[target] {
|
|
refs[slot] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
if len(refs) == 0 {
|
|
return nil
|
|
}
|
|
for _, file := range files {
|
|
for _, sec := range file.Sections {
|
|
if sec == nil || sec.Size < 12 {
|
|
continue
|
|
}
|
|
if sec.Seg != "__TEXT_EXEC" && sec.Seg != "__TEXT" {
|
|
continue
|
|
}
|
|
data, err := s.readSectionData(file, sec)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for off := 0; off+12 <= len(data); off += 4 {
|
|
alias := sec.Addr + uint64(off)
|
|
if s.isOSMetaClassVariant(alias) {
|
|
continue
|
|
}
|
|
if _, ok := importStubReferenceTarget(alias, data[off:], refs); ok {
|
|
s.osMetaClassVariants[alias] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) functionReferencesAddressNoResolve(m *macho.File, fn types.Function, target uint64) (bool, error) {
|
|
data, err := s.functionDataFor(m, fn)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return rawWordReferencesAddress(fn.StartAddr, data, target, nil), nil
|
|
}
|
|
|
|
type rawRefRegState struct {
|
|
adrpBase uint64
|
|
computed uint64
|
|
loadAddr uint64
|
|
}
|
|
|
|
// rawWordReferencesAddress checks whether raw instruction bytes contain
|
|
// an address reference to target using stateful ADRP/ADD/LDR tracking.
|
|
func rawWordReferencesAddress(start uint64, data []byte, target uint64, resolve func(uint64) (uint64, bool)) bool {
|
|
var regs [31]rawRefRegState
|
|
for offset := 0; offset+4 <= len(data); offset += 4 {
|
|
pc := start + uint64(offset)
|
|
raw := readUint32At(data, offset)
|
|
switch {
|
|
case (raw & 0x9f000000) == 0x90000000: // ADRP Xd, #page
|
|
rd := int(raw & 0x1f)
|
|
if rd < 31 {
|
|
if addr, ok := decodeADRPImmediate(pc, raw); ok {
|
|
regs[rd] = rawRefRegState{adrpBase: addr}
|
|
} else {
|
|
regs[rd] = rawRefRegState{}
|
|
}
|
|
}
|
|
case (raw & 0x9f000000) == 0x10000000: // ADR Xd, #imm
|
|
rd := int(raw & 0x1f)
|
|
if rd < 31 {
|
|
immhi := int64((raw >> 5) & 0x7ffff)
|
|
immlo := int64((raw >> 29) & 0x3)
|
|
imm := (immhi << 2) | immlo
|
|
if imm&(1<<20) != 0 {
|
|
imm |= ^int64((1 << 21) - 1)
|
|
}
|
|
addr := uint64(int64(pc) + imm)
|
|
regs[rd] = rawRefRegState{computed: addr}
|
|
if addr == target {
|
|
return true
|
|
}
|
|
}
|
|
case (raw & 0xff800000) == 0x91000000: // ADD Xd, Xn, #imm12
|
|
rd := int(raw & 0x1f)
|
|
rn := int((raw >> 5) & 0x1f)
|
|
if rn < 31 {
|
|
imm12 := uint64((raw >> 10) & 0xfff)
|
|
if (raw>>22)&1 == 1 {
|
|
imm12 <<= 12
|
|
}
|
|
base := regs[rn].computed
|
|
if base == 0 {
|
|
base = regs[rn].adrpBase
|
|
}
|
|
if rd < 31 {
|
|
regs[rd] = rawRefRegState{}
|
|
}
|
|
if base != 0 {
|
|
addr := base + imm12
|
|
if rd < 31 {
|
|
regs[rd] = rawRefRegState{computed: addr}
|
|
}
|
|
if addr == target {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
case (raw & 0xffc00000) == 0xf9400000: // LDR Xt, [Xn, #uimm]
|
|
rt := int(raw & 0x1f)
|
|
rn := int((raw >> 5) & 0x1f)
|
|
if rn < 31 {
|
|
imm12 := uint64((raw>>10)&0xfff) * 8
|
|
base := regs[rn].computed
|
|
if base == 0 {
|
|
base = regs[rn].adrpBase
|
|
}
|
|
if rt < 31 {
|
|
regs[rt] = rawRefRegState{}
|
|
}
|
|
if base != 0 {
|
|
loadAddr := base + imm12
|
|
if rt < 31 {
|
|
regs[rt] = rawRefRegState{loadAddr: loadAddr}
|
|
}
|
|
if resolve != nil {
|
|
if ptr, ok := resolve(loadAddr); ok && ptr == target {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case (raw & 0xff000000) == 0x58000000,
|
|
(raw & 0xff000000) == 0x18000000: // literal LDR
|
|
rt := int(raw & 0x1f)
|
|
imm19 := int64((raw >> 5) & 0x7ffff)
|
|
if imm19&(1<<18) != 0 {
|
|
imm19 |= ^int64((1 << 19) - 1)
|
|
}
|
|
loadAddr := uint64(int64(pc) + (imm19 << 2))
|
|
if rt < 31 {
|
|
regs[rt] = rawRefRegState{loadAddr: loadAddr}
|
|
}
|
|
if resolve != nil {
|
|
if ptr, ok := resolve(loadAddr); ok && ptr == target {
|
|
return true
|
|
}
|
|
}
|
|
case (raw >> 26) == 0b100101: // BL
|
|
if dest, ok := decodeBLTarget(pc, raw); ok && dest == target {
|
|
return true
|
|
}
|
|
case (raw >> 26) == 0b000101: // B
|
|
if dest, ok := decodeBTarget(pc, raw); ok && dest == target {
|
|
return true
|
|
}
|
|
case (raw & 0xffe0ffe0) == 0xaa0003e0: // MOV Xd, Xm
|
|
rd := int(raw & 0x1f)
|
|
rm := int((raw >> 16) & 0x1f)
|
|
if rd < 31 {
|
|
if rm < 31 {
|
|
regs[rd] = regs[rm]
|
|
} else {
|
|
regs[rd] = rawRefRegState{}
|
|
}
|
|
}
|
|
case (raw & 0xff800000) == 0xd2800000: // MOVZ
|
|
rd := int(raw & 0x1f)
|
|
if rd < 31 {
|
|
regs[rd] = rawRefRegState{}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isOSMetaClassCtorName(name string) bool {
|
|
name = strings.TrimPrefix(name, "_")
|
|
return strings.Contains(name, "OSMetaClassC1") || strings.Contains(name, "OSMetaClassC2") || strings.Contains(name, "OSMetaClass::OSMetaClass")
|
|
}
|
|
|
|
func (s *Scanner) isOSMetaClassVariant(addr uint64) bool {
|
|
_, ok := s.osMetaClassVariants[addr]
|
|
return ok
|
|
}
|
|
|
|
func (s *Scanner) collectCtorCandidates(target scanTarget) ([]ctorPath, error) {
|
|
modInit, err := s.collectModInitCtors(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// In MH_FILESET the kernel entry's function list includes
|
|
// infrastructure code (OSMetaClass impl, thunks) that calls
|
|
// OSMetaClass::OSMetaClass for non-kernel classes. Skip the
|
|
// direct-caller scan for the kernel entry; __mod_init_func
|
|
// is authoritative.
|
|
var direct []ctorPath
|
|
if !(target.entryID == kernelBundleName && s.root.FileHeader.Type == types.MH_FILESET) {
|
|
direct, err = s.collectDirectCallers(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
out := make([]ctorPath, 0, len(modInit)+len(direct))
|
|
seen := make(map[fileAddrKey]bool, len(modInit)+len(direct))
|
|
for _, path := range modInit {
|
|
key := fileAddrKey{file: path.owner, addr: path.fn.StartAddr}
|
|
if seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, path)
|
|
}
|
|
for _, path := range direct {
|
|
key := fileAddrKey{file: path.owner, addr: path.fn.StartAddr}
|
|
if seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, path)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Scanner) collectDirectCallers(target scanTarget) ([]ctorPath, error) {
|
|
funcs, err := s.functionsForFile(target.file)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
out := make([]ctorPath, 0, len(funcs)/8)
|
|
for _, fn := range funcs {
|
|
data, err := s.functionDataFor(target.file, fn)
|
|
if err != nil || len(data) < 4 {
|
|
continue
|
|
}
|
|
if functionCallsAnyAnchor(fn.StartAddr, data, s.isOSMetaClassVariant) {
|
|
out = append(out, ctorPath{fn: fn, owner: target.file, entryID: target.entryID})
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func functionCallsAnyAnchor(start uint64, data []byte, isAnchor func(uint64) bool) bool {
|
|
for offset := 0; offset+4 <= len(data); offset += 4 {
|
|
pc := start + uint64(offset)
|
|
raw := readUint32At(data, offset)
|
|
if target, ok := decodeBLTarget(pc, raw); ok && isAnchor(target) {
|
|
return true
|
|
}
|
|
if target, ok := decodeBTarget(pc, raw); ok && isAnchor(target) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Scanner) collectModInitCtors(target scanTarget) ([]ctorPath, error) {
|
|
ptrs, err := s.modInitPointers(target.file)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
out := make([]ctorPath, 0, len(ptrs))
|
|
seen := make(map[fileAddrKey]bool, len(ptrs))
|
|
for _, ptr := range ptrs {
|
|
path, ok, err := s.resolveWrapperChain(target.file, target.entryID, ptr)
|
|
if err != nil || !ok {
|
|
continue
|
|
}
|
|
if startFn, _, err := s.functionForAddrInAnyFile(target.file, ptr); err == nil && startFn.StartAddr != path.fn.StartAddr {
|
|
if ctx, ok := s.simulateWrapperContext(target.file, ptr, path.fn.StartAddr); ok {
|
|
path.preload = ctx
|
|
}
|
|
if wrapperContextEmpty(path.preload) {
|
|
if ctx, ok := s.recoverStaticWrapperContext(target.file, ptr, path.fn.StartAddr); ok && !wrapperContextEmpty(ctx) {
|
|
path.preload = ctx
|
|
}
|
|
}
|
|
}
|
|
key := fileAddrKey{file: path.owner, addr: path.fn.StartAddr}
|
|
if seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, path)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Scanner) modInitPointers(m *macho.File) ([]uint64, error) {
|
|
var sec *types.Section
|
|
if sec = m.Section("__DATA_CONST", "__mod_init_func"); sec == nil {
|
|
sec = m.Section("__DATA", "__mod_init_func")
|
|
}
|
|
if sec == nil {
|
|
return nil, fmt.Errorf("no __mod_init_func section")
|
|
}
|
|
|
|
data, err := s.readSectionData(m, sec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ptrs := make([]uint64, 0, len(data)/8)
|
|
for offset := 0; offset+8 <= len(data); offset += 8 {
|
|
addr := sec.Addr + uint64(offset)
|
|
ptr, ok := s.fallbackPointerAt(m, addr)
|
|
if !ok || ptr == 0 {
|
|
continue
|
|
}
|
|
ptrs = append(ptrs, ptr)
|
|
}
|
|
return ptrs, nil
|
|
}
|
|
|
|
type wrapperInspection struct {
|
|
direct bool
|
|
nextTargets []uint64
|
|
}
|
|
|
|
func (s *Scanner) resolveWrapperChain(startFile *macho.File, startEntry string, addr uint64) (ctorPath, bool, error) {
|
|
type queueItem struct {
|
|
file *macho.File
|
|
entry string
|
|
addr uint64
|
|
depth int
|
|
}
|
|
queue := []queueItem{{file: startFile, entry: startEntry, addr: addr, depth: 0}}
|
|
visited := make(map[fileAddrKey]bool, s.cfg.MaxWrapperDepth+1)
|
|
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
fn, owner, err := s.functionForAddrInAnyFile(current.file, current.addr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
key := fileAddrKey{file: owner, addr: fn.StartAddr}
|
|
if visited[key] {
|
|
continue
|
|
}
|
|
visited[key] = true
|
|
|
|
inspection, err := s.inspectFunction(owner, fn)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
entryID := s.entryForFile(owner)
|
|
if entryID == "" {
|
|
entryID = normalizeEntryID(current.entry)
|
|
}
|
|
if inspection.direct {
|
|
return ctorPath{fn: fn, owner: owner, entryID: entryID}, true, nil
|
|
}
|
|
|
|
if current.depth >= s.cfg.MaxWrapperDepth {
|
|
continue
|
|
}
|
|
for _, next := range inspection.nextTargets {
|
|
queue = append(queue, queueItem{file: owner, entry: entryID, addr: next, depth: current.depth + 1})
|
|
}
|
|
}
|
|
|
|
return ctorPath{}, false, nil
|
|
}
|
|
|
|
func (s *Scanner) inspectFunction(m *macho.File, fn types.Function) (wrapperInspection, error) {
|
|
data, err := s.functionDataFor(m, fn)
|
|
if err != nil {
|
|
return wrapperInspection{}, err
|
|
}
|
|
return inspectFunctionData(fn.StartAddr, data, s.isOSMetaClassVariant), nil
|
|
}
|
|
|
|
func inspectFunctionData(start uint64, data []byte, isAnchor func(uint64) bool) wrapperInspection {
|
|
nextTargets := make([]uint64, 0, 4)
|
|
lastNonNOP := uint64(0)
|
|
lastTarget := uint64(0)
|
|
for offset := 0; offset+4 <= len(data); offset += 4 {
|
|
pc := start + uint64(offset)
|
|
raw := readUint32At(data, offset)
|
|
|
|
if target, ok := decodeBLTarget(pc, raw); ok {
|
|
if isAnchor(target) {
|
|
return wrapperInspection{direct: true}
|
|
}
|
|
if target != 0 && target != start {
|
|
nextTargets = append(nextTargets, target)
|
|
lastTarget = target
|
|
}
|
|
}
|
|
if target, ok := decodeBTarget(pc, raw); ok && target != 0 && target != start {
|
|
nextTargets = append(nextTargets, target)
|
|
lastTarget = target
|
|
}
|
|
if !isArm64Nop(raw) {
|
|
lastNonNOP = pc
|
|
}
|
|
}
|
|
|
|
if len(nextTargets) == 0 && lastNonNOP != 0 && lastTarget != 0 {
|
|
nextTargets = append(nextTargets, lastTarget)
|
|
}
|
|
|
|
slices.Sort(nextTargets)
|
|
nextTargets = slices.Compact(nextTargets)
|
|
return wrapperInspection{nextTargets: nextTargets}
|
|
}
|