Files
ipsw/pkg/dyld/softlinks.go
blacktop 14457b7d96 feat: address sandbox research feedback
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.
2026-05-24 16:28:00 -06:00

1043 lines
28 KiB
Go

package dyld
import (
"fmt"
"maps"
"regexp"
"slices"
"sort"
"strings"
"github.com/blacktop/arm64-cgo/disassemble"
"github.com/blacktop/go-macho"
mtypes "github.com/blacktop/go-macho/types"
"github.com/blacktop/ipsw/internal/demangle"
"github.com/blacktop/ipsw/pkg/xref"
)
// ImportedSymbolNamesForImage returns imported symbol names for a cache image.
// It prefers Mach-O bind metadata and falls back to resolving stub pointer
// targets, which covers chained-cache images that no longer carry LC_DYLD_INFO.
func ImportedSymbolNamesForImage(image *CacheImage) ([]string, error) {
if image == nil {
return nil, fmt.Errorf("nil cache image")
}
var bindErr error
if m, err := image.GetPartialMacho(); err == nil {
if imports, err := m.ImportedSymbolNames(); err == nil {
return imports, nil
} else {
bindErr = err
}
} else {
bindErr = err
}
imports, err := importedSymbolNamesFromResolvedStubs(image)
if err != nil {
if bindErr != nil {
return nil, bindErr
}
return nil, err
}
if len(imports) == 0 && bindErr != nil {
return nil, bindErr
}
return imports, nil
}
func importedSymbolNamesFromResolvedStubs(image *CacheImage) ([]string, error) {
if image == nil || image.cache == nil {
return nil, nil
}
m, err := image.GetMacho()
if err != nil {
return nil, err
}
stubSlots := softLinkStubPointerSlots(m)
resolvedImages := make(map[*CacheImage]struct{})
seen := make(map[string]struct{})
for _, slot := range stubSlots {
ptr, err := image.cache.ReadPointerAtAddress(slot)
if err != nil || ptr == 0 {
continue
}
target := image.cache.SlideInfo.SlidePointer(ptr)
name := fallbackImportedSymbolName(softLinkResolvedTargetName(image.cache, target, resolvedImages))
if name == "" {
continue
}
seen[name] = struct{}{}
}
imports := make([]string, 0, len(seen))
for name := range seen {
imports = append(imports, name)
}
sort.Strings(imports)
return imports, nil
}
func fallbackImportedSymbolName(name string) string {
name = strings.TrimSpace(name)
name = strings.TrimPrefix(name, "j_")
return name
}
// SoftLinkConfig controls soft-link global extraction from a DSC image.
type SoftLinkConfig struct {
Image string
Filter string
}
// SoftLinkRecord describes one SOFT_LINK-style global and the nearby helper
// symbols that can be recovered from the image symbol tables.
type SoftLinkRecord struct {
Image string `json:"image"`
Symbol string `json:"symbol"`
GlobalAddr uint64 `json:"global_addr,omitempty"`
InitFuncAddr uint64 `json:"init_fn_addr,omitempty"`
OnceAddr uint64 `json:"once_addr,omitempty"`
FrameworkLibAddr uint64 `json:"framework_lib_addr,omitempty"`
GlobalName string `json:"global_name,omitempty"`
InitFuncName string `json:"init_fn_name,omitempty"`
OnceName string `json:"once_name,omitempty"`
FrameworkLibName string `json:"framework_lib_name,omitempty"`
}
type softLinkSymbol struct {
Name string
Address uint64
}
// SoftLinks returns softLink* globals for an image, with best-effort recovery
// of matching init, once, and framework-library helper symbols when names are
// present in the cache.
func SoftLinks(f *File, config SoftLinkConfig) ([]SoftLinkRecord, error) {
if f == nil {
return nil, fmt.Errorf("nil dyld cache")
}
if strings.TrimSpace(config.Image) == "" {
return nil, fmt.Errorf("--image is required")
}
filter, err := compileSoftLinkFilter(config.Filter)
if err != nil {
return nil, err
}
image, err := cacheImageByName(f, config.Image)
if err != nil {
return nil, fmt.Errorf("image not in DSC: %w", err)
}
symbols := softLinkSymbols(image)
records := softLinkRecordsFromSymbols(image.Name, symbols)
if len(records) == 0 || filter != nil {
if disasmRecords, err := softLinkRecordsFromDisassembly(image); err == nil {
records = mergeSoftLinkRecords(records, disasmRecords)
}
}
if filter != nil {
records = filterSoftLinkRecords(records, filter)
}
return records, nil
}
func compileSoftLinkFilter(pattern string) (*regexp.Regexp, error) {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
return nil, nil
}
filter, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("invalid --filter regex %q: %w", pattern, err)
}
return filter, nil
}
func softLinkSymbols(image *CacheImage) []softLinkSymbol {
if image == nil {
return nil
}
_ = image.ParseLocalSymbols(false)
_ = image.ParsePublicSymbols(false)
symbols := make([]softLinkSymbol, 0, len(image.LocalSymbols)+len(image.PublicSymbols))
seen := make(map[string]struct{})
add := func(name string, addr uint64) {
display := softLinkDisplayName(name)
if display == "" {
return
}
key := fmt.Sprintf("%016x:%s", addr, display)
if _, ok := seen[key]; ok {
return
}
seen[key] = struct{}{}
symbols = append(symbols, softLinkSymbol{Name: display, Address: addr})
}
for _, sym := range image.LocalSymbols {
add(sym.Name, sym.Value)
}
for _, sym := range image.PublicSymbols {
add(sym.Name, sym.Address)
}
sort.Slice(symbols, func(i, j int) bool {
if symbols[i].Name == symbols[j].Name {
return symbols[i].Address < symbols[j].Address
}
return symbols[i].Name < symbols[j].Name
})
return symbols
}
func softLinkDisplayName(name string) string {
name = strings.TrimSpace(demangle.Do(name, false, false))
name = strings.TrimPrefix(name, "_")
return name
}
func softLinkRecordsFromSymbols(imageName string, symbols []softLinkSymbol) []SoftLinkRecord {
recordsBySuffix := make(map[string]*SoftLinkRecord)
var helpers []softLinkSymbol
for _, sym := range symbols {
suffix, ok := softLinkGlobalSuffix(sym.Name)
if !ok {
helpers = append(helpers, sym)
continue
}
rec := recordsBySuffix[suffix]
if rec == nil {
rec = &SoftLinkRecord{
Image: imageName,
Symbol: suffix,
GlobalAddr: sym.Address,
GlobalName: sym.Name,
}
recordsBySuffix[suffix] = rec
continue
}
if rec.GlobalAddr == 0 || (sym.Address != 0 && sym.Address < rec.GlobalAddr) {
rec.GlobalAddr = sym.Address
rec.GlobalName = sym.Name
}
}
for suffix, rec := range recordsBySuffix {
for _, helper := range helpers {
applySoftLinkHelper(rec, helper, suffix)
}
}
records := make([]SoftLinkRecord, 0, len(recordsBySuffix))
for _, rec := range recordsBySuffix {
records = append(records, *rec)
}
sort.Slice(records, func(i, j int) bool {
if records[i].Symbol == records[j].Symbol {
return records[i].GlobalAddr < records[j].GlobalAddr
}
return records[i].Symbol < records[j].Symbol
})
return records
}
func applySoftLinkHelper(record *SoftLinkRecord, helper softLinkSymbol, suffix string) {
switch classifySoftLinkHelper(helper.Name, suffix) {
case "init":
if shouldReplaceSoftLinkHelper(record.InitFuncAddr, helper.Address) {
record.InitFuncAddr = helper.Address
record.InitFuncName = helper.Name
}
case "once":
if shouldReplaceSoftLinkHelper(record.OnceAddr, helper.Address) {
record.OnceAddr = helper.Address
record.OnceName = helper.Name
}
case "library":
if shouldReplaceSoftLinkHelper(record.FrameworkLibAddr, helper.Address) {
record.FrameworkLibAddr = helper.Address
record.FrameworkLibName = helper.Name
}
}
}
func softLinkGlobalSuffix(name string) (string, bool) {
short := softLinkShortName(name)
for _, prefix := range []string{"softLink", "softlink"} {
if strings.HasPrefix(short, prefix) && len(short) > len(prefix) {
suffix := strings.Trim(short[len(prefix):], "_")
if suffix != "" && !strings.EqualFold(suffix, "Library") && !strings.HasSuffix(suffix, "Library") {
return suffix, true
}
}
}
return "", false
}
func softLinkShortName(name string) string {
if idx := strings.LastIndex(name, "::"); idx >= 0 {
name = name[idx+len("::"):]
}
name = strings.TrimSuffix(name, "()")
return strings.TrimSpace(name)
}
func classifySoftLinkHelper(name, suffix string) string {
short := softLinkShortName(name)
lowerShort := strings.ToLower(short)
lowerName := strings.ToLower(name)
lowerSuffix := strings.ToLower(suffix)
switch {
case strings.Contains(lowerShort, "once") && strings.Contains(lowerName, lowerSuffix):
return "once"
case strings.HasPrefix(short, "init") && strings.Contains(lowerShort, lowerSuffix):
return "init"
case strings.Contains(lowerShort, "library") && softLinkFrameworkMatches(short, suffix):
return "library"
default:
return ""
}
}
func softLinkFrameworkMatches(helper, suffix string) bool {
framework := softLinkFrameworkPrefix(suffix)
if framework == "" {
return false
}
return strings.Contains(strings.ToLower(helper), strings.ToLower(framework))
}
func softLinkFrameworkPrefix(suffix string) string {
for idx := 1; idx < len(suffix); idx++ {
if suffix[idx] >= 'A' && suffix[idx] <= 'Z' && suffix[idx-1] >= 'a' && suffix[idx-1] <= 'z' {
return suffix[:idx]
}
}
return suffix
}
func shouldReplaceSoftLinkHelper(existing, candidate uint64) bool {
if existing == 0 {
return candidate != 0
}
if candidate == 0 {
return false
}
return candidate < existing
}
func filterSoftLinkRecords(records []SoftLinkRecord, filter *regexp.Regexp) []SoftLinkRecord {
if filter == nil {
return records
}
out := records[:0]
for _, record := range records {
if filter.MatchString(record.Symbol) ||
filter.MatchString(record.GlobalName) ||
filter.MatchString(record.InitFuncName) ||
filter.MatchString(record.OnceName) ||
filter.MatchString(record.FrameworkLibName) {
out = append(out, record)
}
}
return out
}
type softLinkCallTargets struct {
dlsym xref.TargetSet
dispatchOnce xref.TargetSet
}
type softLinkFunction struct {
start uint64
end uint64
instrs []xref.Instruction
}
type softLinkRegValue struct {
known bool
addr uint64
loadAddr uint64
}
func softLinkRecordsFromDisassembly(image *CacheImage) ([]SoftLinkRecord, error) {
m, err := image.GetMacho()
if err != nil {
return nil, err
}
targets, err := softLinkImportedCallTargets(image, m)
if err != nil || len(targets.dlsym) == 0 {
return nil, err
}
funcs, err := softLinkInstructionFunctions(m)
if err != nil {
return nil, err
}
return softLinkRecordsFromInstructionFunctions(
image.Name,
funcs,
targets,
image.cache.GetCString,
image.cache.ReadPointerAtAddress,
), nil
}
func softLinkImportedCallTargets(image *CacheImage, m *macho.File) (softLinkCallTargets, error) {
targets := softLinkCallTargets{
dlsym: xref.NewTargetSet(),
dispatchOnce: xref.NewTargetSet(),
}
stubSlots := softLinkStubPointerSlots(m)
var bindErr error
if binds, err := m.GetBindInfo(); err == nil {
bindSlots := make(map[uint64]string)
for _, bind := range binds {
name := strings.TrimPrefix(bind.Name, "_")
switch name {
case "dlsym", "dispatch_once":
bindSlots[bind.Start+bind.SegOffset] = name
}
}
softLinkAddTargetsFromBindSlots(targets, stubSlots, bindSlots)
} else {
bindErr = err
}
softLinkAddTargetsFromResolvedStubs(image, targets, stubSlots)
if len(targets.dlsym) == 0 && len(targets.dispatchOnce) == 0 && bindErr != nil {
return targets, bindErr
}
return targets, nil
}
func softLinkStubPointerSlots(m *macho.File) map[uint64]uint64 {
stubSlots := make(map[uint64]uint64)
for _, sec := range m.Sections {
if sec == nil || sec.Size == 0 || !sec.Flags.IsSymbolStubs() {
continue
}
data, err := sec.Data()
if err != nil {
continue
}
maps.Copy(stubSlots, xref.StubPointerSlotsFromInstructions(xref.Decode(data, sec.Addr)))
}
return stubSlots
}
func softLinkAddTargetsFromBindSlots(targets softLinkCallTargets, stubSlots map[uint64]uint64, bindSlots map[uint64]string) {
for stub, slot := range stubSlots {
softLinkAddCallTarget(targets, bindSlots[slot], stub)
}
}
func softLinkAddTargetsFromResolvedStubs(image *CacheImage, targets softLinkCallTargets, stubSlots map[uint64]uint64) {
if image == nil || image.cache == nil {
return
}
resolvedImages := make(map[*CacheImage]struct{})
for stub, slot := range stubSlots {
ptr, err := image.cache.ReadPointerAtAddress(slot)
if err != nil || ptr == 0 {
continue
}
target := image.cache.SlideInfo.SlidePointer(ptr)
name := softLinkResolvedTargetName(image.cache, target, resolvedImages)
softLinkAddCallTarget(targets, softLinkImportedTargetName(name), stub)
}
}
func softLinkAddCallTarget(targets softLinkCallTargets, name string, stub uint64) {
switch name {
case "dlsym":
targets.dlsym.Add(stub)
case "dispatch_once":
targets.dispatchOnce.Add(stub)
}
}
func softLinkResolvedTargetName(f *File, target uint64, resolvedImages map[*CacheImage]struct{}) string {
if name, ok := f.AddressToSymbol.Get(target); ok {
return name
}
img, err := f.GetImageContainingTextAddr(target)
if err != nil || img == nil {
return ""
}
if _, ok := resolvedImages[img]; !ok {
_ = img.ParsePublicSymbols(false)
resolvedImages[img] = struct{}{}
}
if name, ok := f.AddressToSymbol.Get(target); ok {
return name
}
return ""
}
func softLinkImportedTargetName(name string) string {
short := softLinkShortName(softLinkDisplayName(name))
short = strings.TrimPrefix(short, "j_")
short = strings.TrimPrefix(short, "_")
switch short {
case "dlsym", "dispatch_once":
return short
default:
return ""
}
}
func softLinkInstructionFunctions(m *macho.File) ([]softLinkFunction, error) {
sections := softLinkExecutableSections(m)
if len(sections) == 0 {
return nil, nil
}
funcs := m.GetFunctions()
if len(funcs) == 0 {
generated, err := m.GenerateFunctionStarts()
if err != nil {
return nil, err
}
funcs = generated
}
if len(funcs) == 0 {
return nil, nil
}
sort.Slice(funcs, func(i, j int) bool {
return funcs[i].StartAddr < funcs[j].StartAddr
})
out := make([]softLinkFunction, 0, len(funcs))
for _, sec := range sections {
data, err := sec.Data()
if err != nil {
continue
}
sectionInstrs := xref.Decode(data, sec.Addr)
for idx, fn := range funcs {
if fn.StartAddr < sec.Addr || fn.StartAddr >= sec.Addr+sec.Size {
continue
}
end := softLinkFunctionEnd(fn, funcs, idx, sec)
if end <= fn.StartAddr {
continue
}
startIdx := int((fn.StartAddr - sec.Addr) / 4)
endIdx := int((end - sec.Addr) / 4)
if startIdx < 0 || startIdx >= len(sectionInstrs) || endIdx <= startIdx {
continue
}
if endIdx > len(sectionInstrs) {
endIdx = len(sectionInstrs)
}
out = append(out, softLinkFunction{
start: fn.StartAddr,
end: end,
instrs: sectionInstrs[startIdx:endIdx],
})
}
}
return out, nil
}
func softLinkExecutableSections(m *macho.File) []*mtypes.Section {
sections := make([]*mtypes.Section, 0, len(m.Sections))
for _, sec := range m.Sections {
if sec == nil || sec.Size == 0 {
continue
}
if sec.Flags.IsPureInstructions() || sec.Flags.IsSomeInstructions() || sec.Name == "__text" {
sections = append(sections, sec)
}
}
return sections
}
func softLinkFunctionEnd(fn mtypes.Function, funcs []mtypes.Function, idx int, sec *mtypes.Section) uint64 {
sectionEnd := sec.Addr + sec.Size
end := fn.EndAddr
if end == 0 || end > sectionEnd {
end = sectionEnd
for next := idx + 1; next < len(funcs); next++ {
if funcs[next].StartAddr > fn.StartAddr && funcs[next].StartAddr <= sectionEnd {
end = funcs[next].StartAddr
break
}
}
}
return end
}
func softLinkRecordsFromInstructionFunctions(
imageName string,
funcs []softLinkFunction,
targets softLinkCallTargets,
readCString func(uint64) (string, error),
readPointer func(uint64) (uint64, error),
) []SoftLinkRecord {
recordsByKey := make(map[string]*SoftLinkRecord)
recordsByInit := make(map[uint64][]*SoftLinkRecord)
for _, fn := range funcs {
for idx := range fn.instrs {
target, ok := softLinkDirectCallTarget(&fn.instrs[idx].Inst)
if !ok || !targets.dlsym.Has(target) {
continue
}
record, ok := softLinkRecordFromDlsymCall(imageName, fn, idx, targets, readCString, readPointer)
if !ok {
continue
}
key := softLinkDisasmRecordKey(record)
merged := mergeSoftLinkRecord(recordsByKey[key], record)
recordsByKey[key] = merged
recordsByInit[merged.InitFuncAddr] = appendSoftLinkRecordPointer(recordsByInit[merged.InitFuncAddr], merged)
}
}
if len(recordsByKey) == 0 {
return nil
}
for _, fn := range funcs {
for idx := range fn.instrs {
target, ok := softLinkDirectCallTarget(&fn.instrs[idx].Inst)
if !ok || !targets.dispatchOnce.Has(target) {
continue
}
state := softLinkStateBefore(fn.instrs, idx, readPointer, 32)
initAddr := state[1].addr
if initAddr == 0 {
continue
}
for _, record := range recordsByInit[initAddr] {
if record.OnceAddr == 0 {
record.OnceAddr = state[0].addr
}
}
}
}
records := make([]SoftLinkRecord, 0, len(recordsByKey))
for _, record := range recordsByKey {
records = append(records, *record)
}
sort.Slice(records, func(i, j int) bool {
if records[i].Symbol == records[j].Symbol {
return records[i].InitFuncAddr < records[j].InitFuncAddr
}
return records[i].Symbol < records[j].Symbol
})
return records
}
func softLinkDisasmRecordKey(record SoftLinkRecord) string {
if record.GlobalAddr != 0 {
return fmt.Sprintf("global:%#x", record.GlobalAddr)
}
return fmt.Sprintf("%s:%#x", record.Symbol, record.InitFuncAddr)
}
func appendSoftLinkRecordPointer(records []*SoftLinkRecord, record *SoftLinkRecord) []*SoftLinkRecord {
if slices.Contains(records, record) {
return records
}
return append(records, record)
}
func softLinkRecordFromDlsymCall(
imageName string,
fn softLinkFunction,
callIdx int,
targets softLinkCallTargets,
readCString func(uint64) (string, error),
readPointer func(uint64) (uint64, error),
) (SoftLinkRecord, bool) {
state := softLinkStateBefore(fn.instrs, callIdx, readPointer, 64)
symbolAddr := state[1].addr
if symbolAddr == 0 {
return SoftLinkRecord{}, false
}
symbol, err := readCString(symbolAddr)
if err != nil || !validSoftLinkDlsymSymbol(symbol) {
return SoftLinkRecord{}, false
}
record := SoftLinkRecord{
Image: imageName,
Symbol: strings.TrimPrefix(symbol, "_"),
InitFuncAddr: fn.start,
}
if globalAddr, ok := softLinkStoreAfterCall(fn.instrs, callIdx, readPointer); ok {
record.GlobalAddr = globalAddr
}
if state[0].loadAddr != 0 {
record.FrameworkLibAddr = state[0].loadAddr
} else if helperAddr := softLinkLastInternalCall(fn.instrs, callIdx, targets); helperAddr != 0 {
record.FrameworkLibAddr = helperAddr
}
return record, true
}
func validSoftLinkDlsymSymbol(symbol string) bool {
symbol = strings.TrimSpace(symbol)
if symbol == "" || len(symbol) > 256 {
return false
}
if strings.ContainsAny(symbol, "/ \t\r\n") {
return false
}
return true
}
func softLinkStoreAfterCall(instrs []xref.Instruction, callIdx int, readPointer func(uint64) (uint64, error)) (uint64, bool) {
var state [31]softLinkRegValue
var returnRegs [31]bool
returnRegs[0] = true
limit := min(callIdx+16, len(instrs)-1)
for idx := callIdx + 1; idx <= limit; idx++ {
inst := &instrs[idx].Inst
if addr, ok := softLinkReturnStoreAddress(inst, state); ok {
src, srcOK := xref.OperandRegIndex(inst, 0)
if srcOK && returnRegs[src] {
return addr, true
}
}
softLinkApplyReturnMove(inst, &returnRegs)
softLinkApplyInstruction(inst, &state, readPointer)
if softLinkStopsStoreScan(inst) {
break
}
}
return 0, false
}
func softLinkReturnStoreAddress(inst *disassemble.Inst, state [31]softLinkRegValue) (uint64, bool) {
switch inst.Operation {
case disassemble.ARM64_STR, disassemble.ARM64_STUR:
default:
return 0, false
}
return softLinkMemoryAddress(inst, 1, state)
}
func softLinkApplyReturnMove(inst *disassemble.Inst, returnRegs *[31]bool) {
dst, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return
}
switch inst.Operation {
case disassemble.ARM64_MOV:
src, ok := xref.OperandRegIndex(inst, 1)
returnRegs[dst] = ok && returnRegs[src]
case disassemble.ARM64_ORR:
left, leftOK := xref.OperandReg(inst, 1)
right, rightOK := xref.OperandRegIndex(inst, 2)
returnRegs[dst] = leftOK && rightOK && softLinkIsZeroReg(left) && returnRegs[right]
default:
if softLinkShouldClearDest(inst) {
returnRegs[dst] = false
}
}
}
func softLinkStopsStoreScan(inst *disassemble.Inst) bool {
if inst == nil {
return false
}
switch inst.Operation {
case disassemble.ARM64_BL, disassemble.ARM64_BLR,
disassemble.ARM64_BLRAA, disassemble.ARM64_BLRAAZ,
disassemble.ARM64_BLRAB, disassemble.ARM64_BLRABZ,
disassemble.ARM64_B, disassemble.ARM64_BR,
disassemble.ARM64_BRAA, disassemble.ARM64_BRAAZ,
disassemble.ARM64_RET, disassemble.ARM64_RETAA, disassemble.ARM64_RETAB:
return true
default:
return false
}
}
func softLinkLastInternalCall(instrs []xref.Instruction, callIdx int, targets softLinkCallTargets) uint64 {
for idx := callIdx - 1; idx >= 0 && idx >= callIdx-32; idx-- {
target, ok := softLinkDirectCallTarget(&instrs[idx].Inst)
if !ok || target == 0 || targets.dlsym.Has(target) || targets.dispatchOnce.Has(target) {
continue
}
return target
}
return 0
}
func softLinkStateBefore(
instrs []xref.Instruction,
callIdx int,
readPointer func(uint64) (uint64, error),
maxInstructions int,
) [31]softLinkRegValue {
var state [31]softLinkRegValue
window := maxInstructions
if window <= 0 {
window = 32
}
start := max(callIdx-window, 0)
for idx := start; idx < callIdx; idx++ {
softLinkApplyInstruction(&instrs[idx].Inst, &state, readPointer)
}
return state
}
func softLinkApplyInstruction(
inst *disassemble.Inst,
state *[31]softLinkRegValue,
readPointer func(uint64) (uint64, error),
) {
switch inst.Operation {
case disassemble.ARM64_ADR, disassemble.ARM64_ADRP:
if rd, ok := xref.OperandRegIndex(inst, 0); ok {
if imm, ok := xref.OperandImm(inst, 1); ok {
state[rd] = softLinkRegValue{known: true, addr: imm}
}
}
case disassemble.ARM64_ADD:
softLinkApplyAdd(inst, state)
case disassemble.ARM64_LDR, disassemble.ARM64_LDUR:
softLinkApplyLoad(inst, state, readPointer)
case disassemble.ARM64_MOV:
softLinkApplyMove(inst, state)
case disassemble.ARM64_MOVZ:
if rd, ok := xref.OperandRegIndex(inst, 0); ok {
if imm, ok := xref.OperandImm(inst, 1); ok {
state[rd] = softLinkRegValue{known: true, addr: imm}
}
}
case disassemble.ARM64_MOVK:
softLinkApplyMoveKeep(inst, state)
case disassemble.ARM64_ORR:
if !softLinkApplyORRMove(inst, state) {
softLinkClearDest(inst, state)
}
default:
if softLinkIsCall(inst.Operation) {
for idx := 0; idx <= 17; idx++ {
state[idx] = softLinkRegValue{}
}
return
}
if softLinkShouldClearDest(inst) {
softLinkClearDest(inst, state)
}
}
}
func softLinkApplyAdd(inst *disassemble.Inst, state *[31]softLinkRegValue) {
rd, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return
}
rn, ok := xref.OperandRegIndex(inst, 1)
if !ok || !state[rn].known {
state[rd] = softLinkRegValue{}
return
}
imm, ok := xref.OperandImm(inst, 2)
if !ok {
state[rd] = softLinkRegValue{}
return
}
state[rd] = softLinkRegValue{known: true, addr: state[rn].addr + imm}
}
func softLinkApplyLoad(
inst *disassemble.Inst,
state *[31]softLinkRegValue,
readPointer func(uint64) (uint64, error),
) {
rd, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return
}
addr, ok := softLinkMemoryAddress(inst, 1, *state)
if !ok {
state[rd] = softLinkRegValue{}
return
}
value := softLinkRegValue{loadAddr: addr}
if readPointer != nil {
if ptr, err := readPointer(addr); err == nil && ptr != 0 {
value.known = true
value.addr = ptr
}
}
state[rd] = value
}
func softLinkApplyMove(inst *disassemble.Inst, state *[31]softLinkRegValue) {
rd, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return
}
if rn, ok := xref.OperandRegIndex(inst, 1); ok {
state[rd] = state[rn]
return
}
if imm, ok := xref.OperandImm(inst, 1); ok {
state[rd] = softLinkRegValue{known: true, addr: imm}
return
}
state[rd] = softLinkRegValue{}
}
func softLinkApplyMoveKeep(inst *disassemble.Inst, state *[31]softLinkRegValue) {
rd, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return
}
imm, ok := xref.OperandImm(inst, 1)
if !ok || !state[rd].known {
state[rd] = softLinkRegValue{}
return
}
shift := uint64(0)
if inst.NumOps > 1 && inst.Operands[1].ShiftValueUsed {
shift = uint64(inst.Operands[1].ShiftValue)
}
mask := uint64(0xffff) << shift
state[rd].addr = (state[rd].addr &^ mask) | ((imm << shift) & mask)
}
func softLinkApplyORRMove(inst *disassemble.Inst, state *[31]softLinkRegValue) bool {
rd, ok := xref.OperandRegIndex(inst, 0)
if !ok {
return false
}
left, leftOK := xref.OperandReg(inst, 1)
right, rightOK := xref.OperandRegIndex(inst, 2)
if leftOK && rightOK && softLinkIsZeroReg(left) {
state[rd] = state[right]
return true
}
return false
}
func softLinkMemoryAddress(inst *disassemble.Inst, operand int, state [31]softLinkRegValue) (uint64, bool) {
if target, ok := xref.LabelTarget(inst); ok {
return target, true
}
baseReg, ok := xref.OperandRegIndex(inst, operand)
if !ok || !state[baseReg].known {
return 0, false
}
offset, ok := xref.MemoryOffset(inst, operand)
if !ok {
return 0, false
}
return state[baseReg].addr + offset, true
}
func softLinkClearDest(inst *disassemble.Inst, state *[31]softLinkRegValue) {
if rd, ok := xref.OperandRegIndex(inst, 0); ok {
state[rd] = softLinkRegValue{}
}
}
func softLinkDirectCallTarget(inst *disassemble.Inst) (uint64, bool) {
switch inst.Operation {
case disassemble.ARM64_BL, disassemble.ARM64_B:
return xref.LabelTarget(inst)
default:
return 0, false
}
}
func softLinkIsCall(op disassemble.Operation) bool {
switch op {
case disassemble.ARM64_BL, disassemble.ARM64_BLR,
disassemble.ARM64_BLRAA, disassemble.ARM64_BLRAAZ,
disassemble.ARM64_BLRAB, disassemble.ARM64_BLRABZ:
return true
default:
return false
}
}
func softLinkShouldClearDest(inst *disassemble.Inst) bool {
if inst == nil || inst.NumOps == 0 {
return false
}
if _, ok := xref.OperandRegIndex(inst, 0); !ok {
return false
}
op := strings.ToLower(inst.Operation.String())
if strings.HasPrefix(op, "st") || strings.HasPrefix(op, "b.") || op == "b" || op == "br" || op == "ret" {
return false
}
return true
}
func softLinkIsZeroReg(reg disassemble.Register) bool {
return reg == disassemble.REG_XZR || reg == disassemble.REG_WZR
}
func mergeSoftLinkRecords(existing, extra []SoftLinkRecord) []SoftLinkRecord {
bySymbol := make(map[string]*SoftLinkRecord)
byGlobal := make(map[uint64]*SoftLinkRecord)
for _, record := range existing {
copy := record
bySymbol[copy.Symbol] = &copy
if copy.GlobalAddr != 0 {
byGlobal[copy.GlobalAddr] = &copy
}
}
for _, record := range extra {
if record.GlobalAddr != 0 {
if existing := byGlobal[record.GlobalAddr]; existing != nil {
mergeSoftLinkRecord(existing, record)
continue
}
}
merged := mergeSoftLinkRecord(bySymbol[record.Symbol], record)
bySymbol[merged.Symbol] = merged
if merged.GlobalAddr != 0 {
byGlobal[merged.GlobalAddr] = merged
}
}
records := make([]SoftLinkRecord, 0, len(bySymbol))
for _, record := range bySymbol {
records = append(records, *record)
}
sort.Slice(records, func(i, j int) bool {
if records[i].Symbol == records[j].Symbol {
return records[i].GlobalAddr < records[j].GlobalAddr
}
return records[i].Symbol < records[j].Symbol
})
return records
}
func mergeSoftLinkRecord(existing *SoftLinkRecord, extra SoftLinkRecord) *SoftLinkRecord {
if existing == nil {
copy := extra
return &copy
}
if existing.Image == "" {
existing.Image = extra.Image
}
if existing.GlobalAddr == 0 {
existing.GlobalAddr = extra.GlobalAddr
}
if existing.InitFuncAddr == 0 {
existing.InitFuncAddr = extra.InitFuncAddr
}
if existing.OnceAddr == 0 {
existing.OnceAddr = extra.OnceAddr
}
if existing.FrameworkLibAddr == 0 {
existing.FrameworkLibAddr = extra.FrameworkLibAddr
}
if existing.GlobalName == "" {
existing.GlobalName = extra.GlobalName
}
if existing.InitFuncName == "" {
existing.InitFuncName = extra.InitFuncName
}
if existing.OnceName == "" {
existing.OnceName = extra.OnceName
}
if existing.FrameworkLibName == "" {
existing.FrameworkLibName = extra.FrameworkLibName
}
return existing
}