macho/macho-go/pkg/ios/macho/dyld_info.go
2025-01-13 12:54:05 -06:00

364 lines
8.5 KiB
Go

package macho
import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"
// "bufio"
"io"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
. "ios-wrapper/pkg/leb128"
)
//#include "fixups.h"
import "C"
type ImportSymbol struct {
name string
typ string
dylib string
segment uint32
segment_offset uint32
address uint64
file_address uint64
lib_ordinal uint32
target uint32
high8 uint32
// push number
pnum uint32
stub uint64
next int // only for LC_DYLD_CHAINED_FIXUPS
}
func (sym *ImportSymbol) Name() string {
return sym.name
}
func (sym *ImportSymbol) Type() string {
return sym.typ
}
func (sym *ImportSymbol) SafeForRemoval() bool {
return sym.typ == "lazy" || sym.typ == "fixups"
}
func (sym *ImportSymbol) Dylib() string {
return sym.dylib
}
func (sym *ImportSymbol) LibOrdinal() uint32 {
return sym.lib_ordinal
}
func (sym *ImportSymbol) Address() uint64 {
return sym.address
}
func (sym *ImportSymbol) Pnum() uint32 {
return sym.pnum
}
func (sym *ImportSymbol) Stub() uint64 {
return sym.stub
}
func (sym *ImportSymbol) Segment() uint32 {
return sym.segment
}
func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol {
if mc.dyldinfo == nil {
return mc.CollectBindSymbolsModern()
} else {
return mc.CollectBindSymbolsLegacy()
}
}
func (mc *MachoContext) findSegmentIndexAt(address uint64) int {
for i, segment := range mc.Segments() {
if segment.Fileoff() <= address && segment.Fileoff()+segment.Filesize() > address {
return i
}
}
return -1
}
// New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
start := mc.fixups.dataoff
size := mc.fixups.datasize
buf := mc.buf[start : start+size]
// all pointers used are based from this **buf**
// until buf is freed, all pointers are valid
// remember to copy before moving out
header := (*C.uchar)(unsafe.Pointer(&buf[0]))
imports_table := C.GetImportsTable(header)
// for i := 0; i < int(imports_table.size); i++ {
// s := C.GetImportsAt(&imports_table, C.int(i))
// name := C.GoString(s.name)
// symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// symbol_table = append(symbol_table, symbol)
// fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name)
// }
var syms []*ImportSymbol
var sym ImportSymbol
segment_i := 0
for {
var fix C.struct_SegmentFix
fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix))
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1
if status == 2 {
break
}
if status == 3 {
continue
}
fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
reader := bytes.NewReader(mc.buf)
for page_i := 0; page_i < int(fix.page_count); page_i++ {
// loop through each page in segment, each page has size fix.page_size
// the first item in page is offset through pages[page_i]
address := int64(fix.segment) + int64(page_i)*int64(fix.page_size) + int64(pages[page_i])
reader.Seek(address, io.SeekStart)
fmt.Printf(" page %d offset=%x\n", page_i, address)
code := make([]byte, 8)
for {
reader.Read(code)
v := mc.byteorder.Uint64(code)
var bind C.int
var ret1 C.ulonglong
var ret2 C.ulonglong
if fix.format != 2 && fix.format != 6 {
fmt.Printf("format is %d\n", fix.format)
}
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
&bind, &ret1, &ret2)
if bind == 1 {
s := C.GetImportsAt(&imports_table, C.int(ret1))
name := C.GoString(s.name)
dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
fmt.Printf("// 0x%x bind=%d (%s)%s\n", address, bind, dylib, name)
sym.address = uint64(address)
sym.name = name
sym.dylib = dylib
sym.typ = "fixups"
sym.lib_ordinal = uint32(s.lib_ordinal)
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
sym.file_address = uint64(address)
sym.next = int(next)
new_sym := sym
syms = append(syms, &new_sym)
} else {
fmt.Printf("// 0x%x rebase=%d target=0x%x high8=0x%x\n", address, bind, ret1, ret2)
sym.typ = "rebase"
sym.target = uint32(ret1)
sym.high8 = uint32(ret2)
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
sym.file_address = uint64(address)
sym.next = int(next)
new_sym := sym
syms = append(syms, &new_sym)
}
if int(next) == 0 {
break
}
// because the pointer move up 8 bytes already so we minus 8
address += int64(next * 4)
reader.Seek(int64(next*4)-8, io.SeekCurrent)
}
reader.Seek(0, io.SeekStart)
}
}
fmt.Printf("number of imports %d\n", len(syms))
return syms
}
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner
func (mc *MachoContext) CollectBindSymbolsLegacy() []*ImportSymbol {
noLazy := (func() []*ImportSymbol {
start := mc.dyldinfo.bind_off
size := mc.dyldinfo.bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "no lazy")
})()
lazy := (func() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "lazy")
})()
weak := (func() []*ImportSymbol {
start := mc.dyldinfo.weak_bind_off
size := mc.dyldinfo.weak_bind_size
end := start + size
buf := bytes.NewBuffer(mc.buf[start:end])
return mc.readBindStream(buf, "weak")
})()
var symbols []*ImportSymbol
symbols = append(symbols, noLazy...)
symbols = append(symbols, lazy...)
symbols = append(symbols, weak...)
return symbols
}
func (mc *MachoContext) readBindStream(buf *bytes.Buffer, typ string) []*ImportSymbol {
size := buf.Len()
if size == 0 {
return []*ImportSymbol{}
}
var syms []*ImportSymbol
var sym ImportSymbol
offset := uint(0)
lastop_done := false
for offset < uint(size) {
d, _ := buf.ReadByte()
op := d & 0xf0
imm := d & 0x0f
log.WithFields(log.Fields{
"op": fmt.Sprintf("0x%x", op),
"imm": fmt.Sprintf("0x%x", imm),
}).Trace("Bytecode")
if op != BIND_OPCODE_DONE && lastop_done {
// symoffset = offset
lastop_done = false
}
switch op {
case BIND_OPCODE_DONE:
lastop_done = true
offset += 1
break
case BIND_OPCODE_DO_BIND:
if sym.name != "" {
new_sym := sym
new_sym.typ = typ
syms = append(syms, &new_sym)
// fmt.Printf("Offset 0x%x: Symbol %+v\n", symoffset, sym)
log.WithFields(log.Fields{
// "offset": fmt.Sprintf("0x%x", symoffset),
"symbol": sym.name,
}).Trace("Bind")
sym.name = ""
sym.address += 8
}
offset += 1
break
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
sym.dylib = string(mc.dylibs[int32(imm)-1].name[:])
offset += 1
break
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
uleb, br := ReadULEB(buf)
sym.dylib = string(mc.dylibs[int32(uleb)-1].name[:])
offset += br
break
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
if imm == 0 {
sym.dylib = "dylib_0"
} else if imm == 0x0f {
sym.dylib = "dylib_-1"
} else if imm == 0x0e {
sym.dylib = "dylib_-2"
}
offset += 1
break
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
sym.name, _ = buf.ReadString(0)
// ReadString input the 0x00 byte to buffer
// while we are string so we can remove that
sym.name = sym.name[:len(sym.name)-1]
offset += uint(len(sym.name))
break
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
uleb, br := ReadULEB(buf)
sym.segment = uint32(imm)
sym.segment_offset = uint32(uleb)
seg := mc.segments[sym.segment]
sym.address = seg.Vmaddr() + uint64(sym.segment_offset)
sym.file_address = seg.Fileoff() + uint64(sym.segment_offset)
offset += br
break
case BIND_OPCODE_ADD_ADDR_ULEB:
uleb, br := ReadULEB(buf)
sym.segment_offset += uint32(uleb)
sym.address += uint64(uleb)
sym.file_address += uint64(uleb)
offset += br
break
case BIND_OPCODE_SET_TYPE_IMM:
fmt.Println("// symbol type", imm)
break
default:
fmt.Println("BIND OPCODE NOT SUPPORTED", op, imm)
break
}
}
for _, sym := range syms {
switch mc.ArchName() {
case "armv7", "armv7s":
var addr uint32
b := mc.buf[sym.file_address : sym.file_address+4]
r := bytes.NewBuffer(b)
binary.Read(r, mc.byteorder, &addr)
sym.stub = uint64(addr)
break
case "arm64", "arm64e":
b := mc.buf[sym.file_address : sym.file_address+8]
r := bytes.NewBuffer(b)
binary.Read(r, mc.byteorder, &sym.stub)
break
default:
sym.pnum = 0
sym.stub = 0
}
}
return syms
}