macho/macho-go/pkg/ios/macho/macho.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
}