364 lines
8.5 KiB
Go
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
|
|
}
|