284 lines
6.3 KiB
Go
284 lines
6.3 KiB
Go
package macho
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"os"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
. "ios-wrapper/pkg/ios"
|
|
)
|
|
|
|
// A Mach-O binary context
|
|
// holds byteorder, pointersize, header
|
|
// And load commands
|
|
type MachoContext struct {
|
|
file *os.File // backed file enabled write
|
|
buf []byte // backed up data
|
|
byteorder binary.ByteOrder
|
|
pointersize uint32
|
|
entryoff uint64
|
|
|
|
header Header
|
|
commands []LoadCommand
|
|
rpaths []*RPath
|
|
dylibs []*Dylib
|
|
linkedits []*LinkEdit
|
|
segments []Segment
|
|
symtab *Symtab
|
|
dysymtab *DySymtab
|
|
|
|
dyldinfo *DyldInfo
|
|
fixups *LinkEdit
|
|
exports *LinkEdit
|
|
}
|
|
|
|
func (mc *MachoContext) FileSize() uint32 {
|
|
return uint32(len(mc.buf))
|
|
}
|
|
|
|
func (mc *MachoContext) Header() *Header {
|
|
return &mc.header
|
|
}
|
|
|
|
func (mc *MachoContext) Commands() []LoadCommand {
|
|
return mc.commands
|
|
}
|
|
|
|
func (mc *MachoContext) Rpaths() []*RPath {
|
|
return mc.rpaths
|
|
}
|
|
|
|
func (mc *MachoContext) Dylibs() []*Dylib {
|
|
return mc.dylibs
|
|
}
|
|
|
|
func (mc *MachoContext) Linkedits() []*LinkEdit {
|
|
return mc.linkedits
|
|
}
|
|
|
|
func (mc *MachoContext) Segments() []Segment {
|
|
return mc.segments
|
|
}
|
|
|
|
func (mc *MachoContext) WriteEnabled() bool {
|
|
return mc.file != nil
|
|
}
|
|
|
|
func (mc *MachoContext) WriteBufferTo(w io.Writer) (int, error) {
|
|
return w.Write(mc.buf)
|
|
}
|
|
|
|
// Parse the provided Mach-O binary from a file
|
|
// The first 4 bytes of the file must be the MachO magic
|
|
// That is:
|
|
//
|
|
// file.Seek(0, io.SeekStart)
|
|
// magic := make([]byte, 4)
|
|
// file.Read(magic)
|
|
// assert magic == []byte{macho magic bytes}
|
|
//
|
|
// or else, parsing error is panic
|
|
func (mc *MachoContext) ParseFile(file *os.File, length int) error {
|
|
file.Seek(0, io.SeekStart)
|
|
mc.file = file
|
|
|
|
// save the file content to buf
|
|
if length == 0 {
|
|
last, _ := mc.file.Seek(0, io.SeekEnd)
|
|
mc.buf = make([]byte, last)
|
|
mc.file.Seek(0, io.SeekStart)
|
|
} else {
|
|
mc.buf = make([]byte, length)
|
|
}
|
|
|
|
if _, err := io.ReadFull(mc.file, mc.buf); err != nil {
|
|
return err
|
|
}
|
|
mc.file.Seek(0, io.SeekStart) // reset peek, safety reason
|
|
r := bufio.NewReader(file)
|
|
return mc.Parse(r)
|
|
}
|
|
|
|
// A macho context can be made from a buffer
|
|
// In this case write is disable
|
|
// Every information accessed will be through
|
|
// this byte buffer
|
|
func (mc *MachoContext) ParseBuffer(buf []byte) error {
|
|
mc.file = nil
|
|
mc.buf = buf
|
|
r := bytes.NewReader(buf)
|
|
rr := bufio.NewReader(r)
|
|
return mc.Parse(rr)
|
|
}
|
|
|
|
// Parse the provided Mach-O binary from a buffer
|
|
func (mc *MachoContext) Parse(r *bufio.Reader) error {
|
|
{ // read magic to define byteorder and pointersize
|
|
var magic uint32
|
|
magic_buf, _ := r.Peek(4)
|
|
magic_r := bytes.NewReader(magic_buf)
|
|
binary.Read(magic_r, binary.LittleEndian, &magic)
|
|
|
|
if magic != Magic32 && magic != Magic64 && magic != Cigam32 &&
|
|
magic != Cigam64 {
|
|
log.WithFields(log.Fields{
|
|
"magic": magic,
|
|
}).Error("Magic for Mach-O does not match")
|
|
return &ParseError{
|
|
Expect: "Magic for Mach-O does not match",
|
|
}
|
|
}
|
|
|
|
if magic == Magic32 || magic == Magic64 {
|
|
mc.byteorder = binary.LittleEndian
|
|
} else {
|
|
mc.byteorder = binary.BigEndian
|
|
}
|
|
|
|
if magic == Magic32 {
|
|
mc.pointersize = 4
|
|
} else {
|
|
mc.pointersize = 8
|
|
}
|
|
}
|
|
|
|
mc.header.Deserialize(mc, r)
|
|
|
|
for i := uint32(0); i < mc.header.ncmds; i++ {
|
|
var cmd uint32
|
|
var cmdsize uint32
|
|
load_buf, _ := r.Peek(8)
|
|
load_r := bytes.NewReader(load_buf)
|
|
|
|
binary.Read(load_r, mc.byteorder, &cmd)
|
|
binary.Read(load_r, mc.byteorder, &cmdsize)
|
|
log.WithFields(log.Fields{
|
|
"nth": i,
|
|
"cmd": cmd,
|
|
"cmdsize": cmdsize,
|
|
}).Trace("Load command")
|
|
|
|
command_buf := make([]byte, cmdsize)
|
|
if bytesread, err := io.ReadFull(r, command_buf); err != nil ||
|
|
uint32(bytesread) != cmdsize {
|
|
log.WithFields(log.Fields{
|
|
"nth": i,
|
|
"cmdsize": cmdsize,
|
|
"bytesread": bytesread,
|
|
}).Error("Cannot read load command")
|
|
return &ParseError{
|
|
Expect: "Cannot read load command",
|
|
}
|
|
}
|
|
|
|
switch cmd {
|
|
case LC_RPATH:
|
|
lcmd := new(RPath)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.rpaths = append(mc.rpaths, lcmd)
|
|
break
|
|
|
|
case LC_ID_DYLIB,
|
|
LC_LOAD_DYLIB,
|
|
LC_LAZY_LOAD_DYLIB,
|
|
LC_REEXPORT_DYLIB,
|
|
LC_LOAD_WEAK_DYLIB:
|
|
lcmd := new(Dylib)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
if cmd != LC_ID_DYLIB {
|
|
mc.dylibs = append(mc.dylibs, lcmd)
|
|
}
|
|
break
|
|
|
|
case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
|
|
lcmd := new(EncryptionInfo)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
break
|
|
|
|
case LC_SEGMENT:
|
|
lcmd := new(Segment32)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.segments = append(mc.segments, lcmd)
|
|
break
|
|
|
|
case LC_SEGMENT_64:
|
|
lcmd := new(Segment64)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.segments = append(mc.segments, lcmd)
|
|
break
|
|
|
|
case LC_MAIN:
|
|
lcmd := new(EntryPoint)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.entryoff = lcmd.entryoff
|
|
break
|
|
|
|
case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
|
|
lcmd := new(DyldInfo)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.dyldinfo = lcmd
|
|
break
|
|
|
|
case LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_CODE_SIGNATURE, LC_DYLD_CHAINED_FIXUPS, LC_DYLD_EXPORTS_TRIE:
|
|
lcmd := new(LinkEdit)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.linkedits = append(mc.linkedits, lcmd)
|
|
if lcmd.Cmd() == LC_DYLD_CHAINED_FIXUPS {
|
|
mc.fixups = lcmd
|
|
}
|
|
if lcmd.Cmd() == LC_DYLD_EXPORTS_TRIE {
|
|
mc.exports = lcmd
|
|
}
|
|
break
|
|
|
|
case LC_SYMTAB:
|
|
lcmd := new(Symtab)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.symtab = lcmd
|
|
|
|
case LC_DYSYMTAB:
|
|
lcmd := new(DySymtab)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
mc.dysymtab = lcmd
|
|
|
|
default:
|
|
lcmd := new(LoadCmd)
|
|
lcmd.Deserialize(mc, command_buf)
|
|
mc.commands = append(mc.commands, lcmd)
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mc *MachoContext) Is64bit() bool {
|
|
return mc.pointersize == 8
|
|
}
|
|
|
|
func (mc *MachoContext) PointerSize() uint32 {
|
|
return mc.pointersize
|
|
}
|
|
|
|
func (mc *MachoContext) ImageBase() uint64 {
|
|
for _, segment := range mc.segments {
|
|
if segment.Fileoff() == 0 && segment.Filesize() != 0 {
|
|
return segment.Vmaddr()
|
|
}
|
|
}
|
|
return 0
|
|
}
|