mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
- Updated disassembly functions to utilize the new `disassemble.Inst` type instead of `disassemble.Instruction`. - Modified operand retrieval functions to accommodate the new instruction structure. - Enhanced error handling and logging for instruction decoding failures. - Improved JSON output for disassembly to ensure disassembly strings are preserved. - Refactored various components across the disassembly package, including Mach-O and dyld handling, to streamline instruction processing. - Added tests to validate the new disassembly behavior and ensure backward compatibility.
907 lines
25 KiB
Go
907 lines
25 KiB
Go
package disass
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"io"
|
|
"maps"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/arm64-cgo/disassemble"
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-macho/pkg/fixupchains"
|
|
"github.com/blacktop/go-macho/pkg/swift"
|
|
"github.com/blacktop/go-macho/types/objc"
|
|
"github.com/blacktop/ipsw/internal/demangle"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/blacktop/ipsw/pkg/symbols"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type MachoDisass struct {
|
|
f *macho.File
|
|
cfg *Config
|
|
tr *Triage
|
|
a2s map[uint64]string
|
|
sinfo map[uint64]uint64
|
|
swiftstrs map[uint64]string
|
|
decoder disassemble.Decoder
|
|
}
|
|
|
|
func NewMachoDisass(f *macho.File, cfg *Config) *MachoDisass {
|
|
return &MachoDisass{f: f, cfg: cfg, a2s: make(map[uint64]string, 0), swiftstrs: make(map[uint64]string, 0)}
|
|
}
|
|
|
|
func (d MachoDisass) Demangle() bool {
|
|
return d.cfg.Demangle
|
|
}
|
|
func (d MachoDisass) Quite() bool {
|
|
return d.cfg.Quiet
|
|
}
|
|
func (d MachoDisass) Color() bool {
|
|
return d.cfg.Color
|
|
}
|
|
func (d MachoDisass) AsJSON() bool {
|
|
return d.cfg.AsJSON
|
|
}
|
|
func (d MachoDisass) Data() []byte {
|
|
return d.cfg.Data
|
|
}
|
|
func (d MachoDisass) StartAddr() uint64 {
|
|
return d.cfg.StartAddress
|
|
}
|
|
func (d MachoDisass) Middle() uint64 {
|
|
return d.cfg.Middle
|
|
}
|
|
func (d MachoDisass) ReadAddr(addr uint64) (uint64, error) {
|
|
ptr, err := d.f.GetPointerAtAddress(addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return d.f.SlidePointer(ptr), nil
|
|
}
|
|
|
|
// Triage walks a function and analyzes all immediates
|
|
func (d *MachoDisass) Triage() error {
|
|
var instrValue uint32
|
|
var prevInstr disassemble.Inst
|
|
var hasPrev bool
|
|
|
|
d.tr = &Triage{
|
|
Addresses: make(map[uint64]uint64),
|
|
Locations: make(map[uint64][]uint64),
|
|
}
|
|
startAddr := d.StartAddr()
|
|
r := bytes.NewReader(d.Data())
|
|
|
|
// extract all immediates
|
|
for {
|
|
err := binary.Read(r, binary.LittleEndian, &instrValue)
|
|
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
var instruction disassemble.Inst
|
|
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
continue
|
|
}
|
|
|
|
if IsBranchOp(instruction.Operation) {
|
|
for idx := 0; idx < int(instruction.NumOps); idx++ {
|
|
op := instruction.Operands[idx]
|
|
if op.Class == disassemble.LABEL {
|
|
d.tr.Addresses[instruction.Address] = uint64(op.Immediate)
|
|
}
|
|
}
|
|
} else if IsLoadLiteral(&instruction) {
|
|
d.tr.Addresses[instruction.Address] = uint64(instruction.Operands[1].Immediate)
|
|
} else if hasPrev && prevInstr.Operation == disassemble.ARM64_ADRP &&
|
|
(instruction.Operation == disassemble.ARM64_ADD ||
|
|
instruction.Operation == disassemble.ARM64_LDR ||
|
|
instruction.Operation == disassemble.ARM64_LDRB ||
|
|
instruction.Operation == disassemble.ARM64_LDRSW) {
|
|
adrpRegister, ok := operandRegister(&prevInstr, 0)
|
|
if !ok {
|
|
prevInstr = instruction
|
|
hasPrev = true
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
continue
|
|
}
|
|
adrpImm, ok := operandImmediate(&prevInstr, 1)
|
|
if !ok {
|
|
prevInstr = instruction
|
|
hasPrev = true
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
continue
|
|
}
|
|
if srcRegister, ok := operandRegister(&instruction, 1); ok && adrpRegister == srcRegister {
|
|
switch instruction.Operation {
|
|
case disassemble.ARM64_LDR, disassemble.ARM64_LDRB, disassemble.ARM64_LDRSW:
|
|
if imm, ok := operandImmediate(&instruction, 1); ok {
|
|
adrpImm += imm
|
|
}
|
|
case disassemble.ARM64_ADD:
|
|
if imm, ok := operandImmediate(&instruction, 2); ok {
|
|
adrpImm += imm
|
|
}
|
|
}
|
|
}
|
|
d.tr.Addresses[instruction.Address] = adrpImm
|
|
}
|
|
|
|
prevInstr = instruction
|
|
hasPrev = true
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
}
|
|
|
|
if !d.Quite() {
|
|
d.tr.Details = make(map[uint64]AddrDetails)
|
|
|
|
for addr, imm := range d.tr.Addresses {
|
|
if fn, err := d.f.GetFunctionForVMAddr(addr); err == nil {
|
|
d.tr.Function = &fn
|
|
if d.tr.Function.StartAddr <= imm && imm < d.tr.Function.EndAddr {
|
|
d.tr.Locations[imm] = append(d.tr.Locations[imm], addr)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if c := d.f.FindSectionForVMAddr(imm); c != nil {
|
|
d.tr.Details[imm] = AddrDetails{
|
|
Segment: c.Seg,
|
|
Section: c.Name,
|
|
Flags: c.Flags,
|
|
}
|
|
} else {
|
|
ptr, _ := d.f.GetPointerAtAddress(imm)
|
|
ptr = d.f.SlidePointer(ptr)
|
|
if c := d.f.FindSectionForVMAddr(ptr); c != nil {
|
|
d.tr.Details[imm] = AddrDetails{
|
|
Segment: c.Seg,
|
|
Section: c.Name,
|
|
Pointer: ptr,
|
|
Flags: c.Flags,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FindSwiftStrings walks a function extracts Swift StringObjects/String Structs/Compiler optimized strings
|
|
// ref - test/SILOptimizer/character_literals.swift
|
|
// ref - stdlib/public/core/StringObject.swift
|
|
func (d *MachoDisass) FindSwiftStrings() (out map[uint64]string, err error) {
|
|
var instrValue uint32
|
|
var prevInstr disassemble.Inst
|
|
var hasPrev bool
|
|
|
|
d.tr = &Triage{
|
|
Addresses: make(map[uint64]uint64),
|
|
Locations: make(map[uint64][]uint64),
|
|
}
|
|
startAddr := d.StartAddr()
|
|
r := bytes.NewReader(d.Data())
|
|
|
|
out = make(map[uint64]string)
|
|
|
|
ss := make([]byte, 16)
|
|
buf := bytes.NewBuffer(ss)
|
|
|
|
strAddr := uint64(0)
|
|
reg := disassemble.REG_NONE
|
|
regVal := uint64(0)
|
|
next := disassemble.REG_NONE
|
|
nextVal := uint64(0)
|
|
|
|
// extract all Swift strings
|
|
for {
|
|
err := binary.Read(r, binary.LittleEndian, &instrValue)
|
|
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
var instruction disassemble.Inst
|
|
if err := d.decoder.DecomposeInto(startAddr, instrValue, &instruction); err != nil {
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
continue
|
|
}
|
|
|
|
if instruction.Operation == disassemble.ARM64_MOV {
|
|
if dstRegister, ok := operandRegister(&instruction, 0); ok {
|
|
if imm, ok := operandImmediate(&instruction, 1); ok {
|
|
if reg == disassemble.REG_NONE {
|
|
strAddr = instruction.Address
|
|
reg = dstRegister
|
|
regVal = imm
|
|
} else {
|
|
if regVal > 0 {
|
|
next = dstRegister
|
|
nextVal = imm
|
|
} else {
|
|
strAddr = instruction.Address
|
|
reg = dstRegister
|
|
regVal = imm
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if hasPrev &&
|
|
((prevInstr.Operation == disassemble.ARM64_MOV && instruction.Operation == disassemble.ARM64_MOVK) ||
|
|
(prevInstr.Operation == disassemble.ARM64_MOVK && instruction.Operation == disassemble.ARM64_MOVK)) {
|
|
if dstRegister, ok := operandRegister(&instruction, 0); ok {
|
|
if imm, ok := operandImmediate(&instruction, 1); ok {
|
|
if shift, ok := operandShiftValue(&instruction, 1); ok {
|
|
if reg == dstRegister {
|
|
regVal += imm << shift
|
|
} else if next == dstRegister {
|
|
nextVal += imm << shift
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if regVal > 0 && nextVal > 0 {
|
|
discriminator := (nextVal & 0xFF00_0000_0000_0000) >> 56
|
|
count := discriminator & 0xF
|
|
if count > 0 {
|
|
nextVal = nextVal & 0x00FF_FFFF_FFFF_FFFF
|
|
buf.Reset()
|
|
binary.Write(buf, binary.LittleEndian, regVal)
|
|
binary.Write(buf, binary.LittleEndian, nextVal)
|
|
if (discriminator & 0xF0) == 0xE0 { // small ascii string
|
|
if utils.IsASCII(string(ss[:count])) {
|
|
out[strAddr] = string(ss[:count])
|
|
ss = ss[:0]
|
|
}
|
|
} else if (discriminator & 0xF0) == 0xA0 { // small non-ascii string
|
|
out[strAddr] = utils.UnicodeSanitize(string(ss[:count]))
|
|
ss = ss[:0]
|
|
} // TODO: add support for (discriminator & 0xF0) == 0x80 { // large string
|
|
}
|
|
}
|
|
// RESET
|
|
strAddr = uint64(0)
|
|
reg = disassemble.REG_NONE
|
|
regVal = uint64(0)
|
|
next = disassemble.REG_NONE
|
|
nextVal = uint64(0)
|
|
}
|
|
|
|
prevInstr = instruction
|
|
hasPrev = true
|
|
startAddr += uint64(binary.Size(uint32(0)))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// IsFunctionStart checks if address is at a function start and returns symbol name
|
|
func (d MachoDisass) IsFunctionStart(addr uint64) (bool, string) {
|
|
for _, fn := range d.f.GetFunctions() {
|
|
if addr == fn.StartAddr {
|
|
if symName, ok := d.a2s[addr]; ok {
|
|
display := symbols.FormatSymbol(symName, d.Demangle())
|
|
return ok, display
|
|
}
|
|
return true, fmt.Sprintf("sub_%x", addr)
|
|
}
|
|
}
|
|
return false, ""
|
|
}
|
|
|
|
// IsLocation returns if given address is a local branch location within the disassembled function
|
|
func (d MachoDisass) IsLocation(imm uint64) bool {
|
|
if _, ok := d.tr.Locations[imm]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsBranchLocation returns if given address is branch to a location instruction
|
|
func (d MachoDisass) IsBranchLocation(addr uint64) (bool, uint64) {
|
|
for loc, addrs := range d.tr.Locations {
|
|
if slices.Contains(addrs, addr) {
|
|
return true, loc
|
|
}
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
// IsData returns if given address is a data variable address referenced in the disassembled function
|
|
func (d MachoDisass) IsData(addr uint64) (bool, *AddrDetails) {
|
|
if detail, ok := d.tr.Details[addr]; ok {
|
|
if detail.Flags.IsCstringLiterals() {
|
|
return true, &detail
|
|
}
|
|
segment := strings.ToLower(detail.Segment)
|
|
if strings.Contains(segment, "data") {
|
|
return true, &detail
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// IsPointer returns if given address is a pointer to another address
|
|
func (d MachoDisass) IsPointer(imm uint64) (bool, *AddrDetails) {
|
|
if deet, ok := d.tr.Details[imm]; ok {
|
|
if deet.Pointer > 0 {
|
|
return true, &deet
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// FindSymbol returns symbol from the addr2symbol map for a given virtual address
|
|
func (d MachoDisass) FindSymbol(addr uint64) (string, bool) {
|
|
if symName, ok := d.a2s[addr]; ok {
|
|
return symName, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (d MachoDisass) FindSwiftString(addr uint64) (string, bool) {
|
|
if str, ok := d.swiftstrs[addr]; ok {
|
|
return str, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// Contains returns true if Triage immediates contains a given address and will return the instruction address
|
|
func (d MachoDisass) Contains(address uint64) (bool, uint64) {
|
|
for loc, addr := range d.tr.Addresses {
|
|
if addr == address {
|
|
return true, loc
|
|
}
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
func (d MachoDisass) GetCString(addr uint64) (string, error) {
|
|
return d.f.GetCString(addr)
|
|
}
|
|
|
|
func (d MachoDisass) Analyze() error {
|
|
|
|
if err := d.parseSymbols(); err != nil {
|
|
return fmt.Errorf("failed to parse symbols: %v", err)
|
|
}
|
|
|
|
if err := d.parseImports(); err != nil {
|
|
return fmt.Errorf("failed to parse imports: %v", err)
|
|
}
|
|
|
|
if err := d.parseObjC(); err != nil {
|
|
return fmt.Errorf("failed to parse objc runtime: %v", err)
|
|
}
|
|
|
|
if err := d.parseSwift(); err != nil {
|
|
return fmt.Errorf("failed to parse swift: %v", err)
|
|
}
|
|
|
|
if err := d.parseRebaseInfo(); err != nil {
|
|
if !errors.Is(err, macho.ErrMachODyldInfoNotFound) {
|
|
return fmt.Errorf("failed to parse rebase info: %v", err)
|
|
}
|
|
}
|
|
|
|
if err := d.parseHelpers(); err != nil {
|
|
if !errors.Is(err, macho.ErrMachOSectionNotFound) {
|
|
return fmt.Errorf("failed to parse stubs helpers: %v", err)
|
|
}
|
|
}
|
|
|
|
if err := d.parseGOT(); err != nil {
|
|
return fmt.Errorf("failed to parse GOT: %v", err)
|
|
}
|
|
|
|
if err := d.parseStubs(); err != nil {
|
|
return fmt.Errorf("failed to parse symbol stubs: %v", err)
|
|
}
|
|
|
|
if a2s, err := d.FindSwiftStrings(); err == nil {
|
|
maps.Copy(d.swiftstrs, a2s)
|
|
} else {
|
|
return fmt.Errorf("failed to find swift strings: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseSymbols() error {
|
|
for _, sym := range d.f.Symtab.Syms {
|
|
if sym.Value > 0 && len(sym.Name) > 0 {
|
|
d.a2s[sym.Value] = sym.Name
|
|
}
|
|
}
|
|
exports, err := d.f.GetExports()
|
|
if err != nil {
|
|
if err != macho.ErrMachODyldInfoNotFound {
|
|
return fmt.Errorf("failed to get exports: %v", err)
|
|
}
|
|
}
|
|
for _, sym := range exports {
|
|
if sym.Address > 0 {
|
|
d.a2s[sym.Address] = sym.Name
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseRebaseInfo() error {
|
|
d.sinfo = make(map[uint64]uint64)
|
|
|
|
if d.f.HasFixups() {
|
|
dcf, err := d.f.DyldChainedFixups()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse fixups: %v", err)
|
|
}
|
|
for _, start := range dcf.Starts {
|
|
if start.PageStarts != nil {
|
|
// var sec *macho.Section
|
|
// var lastSec *macho.Section
|
|
for _, fixup := range start.Fixups {
|
|
addr, err := d.f.GetVMAddress(fixup.Offset())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
switch fx := fixup.(type) {
|
|
case fixupchains.Bind:
|
|
// var addend string
|
|
// addr := uint64(fx.Offset()) + d.f.GetBaseAddress()
|
|
// if fullAddend := dcf.Imports[fx.Ordinal()].Addend() + fx.Addend(); fullAddend > 0 {
|
|
// addend = fmt.Sprintf(" + %#x", fullAddend)
|
|
// addr += fullAddend
|
|
// }
|
|
// sec = d.f.FindSectionForVMAddr(addr)
|
|
// lib := d.f.LibraryOrdinalName(dcf.Imports[fx.Ordinal()].LibOrdinal())
|
|
// if sec != nil && sec != lastSec {
|
|
// fmt.Printf("%s.%s\n", sec.Seg, sec.Name)
|
|
// }
|
|
// fmt.Printf("%s\t%s/%s%s\n", fixupchains.Bind(fx).String(d.f.GetBaseAddress()), lib, fx.Name(), addend)
|
|
case fixupchains.Rebase:
|
|
d.sinfo[addr] = uint64(fx.Target()) + d.f.GetBaseAddress()
|
|
}
|
|
// lastSec = sec
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
rbs, err := d.f.GetRebaseInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, r := range rbs {
|
|
d.sinfo[r.Start+r.Offset] = r.Value
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseObjC() error {
|
|
if d.f.HasObjC() {
|
|
if cfstrs, err := d.f.GetCFStrings(); err == nil {
|
|
for _, cfstr := range cfstrs {
|
|
d.a2s[cfstr.Address] = fmt.Sprintf("%#v", cfstr.Name)
|
|
}
|
|
}
|
|
if selRefs, err := d.f.GetObjCSelectorReferences(); err == nil {
|
|
for off, sel := range selRefs {
|
|
d.a2s[off] = fmt.Sprintf("sel_%s", sel.Name)
|
|
d.a2s[d.f.GetBaseAddress()+sel.VMAddr] = sel.Name
|
|
}
|
|
}
|
|
if classes, err := d.f.GetObjCClasses(); err == nil {
|
|
for _, class := range classes {
|
|
d.a2s[class.ClassPtr] = fmt.Sprintf("class_%s", class.Name)
|
|
d.a2s[class.IsaVMAddr] = fmt.Sprintf("objc_isa_%s", class.Isa)
|
|
for _, meth := range class.ClassMethods {
|
|
if len(meth.Name) > 0 {
|
|
d.a2s[meth.ImpVMAddr] = fmt.Sprintf("+[%s %s]", class.Name, meth.Name)
|
|
}
|
|
}
|
|
for _, imeth := range class.InstanceMethods {
|
|
if len(imeth.Name) > 0 {
|
|
d.a2s[imeth.ImpVMAddr] = fmt.Sprintf("-[%s %s]", class.Name, imeth.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if classRefs, err := d.f.GetObjCClassReferences(); err == nil {
|
|
for off, class := range classRefs {
|
|
d.a2s[off] = fmt.Sprintf("class_%s", class.Name)
|
|
d.a2s[d.f.GetBaseAddress()+class.ClassPtr] = class.Name
|
|
}
|
|
}
|
|
if superRefs, err := d.f.GetObjCSuperReferences(); err == nil {
|
|
for off, class := range superRefs {
|
|
d.a2s[off] = fmt.Sprintf("class_%s", class.Name)
|
|
d.a2s[class.ClassPtr] = class.Name
|
|
d.a2s[class.IsaVMAddr] = class.Isa
|
|
}
|
|
}
|
|
if protoRefs, err := d.f.GetObjCProtoReferences(); err == nil {
|
|
for off, proto := range protoRefs {
|
|
d.a2s[off] = fmt.Sprintf("proto_%s", proto.Name)
|
|
d.a2s[d.f.GetBaseAddress()+proto.Ptr] = proto.Name
|
|
}
|
|
}
|
|
if objcStubs, err := d.f.GetObjCStubs(func(addr uint64, data []byte) (map[uint64]*objc.Stub, error) {
|
|
stubs := make(map[uint64]*objc.Stub)
|
|
addr2sel, err := ParseStubsASM(data, addr, func(u uint64) (uint64, error) {
|
|
ptr, err := d.f.GetPointerAtAddress(u)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if name, err := d.f.GetBindName(ptr); err == nil && name == "_objc_msgSend" {
|
|
return 0, nil
|
|
}
|
|
ptr = d.f.SlidePointer(ptr)
|
|
if ptr < d.f.GetBaseAddress() {
|
|
return ptr + d.f.GetBaseAddress(), nil
|
|
}
|
|
return ptr, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for addr, sel := range addr2sel {
|
|
if d.a2s[sel] != "_objc_msgSend" {
|
|
stubs[addr] = &objc.Stub{
|
|
Name: d.a2s[sel],
|
|
SelectorRef: sel,
|
|
}
|
|
}
|
|
}
|
|
return stubs, nil
|
|
}); err == nil {
|
|
for addr, stub := range objcStubs {
|
|
if len(stub.Name) > 0 {
|
|
d.a2s[addr] = fmt.Sprintf("j__objc_msgSend(x0, \"%s\")", stub.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseSwift() error {
|
|
if d.f.HasSwift() {
|
|
if types, err := d.f.GetSwiftTypes(); err == nil {
|
|
for _, typ := range types {
|
|
if typ.Name != "" {
|
|
if d.cfg.Demangle {
|
|
typ.Name, _ = swift.Demangle(typ.Name)
|
|
}
|
|
d.a2s[typ.Address] = fmt.Sprintf("type descriptor for %s", typ.Name)
|
|
}
|
|
}
|
|
}
|
|
if fields, err := d.f.GetSwiftFields(); err == nil {
|
|
for _, field := range fields {
|
|
if field.Type != "" {
|
|
if d.cfg.Demangle {
|
|
field.Type, _ = swift.Demangle(field.Type)
|
|
}
|
|
d.a2s[field.Address] = fmt.Sprintf("field descriptor for %s", field.Type)
|
|
}
|
|
}
|
|
}
|
|
if dtds, err := d.f.GetSwiftProtocolConformances(); err == nil {
|
|
for _, dtd := range dtds {
|
|
if dtd.TypeRef.Name != "" {
|
|
if dtd.TypeRef.Parent != nil && dtd.TypeRef.Parent.Name != "" &&
|
|
dtd.TypeRef.Parent.Parent != nil && dtd.TypeRef.Parent.Parent.Name != "" {
|
|
dtd.TypeRef.Name = fmt.Sprintf("%s.%s", dtd.TypeRef.Parent, dtd.TypeRef.Name)
|
|
}
|
|
if d.cfg.Demangle {
|
|
dtd.Protocol, _ = swift.DemangleSimple(dtd.Protocol)
|
|
}
|
|
// log.Debugf("nominal type descriptor for %s : %s", dtd.TypeRef.Name, dtd.Protocol)
|
|
d.a2s[dtd.TypeRef.Address] = fmt.Sprintf("nominal type descriptor for %s : %s", dtd.TypeRef.Name, dtd.Protocol)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseImports() error {
|
|
if d.f.HasFixups() {
|
|
var addr uint64
|
|
|
|
dcf, err := d.f.DyldChainedFixups()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dcf.Imports != nil {
|
|
for _, start := range dcf.Starts {
|
|
if start.PageStarts != nil {
|
|
binds := start.Binds()
|
|
if len(binds) > 0 {
|
|
for _, bind := range binds {
|
|
fullAddend := dcf.Imports[bind.Ordinal()].Addend() + bind.Addend()
|
|
addr = d.f.GetBaseAddress() + bind.Offset() + fullAddend
|
|
d.a2s[bind.Raw()] = bind.Name()
|
|
d.a2s[addr] = bind.Name()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseGOT() error {
|
|
gots, err := ParseGotPtrs(d.f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for entry, target := range gots {
|
|
if slide, ok := d.sinfo[entry]; ok {
|
|
target = slide
|
|
}
|
|
if symName, ok := d.a2s[target]; ok {
|
|
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, symName)
|
|
continue
|
|
}
|
|
if laptr, ok := gots[target]; ok {
|
|
if symName, ok := d.a2s[laptr]; ok {
|
|
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, symName)
|
|
continue
|
|
}
|
|
}
|
|
// Try to decode as a chained fixup bind pointer
|
|
if d.f.HasFixups() {
|
|
if name, err := d.f.GetBindName(target); err == nil {
|
|
d.a2s[entry] = fmt.Sprintf("%s%s", symbols.PrefixGot, name)
|
|
continue
|
|
}
|
|
}
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for GOT entry %#x => %#x", entry, target))
|
|
d.a2s[entry] = fmt.Sprintf("%s%x", symbols.PrefixGotFallback, target)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseStubs() error {
|
|
stubs, err := ParseStubsForMachO(d.f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for stub, target := range stubs {
|
|
if slide, ok := d.sinfo[stub]; ok {
|
|
target = slide
|
|
}
|
|
// Check if target is in GOT - if so, use the GOT entry's symbol
|
|
if gotSymName, ok := d.a2s[target]; ok {
|
|
if !strings.HasPrefix(gotSymName, symbols.PrefixJump) {
|
|
d.a2s[stub] = symbols.PrefixJump + strings.TrimPrefix(strings.TrimPrefix(gotSymName, symbols.PrefixGot), symbols.PrefixStubHelper)
|
|
} else {
|
|
d.a2s[stub] = gotSymName
|
|
}
|
|
continue
|
|
}
|
|
// Try to decode as a chained fixup bind pointer
|
|
if d.f.HasFixups() {
|
|
if name, err := d.f.GetBindName(target); err == nil {
|
|
d.a2s[stub] = fmt.Sprintf("%s%s", symbols.PrefixJump, name)
|
|
continue
|
|
}
|
|
}
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("no sym found for stub %#x => %#x", stub, target))
|
|
d.a2s[stub] = fmt.Sprintf("%s%x", symbols.PrefixStubFallback, target)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) parseHelpers() error {
|
|
helpers, err := ParseHelpersASM(d.f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for start, target := range helpers {
|
|
if slide, ok := d.sinfo[start]; ok {
|
|
target = slide
|
|
}
|
|
if symName, ok := d.a2s[target]; ok {
|
|
d.a2s[start] = fmt.Sprintf("%s%s", symbols.PrefixStubHelper, symName)
|
|
} else {
|
|
d.a2s[start] = fmt.Sprintf("%s%x", symbols.PrefixStubHelper, target)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) EmptySymMap() bool {
|
|
return len(d.a2s) == 0
|
|
}
|
|
|
|
func (d *MachoDisass) SetStartSym(addr uint64) {
|
|
d.a2s[addr] = "start"
|
|
}
|
|
|
|
func (d *MachoDisass) loadDwarf(machoPath string) error {
|
|
dsymPath := filepath.Join(machoPath+".dSYM", "Contents/Resources/DWARF", filepath.Base(machoPath))
|
|
if _, statErr := os.Stat(dsymPath); statErr == nil {
|
|
dm, err := macho.Open(dsymPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open dSYM file: %v", err)
|
|
} else {
|
|
foundDSYMSymbols := false
|
|
for _, sym := range dm.Symtab.Syms {
|
|
if sym.Name != "" {
|
|
name := sym.Name
|
|
if d.cfg.Demangle {
|
|
if strings.HasPrefix(name, "_$s") {
|
|
name, _ = swift.Demangle(name)
|
|
} else {
|
|
name = demangle.Do(name, false, false)
|
|
}
|
|
}
|
|
d.a2s[sym.Value] = name
|
|
foundDSYMSymbols = true
|
|
}
|
|
}
|
|
dm.Close()
|
|
if foundDSYMSymbols {
|
|
utils.Indent(log.Info, 2)(fmt.Sprintf("Loaded %d symbols from .dSYM file", len(d.a2s)))
|
|
} else {
|
|
utils.Indent(log.Warn, 2)("No symbols found in dSYM file")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) getTempCachePath(cacheFile *string) string {
|
|
var tmpfile string
|
|
if d.f != nil {
|
|
uuid := d.f.UUID()
|
|
if uuid.UUID.IsNull() {
|
|
tmpfile = filepath.Base(*cacheFile)
|
|
} else {
|
|
tmpfile = uuid.String() + ".a2s"
|
|
}
|
|
}
|
|
return filepath.Join(os.TempDir(), tmpfile)
|
|
}
|
|
|
|
var ErrCorruptCache = errors.New("corrupt cache file")
|
|
|
|
var (
|
|
cachedSymbols map[uint64]string // In-memory cache of loaded symbols
|
|
cachedSymbolsFile string // Track which file was loaded
|
|
cacheLoadedOnce bool // Track if we've printed the cache load message
|
|
)
|
|
|
|
func (d *MachoDisass) loadCache(cacheFile *string) error {
|
|
// If we've already loaded this exact file, copy from memory cache
|
|
if cachedSymbols != nil && cachedSymbolsFile == *cacheFile {
|
|
maps.Copy(d.a2s, cachedSymbols)
|
|
return nil
|
|
}
|
|
|
|
// Load from disk
|
|
f, err := os.Open(*cacheFile)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return os.ErrNotExist
|
|
}
|
|
return fmt.Errorf("failed to open cache file %s: %v", *cacheFile, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
a2s := make(map[uint64]string)
|
|
if gobErr := gob.NewDecoder(f).Decode(&a2s); gobErr != nil {
|
|
return ErrCorruptCache
|
|
}
|
|
|
|
// Copy to instance
|
|
maps.Copy(d.a2s, a2s)
|
|
|
|
// Cache in memory for future instances
|
|
cachedSymbols = a2s
|
|
cachedSymbolsFile = *cacheFile
|
|
|
|
// Only print the load message once per program execution
|
|
if !cacheLoadedOnce {
|
|
log.Infof("Loaded %d symbols from cache file: %s", len(a2s), *cacheFile)
|
|
cacheLoadedOnce = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) OpenOrCreateSymMap(cacheFile *string, machoPath string) error {
|
|
if err := d.loadCache(cacheFile); err == nil {
|
|
return nil // cache file loaded successfully
|
|
} else if errors.Is(err, os.ErrNotExist) {
|
|
tmpcache := d.getTempCachePath(cacheFile)
|
|
if err := d.loadCache(&tmpcache); err == nil {
|
|
*cacheFile = tmpcache
|
|
return nil // temp cache file loaded successfully
|
|
} else if errors.Is(err, os.ErrNotExist) {
|
|
// cache AND temp cache files do not exist (create new one)
|
|
} else if errors.Is(err, ErrCorruptCache) {
|
|
log.Warn("temp cache file is corrupt, recreating it")
|
|
if err := os.Remove(tmpcache); err != nil {
|
|
return fmt.Errorf("failed to remove temp cache file: %v", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("failed to load temp cache file: %v", err)
|
|
}
|
|
} else if errors.Is(err, ErrCorruptCache) {
|
|
log.Warn("cache file is corrupt, recreating it")
|
|
if err := os.Remove(*cacheFile); err != nil {
|
|
return fmt.Errorf("failed to remove cache file: %v", err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("failed to load cache file: %v", err)
|
|
}
|
|
// load the .dSYM file if it exists
|
|
if err := d.loadDwarf(machoPath); err != nil {
|
|
return fmt.Errorf("failed to load dSYM file: %v", err)
|
|
}
|
|
if _, err := os.Create(*cacheFile); err != nil {
|
|
if errors.Is(err, os.ErrPermission) {
|
|
var e *os.PathError
|
|
if errors.As(err, &e) {
|
|
log.Errorf("failed to create symbol cache file %s (most likely a read-only location): %v", filepath.Base(e.Path), e.Err)
|
|
}
|
|
tmpcache := d.getTempCachePath(cacheFile)
|
|
if _, err := os.Create(tmpcache); err != nil {
|
|
return fmt.Errorf("failed to create temp cache file: %v", err)
|
|
}
|
|
utils.Indent(log.Warn, 2)("creating in the temp folder")
|
|
utils.Indent(log.Warn, 3)(fmt.Sprintf("to use in the future supply the flag: --cache %s ", tmpcache))
|
|
*cacheFile = tmpcache
|
|
return nil // Successfully created temp cache file
|
|
}
|
|
return fmt.Errorf("failed to create cache file: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MachoDisass) SaveAddrToSymMap(dest string) (err error) {
|
|
f, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open symbol cache file %s: %v", dest, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
buff := new(bytes.Buffer)
|
|
|
|
// Encoding the map
|
|
if err := gob.NewEncoder(buff).Encode(d.a2s); err != nil {
|
|
return fmt.Errorf("failed to encode addr2sym map to binary: %v", err)
|
|
}
|
|
if _, err := buff.WriteTo(f); err != nil {
|
|
return fmt.Errorf("failed to write addr2sym map to file: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|