add old go tooling

This commit is contained in:
2023-05-31 16:17:03 +07:00
commit 54f1f3eb38
57 changed files with 5791 additions and 0 deletions

305
macho-go/pkg/dwarf/dwarf.go Normal file
View File

@ -0,0 +1,305 @@
package dwarf
import (
"bytes"
"encoding/binary"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/leb128"
)
type header struct {
unit_length uint32
version uint16
header_length uint32
minimum_instruction_length uint8
maximum_operations_per_instruction uint8
default_is_stmt uint8
line_base int8
line_range uint8
opcode_base uint8
}
type register struct {
address uint64
op_index uint32
file uint32
line uint32
column uint32
is_stmt bool
basic_block bool
end_sequence bool
prologue_end bool
epilogue_begin bool
isa uint32
discriminator uint32
}
func initRegister(is_stmt bool) register {
var reg register
reg.is_stmt = is_stmt
reg.file = 1
reg.line = 1
return reg
}
func (reg *register) reset() {
reg.basic_block = false
reg.prologue_end = false
reg.epilogue_begin = false
reg.discriminator = 0
}
// Opcodes
const (
Copy uint8 = 1
AdvancePc uint8 = 2
AdvanceLine uint8 = 3
SetFile uint8 = 4
SetColumn uint8 = 5
NegateStmt uint8 = 6
SetBasicBlock uint8 = 7
ConstAddPc uint8 = 8
FixedAdvancePc uint8 = 9
SetPrologueEnd uint8 = 10
SetEpiloqueBegin uint8 = 11
SetIsa uint8 = 12
// Extended opcodes
EndSequence uint8 = 1
SetAddress uint8 = 2
DefineFile uint8 = 3
SetDiscriminator uint8 = 4
)
type row struct {
address uint64
op_index uint32
directory string
file string
line int32
column int32
end_sequence bool
}
type filename struct {
name string
dir uint
}
type DebuglineParser struct {
buf *bytes.Buffer
byteorder binary.ByteOrder
header header
standard_opcode_lengths []uint8
include_directories []string
file_names []filename
row []row
}
func (parser *DebuglineParser) Find(address uint64) (bool, uint32, string, string) {
for _, row := range parser.row {
if address == row.address {
return true, uint32(row.line), row.directory, row.file
}
}
return false, 0, "", ""
}
func (parser *DebuglineParser) Parse(buf *bytes.Buffer, byteorder binary.ByteOrder) {
parser.buf = buf
parser.byteorder = byteorder
parser.parseHeader()
parser.parseOpcode()
parser.parseDirectoryTable()
parser.parseFilenameTable()
parser.parseStatementProgram()
}
func (parser *DebuglineParser) parseHeader() {
binary.Read(parser.buf, parser.byteorder, &parser.header.unit_length)
binary.Read(parser.buf, parser.byteorder, &parser.header.version)
binary.Read(parser.buf, parser.byteorder, &parser.header.header_length)
binary.Read(parser.buf, parser.byteorder, &parser.header.minimum_instruction_length)
if parser.header.version >= 4 {
binary.Read(parser.buf, parser.byteorder, &parser.header.maximum_operations_per_instruction)
}
binary.Read(parser.buf, parser.byteorder, &parser.header.default_is_stmt)
binary.Read(parser.buf, parser.byteorder, &parser.header.line_base)
binary.Read(parser.buf, parser.byteorder, &parser.header.line_range)
binary.Read(parser.buf, parser.byteorder, &parser.header.opcode_base)
}
func (parser *DebuglineParser) parseOpcode() {
var standard_opcode_lengths []uint8
for i := uint8(1); i < parser.header.opcode_base; i++ {
var num uint8
binary.Read(parser.buf, parser.byteorder, &num)
standard_opcode_lengths = append(standard_opcode_lengths, num)
}
parser.standard_opcode_lengths = standard_opcode_lengths
}
func (parser *DebuglineParser) parseDirectoryTable() {
var include_directories []string
for {
dir, _ := parser.buf.ReadString(0)
if dir == "\x00" {
break
}
include_directories = append(include_directories, dir)
}
parser.include_directories = include_directories
}
func (parser *DebuglineParser) parseFilenameTable() {
var file_names []filename
for {
file, _ := parser.buf.ReadString(0)
if file == "\x00" {
break
}
dir, _ := ReadULEB(parser.buf) // directory index
ReadULEB(parser.buf) // time
ReadULEB(parser.buf) // length
file_names = append(file_names, filename{
name: file,
dir: dir,
})
}
parser.file_names = file_names
}
func (parser *DebuglineParser) parseStatementProgram() {
reg := initRegister(parser.header.default_is_stmt == 1)
runloop:
for {
var opcode uint8
binary.Read(parser.buf, parser.byteorder, &opcode)
if opcode >= parser.header.opcode_base {
adjusted_opcode := opcode - parser.header.opcode_base
operation_advance := adjusted_opcode / parser.header.line_range
increment_address_and_op_index(&reg, &parser.header, uint(operation_advance))
reg.line += uint32(int32(parser.header.line_base) + int32(adjusted_opcode%parser.header.line_range))
parser.register_to_matrix(&reg)
reg.reset()
} else if opcode == 0 {
size_, _ := ReadULEB(parser.buf)
size := size_ - 1
var extended_opcode uint8
binary.Read(parser.buf, parser.byteorder, &extended_opcode)
switch extended_opcode {
case EndSequence:
reg.end_sequence = true
parser.register_to_matrix(&reg)
reg = initRegister(parser.header.default_is_stmt == 1)
break runloop
case SetAddress:
if size == 8 {
binary.Read(parser.buf, parser.byteorder, &reg.address)
} else if size == 4 {
var v uint32
binary.Read(parser.buf, parser.byteorder, &v)
reg.address = uint64(v)
} else {
parser.buf.Next(int(size))
}
reg.op_index = 0
case SetDiscriminator:
v, _ := ReadULEB(parser.buf)
reg.discriminator = uint32(v)
default:
parser.buf.Next(int(size))
}
} else {
switch opcode {
case Copy:
parser.register_to_matrix(&reg)
reg.reset()
case AdvancePc:
v, _ := ReadULEB(parser.buf)
increment_address_and_op_index(&reg, &parser.header, v)
case AdvanceLine:
v, _ := ReadSLEB(parser.buf)
if v < 0 {
reg.line -= uint32(-v)
} else {
reg.line += uint32(v)
}
case SetFile:
v, _ := ReadULEB(parser.buf)
reg.file = uint32(v)
case SetColumn:
v, _ := ReadULEB(parser.buf)
reg.column = uint32(v)
case NegateStmt:
reg.is_stmt = !reg.is_stmt
case SetBasicBlock:
reg.basic_block = true
case ConstAddPc:
adjusted_opcode := 255 - parser.header.opcode_base
operation_advance := adjusted_opcode / parser.header.line_range
increment_address_and_op_index(&reg, &parser.header, uint(operation_advance))
case FixedAdvancePc:
var v uint16
binary.Read(parser.buf, parser.byteorder, &v)
reg.address += uint64(v)
reg.op_index = 0
case SetPrologueEnd:
reg.prologue_end = true
case SetEpiloqueBegin:
reg.epilogue_begin = true
case SetIsa:
v, _ := ReadULEB(parser.buf)
reg.isa = uint32(v)
default:
break
}
}
}
}
func increment_address_and_op_index(reg *register, h *header, operation_advance uint) {
min_length := uint64(h.minimum_instruction_length)
max_op := uint64(h.maximum_operations_per_instruction)
advance := uint64(operation_advance)
op_index := uint64(reg.op_index)
if h.maximum_operations_per_instruction == 1 {
reg.address += advance * min_length
} else {
reg.address += min_length * ((op_index + advance) / max_op)
reg.op_index = uint32((op_index + advance) % max_op)
}
}
func (parser *DebuglineParser) register_to_matrix(reg *register) {
if !(reg.is_stmt || (reg.line > 0 && reg.column > 0)) {
return
}
file := parser.file_names[reg.file-1]
path := parser.include_directories[file.dir-1]
row := row{
address: reg.address,
op_index: reg.op_index,
directory: path,
file: file.name,
line: int32(reg.line),
column: int32(reg.column),
end_sequence: reg.end_sequence,
}
parser.row = append(parser.row, row)
log.WithFields(log.Fields{
"address": row.address,
"directory": row.directory,
"file": row.file,
"line": row.line,
}).Trace("Debugline Row")
}

159
macho-go/pkg/ios/const.go Normal file
View File

@ -0,0 +1,159 @@
package ios
// Magic values of Mach-O and Fat binaries
const (
Magic32 uint32 = 0xfeedface
Cigam32 uint32 = 0xcefaedfe
Magic64 uint32 = 0xfeedfacf
Cigam64 uint32 = 0xcffaedfe
MagicFat uint32 = 0xcafebabe
CigamFat uint32 = 0xbebafeca
)
// Flags defined in mach_header.flags
const (
FlagNoUndefs uint32 = 0x1
FlagIncrLink uint32 = 0x2
FlagDyldLink uint32 = 0x4
FlagBindAtLoad uint32 = 0x8
FlagPrebound uint32 = 0x10
FlagSplitSegs uint32 = 0x20
FlagLazyInit uint32 = 0x40
FlagTwoLevel uint32 = 0x80
FlagForceFlat uint32 = 0x100
FlagNoMultiDefs uint32 = 0x200
FlagNoFixPrebinding uint32 = 0x400
FlagPrebindable uint32 = 0x800
FlagAllModsBound uint32 = 0x1000
FlagSubsectionsViaSymbols uint32 = 0x2000
FlagCanonical uint32 = 0x4000
FlagWeakDefines uint32 = 0x8000
FlagBindsToWeak uint32 = 0x10000
FlagAllowStackExecution uint32 = 0x20000
FlagRootSafe uint32 = 0x40000
FlagSetuidSafe uint32 = 0x80000
FlagNoReexportedDylibs uint32 = 0x100000
FlagPIE uint32 = 0x200000
FlagDeadStrippableDylib uint32 = 0x400000
FlagHasTLVDescriptors uint32 = 0x800000
FlagNoHeapExecution uint32 = 0x1000000
FlagAppExtensionSafe uint32 = 0x2000000
)
// Types of Load command
const (
LC_DUMMY uint32 = 0x0
LC_SEGMENT uint32 = 0x1
LC_SYMTAB uint32 = 0x2
LC_SYMSEG uint32 = 0x3
LC_THREAD uint32 = 0x4
LC_UNIXTHREAD uint32 = 0x5
LC_LOADFVMLIB uint32 = 0x6
LC_IDFVMLIB uint32 = 0x7
LC_IDENT uint32 = 0x8
LC_FVMFILE uint32 = 0x9
LC_PREPAGE uint32 = 0xa
LC_DYSYMTAB uint32 = 0xb
LC_LOAD_DYLIB uint32 = 0xc
LC_ID_DYLIB uint32 = 0xd
LC_LOAD_DYLINKER uint32 = 0xe
LC_ID_DYLINKER uint32 = 0xf
LC_PREBOUND_DYLIB uint32 = 0x10
LC_ROUTINES uint32 = 0x11
LC_SUB_FRAMEWORK uint32 = 0x12
LC_SUB_UMBRELLA uint32 = 0x13
LC_SUB_CLIENT uint32 = 0x14
LC_SUB_LIBRARY uint32 = 0x15
LC_TWOLEVEL_HINTS uint32 = 0x16
LC_PREBIND_CKSUM uint32 = 0x17
LC_LOAD_WEAK_DYLIB uint32 = 0x18 | 0x80000000
LC_SEGMENT_64 uint32 = 0x19
LC_ROUTINES_64 uint32 = 0x1a
LC_UUID uint32 = 0x1b
LC_RPATH uint32 = 0x1c | 0x80000000
LC_CODE_SIGNATURE uint32 = 0x1d
LC_SEGMENT_SPLIT_INFO uint32 = 0x1e
LC_REEXPORT_DYLIB uint32 = 0x1f | 0x80000000
LC_LAZY_LOAD_DYLIB uint32 = 0x20
LC_ENCRYPTION_INFO uint32 = 0x21
LC_DYLD_INFO uint32 = 0x22
LC_DYLD_INFO_ONLY uint32 = 0x22 | 0x80000000
LC_VERSION_MIN_MACOSX uint32 = 0x24
LC_VERSION_MIN_IPHONEOS uint32 = 0x25
LC_FUNCTION_STARTS uint32 = 0x26
LC_DYLD_ENVIRONMENT uint32 = 0x27
LC_MAIN uint32 = 0x28 | 0x80000000
LC_DATA_IN_CODE uint32 = 0x29
LC_SOURCE_VERSION uint32 = 0x2A
LC_DYLIB_CODE_SIGN_DRS uint32 = 0x2B
LC_ENCRYPTION_INFO_64 uint32 = 0x2C
LC_LINKER_OPTION uint32 = 0x2D
LC_LINKER_OPTIMIZATION_HINT uint32 = 0x2E
LC_VERSION_MIN_TVOS uint32 = 0x2F
LC_VERSION_MIN_WATCHOS uint32 = 0x30
LC_BUILD_VERSION uint32 = 0x32
LC_DYLD_EXPORTS_TRIE uint32 = 0x33 | 0x80000000
LC_DYLD_CHAINED_FIXUPS uint32 = 0x34 | 0x80000000
)
const (
Header_size uint64 = 28
Header_size_64 uint64 = 28 + 4
)
const (
CPU_ARCH_ABI64 uint32 = 0x0100_0000
CPU_TYPE_ARM uint32 = 12
CPU_TYPE_ARM64 uint32 = CPU_TYPE_ARM | CPU_ARCH_ABI64
CPU_TYPE_POWERPC uint32 = 18
CPU_TYPE_POWERPC64 uint32 = CPU_TYPE_POWERPC | CPU_ARCH_ABI64
CPU_TYPE_I386 uint32 = 7
CPU_TYPE_X86_64 uint32 = CPU_TYPE_I386 | CPU_ARCH_ABI64
)
const (
// Section Type
S_REGULAR uint8 = 0x0
S_ZEROFILL uint8 = 0x1
S_CSTRING_LITERALS uint8 = 0x2
S_4BYTE_LITERALS uint8 = 0x3
S_8BYTE_LITERALS uint8 = 0x4
S_LITERAL_POINTERS uint8 = 0x5
S_NON_LAZY_SYMBOL_POINTERS uint8 = 0x6
S_LAZY_SYMBOL_POINTERS uint8 = 0x7
S_SYMBOL_STUBS uint8 = 0x8
S_MOD_INIT_FUNC_POINTERS uint8 = 0x9
S_MOD_TERM_FUNC_POINTERS uint8 = 0xa
S_COALESCED uint8 = 0xb
S_GB_ZEROFILL uint8 = 0xc
S_INTERPOSING uint8 = 0xd
S_16BYTE_LITERALS uint8 = 0xe
S_DTRACE_DOF uint8 = 0xf
S_LAZY_DYLIB_SYMBOL_POINTERS uint8 = 0x10
S_THREAD_LOCAL_REGULAR uint8 = 0x11
S_THREAD_LOCAL_ZEROFILL uint8 = 0x12
S_THREAD_LOCAL_VARIABLES uint8 = 0x13
S_THREAD_LOCAL_VARIABLE_POINTERS uint8 = 0x14
S_THREAD_LOCAL_INIT_FUNCTION_POINTERS uint8 = 0x15
)
const (
// Section Attribute
)
const (
BIND_OPCODE_DONE uint8 = 0x00
BIND_OPCODE_SET_DYLIB_ORDINAL_IMM uint8 = 0x10
BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB uint8 = 0x20
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM uint8 = 0x30
BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM uint8 = 0x40
BIND_OPCODE_SET_TYPE_IMM uint8 = 0x50
BIND_OPCODE_SET_ADDEND_SLEB uint8 = 0x60
BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB uint8 = 0x70
BIND_OPCODE_ADD_ADDR_ULEB uint8 = 0x80
BIND_OPCODE_DO_BIND uint8 = 0x90
BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB uint8 = 0xA0
BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED uint8 = 0xB0
BIND_OPCODE_DO_BIND_ADD_ULEB_TIMES_SKIPPING_ULEB uint8 = 0xC0
)

View File

@ -0,0 +1,184 @@
/// Contains the declaration of FatHeader and FatArch
/// These structs are always written using Big-Endian,
/// as documented in the mach-o/fat.h
/// This file also has a CreateFat function to generate
/// Fat file from a list of MachoContext
package fat
import (
"io"
"os"
"sort"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
macho "ios-wrapper/pkg/ios/macho"
)
/// Get the alignment for the Mach-O in Fat binary
/// The returned value is the multiplier of 2
func GetAlignment(h *macho.Header) uint32 {
switch h.Cputype() {
case CPU_TYPE_ARM, CPU_TYPE_ARM64:
return 0xe // log2(0x4000)
case CPU_TYPE_POWERPC,
CPU_TYPE_POWERPC64,
CPU_TYPE_I386,
CPU_TYPE_X86_64:
return 0xc // log2(0x1000)
default:
return 0xd // log2(0x2000)
}
}
func MachosToFatArchs(machos []*macho.MachoContext) []*FatArch {
var fa []*FatArch
for _, m := range machos {
fa = append(fa, &FatArch{
Cputype: m.Header().Cputype(),
Cpusubtype: m.Header().Cpusubtype(),
Size: m.FileSize(),
Offset: 0,
Align: GetAlignment(m.Header()),
})
}
return fa
}
/// Create a Fat binary from MachoContext
/// Convert MachoContext to FatArch
/// Calculate the alignment, now using basic calculation
/// because iOS always has valid archs ARM
///
/// Sort the Fat arch as per the cctools/lipo
/// Calculate the offset with each Mach-O
///
/// Write the FatHeader
/// Write the FatArchs
/// Write the Mach-Os
func CreateFat(machos []*macho.MachoContext, outfilename string) error {
archs := MachosToFatArchs(machos)
sort.SliceStable(archs, func(i, j int) bool {
if archs[i].Cputype == archs[j].Cputype {
return archs[i].Cpusubtype < archs[i].Cpusubtype
}
if archs[i].Cputype == CPU_TYPE_ARM64 {
return false
}
if archs[j].Cputype == CPU_TYPE_ARM64 {
return true
}
return archs[i].Cpusubtype < archs[j].Cpusubtype
})
// calculate the offset for each FatArch
offset := uint32(8) // size of fat header, bytes
// offset to the first macho
offset += uint32(len(archs)) * 5 // size of fat_arch, bytes
for _, arch := range archs {
offset = uint32(OffsetRounder(uint64(offset), 1<<arch.Align))
arch.Offset = offset
offset += arch.Size
log.WithFields(log.Fields{
"cputype": arch.Cputype,
"cpusubtype": arch.Cpusubtype,
"size": arch.Size,
"offset": arch.Offset,
"algin": arch.Align,
}).Debug("Arch to add")
}
file, err := os.OpenFile(outfilename, os.O_CREATE|os.O_WRONLY, 0777)
if err != nil {
return err
// log.Fatal("Cannot open output file")
}
var w fatWriter
w.WriteFatHeader(file, &FatHeader{
Magic: MagicFat,
Nfat_arch: uint32(len(archs)),
})
w.WriteFatArchs(file, archs)
w.WriteMachos(file, archs, machos)
return w.err
}
type fatWriter struct {
err error
}
func (fw *fatWriter) WriteFatHeader(w io.Writer, h *FatHeader) {
if fw.err != nil {
return
}
b := h.Serialize()
_, err := w.Write(b)
fw.err = err
}
func (fw *fatWriter) WriteFatArchs(w io.Writer, archs []*FatArch) {
for _, arch := range archs {
if fw.err != nil {
return
}
b := arch.Serialize()
_, err := w.Write(b)
fw.err = err
log.WithFields(log.Fields{
"cputype": arch.Cputype,
"cpusubtype": arch.Cpusubtype,
"size": arch.Size,
"offset": arch.Offset,
"algin": arch.Align,
}).Info("Attempt to write Fat Arch")
}
}
/// for each fat arch sorted, we locate the MachoContext and
/// use it to Write to the buffer
/// locating the macho by its cputype and cpusubtype
func (fw *fatWriter) WriteMachos(
w io.WriteSeeker,
archs []*FatArch,
machos []*macho.MachoContext,
) {
for _, arch := range archs {
for _, macho := range machos {
if fw.err != nil {
return
}
cputype := macho.Header().Cputype()
cpusubtype := macho.Header().Cpusubtype()
if cputype == arch.Cputype &&
cpusubtype == arch.Cpusubtype {
log.WithFields(log.Fields{
"cputype": cputype,
"cpusubtype": cpusubtype,
"offset": arch.Offset,
"size": arch.Size,
}).Info("Attempt to write Mach-O to file")
_, err1 := w.Seek(int64(arch.Offset), io.SeekStart)
_, err2 := macho.WriteBufferTo(w)
if err1 != nil {
fw.err = err1
} else if err2 != nil {
fw.err = err2
}
}
}
}
}
func OffsetRounder(v, r uint64) uint64 {
r--
v += r
v &= uint64(^int64(r))
return v
}

110
macho-go/pkg/ios/fat/fat.go Normal file
View File

@ -0,0 +1,110 @@
package fat
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"os"
. "ios-wrapper/pkg/ios"
"ios-wrapper/pkg/ios/macho"
)
type FatHeader struct {
Magic uint32
Nfat_arch uint32
}
func (h *FatHeader) Serialize() []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, h.Magic)
binary.Write(buf, binary.BigEndian, h.Nfat_arch)
return buf.Bytes()
}
type FatArch struct {
Cputype uint32
Cpusubtype uint32
Offset uint32
Size uint32
Align uint32
}
func (arch *FatArch) Serialize() []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, arch.Cputype)
binary.Write(buf, binary.BigEndian, arch.Cpusubtype)
binary.Write(buf, binary.BigEndian, arch.Offset)
binary.Write(buf, binary.BigEndian, arch.Size)
binary.Write(buf, binary.BigEndian, arch.Align)
return buf.Bytes()
}
// don't know when to use
// it seems that only use if the size of any file is > 4GB
// else just use FatArch
type FatArch64 struct {
Cputype uint32
Cpusubtype uint32
Offset uint64
Size uint64
Align uint32
}
type FatContext struct {
fatArch []*FatArch
}
func (fc *FatContext) ParseFile(file *os.File) {
r := bufio.NewReader(file)
{ // read magic to define byteorder and pointersize
var magic uint32
magic_buf := make([]byte, 4)
r.Read(magic_buf)
magic_r := bytes.NewReader(magic_buf)
binary.Read(magic_r, binary.BigEndian, &magic)
if magic != MagicFat {
return
}
}
var nfat_arch uint32
binary.Read(r, binary.BigEndian, &nfat_arch)
for i := uint32(0); i < nfat_arch; i++ {
var fat_arch FatArch
binary.Read(r, binary.BigEndian, &fat_arch.Cputype)
binary.Read(r, binary.BigEndian, &fat_arch.Cpusubtype)
binary.Read(r, binary.BigEndian, &fat_arch.Offset)
binary.Read(r, binary.BigEndian, &fat_arch.Size)
binary.Read(r, binary.BigEndian, &fat_arch.Align)
fc.fatArch = append(fc.fatArch, &fat_arch)
}
}
func (fc *FatContext) Machos() []*FatArch {
return fc.fatArch
}
func FatSplit(path string) ([]string, error) {
return CreateMachosFromFat(path)
}
// Parse files into Mach-O, calculate fat_arch and join
// into a Fat binary
// @paths: paths to Mach-O binaries
// @out: output Fat file path
// returns success?
func FatJoin(paths []string, out string) error {
machos, err := macho.MachosFromFiles(paths)
if err != nil {
return err
}
if macho.CheckDuplicateArch(machos) {
return errors.New("Duplicate Arch")
}
return CreateFat(machos, out)
}

View File

@ -0,0 +1,62 @@
package fat
import (
"fmt"
"io"
"os"
"strings"
)
// Split a Fat binary into multiple Mach-O binaries
// @ifile: path to Fat binary
// returns a list of file path of splited Mach-O binaries
func CreateMachosFromFat(ifile string) ([]string, error) {
f, err := os.OpenFile(ifile, os.O_RDONLY, 0644)
if err != nil {
fmt.Println("Cannot open file")
return []string{}, err
}
var fc FatContext
fc.ParseFile(f)
var r []string
for _, arch := range fc.Machos() {
offset := arch.Offset
size := arch.Size
buf := make([]byte, size)
filename := fmt.Sprintf(
"%s_%x",
strings.ReplaceAll(ifile, " ", "_"),
offset,
)
var err error
_, err = f.Seek(int64(offset), io.SeekStart)
if err != nil {
return r, err
}
_, err = f.Read(buf)
if err != nil {
return r, err
}
outfile, err := os.OpenFile(
filename,
os.O_CREATE|os.O_WRONLY,
0777,
)
if err != nil {
return r, err
}
_, err = outfile.Write(buf)
if err != nil {
return r, err
}
r = append(r, filename) // everything is find, append new file path
}
return r, nil
}

View File

@ -0,0 +1,55 @@
package macho
import (
"bytes"
"ios-wrapper/pkg/dwarf"
)
type DebuglineInfo struct {
debuglines []*dwarf.DebuglineParser
}
type LineInfo struct {
Line uint32
Directory string
File string
}
func (dinfo *DebuglineInfo) Find(address uint64) *LineInfo {
for _, debugline := range dinfo.debuglines {
found, line, directory, file := debugline.Find(address)
if found {
return &LineInfo{
line, directory, file,
}
}
}
return nil
}
// Parse the __debug_line into a list of DebugLine
//
// reference from Crystal dwarf parser
// https://github.com/crystal-lang/crystal/blob/421fa11bf05b11441b74bbeb2762b673f96fec3f/src/crystal/dwarf/line_numbers.cr
func (mc *MachoContext) DebugLineInfo() *DebuglineInfo {
debug_line_section := mc.FindSection("__debug_line")
if debug_line_section == nil {
return nil
}
start := uint64(debug_line_section.Offset())
end := start + debug_line_section.Size()
buf := bytes.NewBuffer(mc.buf[start:end])
debugLine := []*dwarf.DebuglineParser{}
for buf.Len() != 0 {
parser := &dwarf.DebuglineParser{}
parser.Parse(buf, mc.byteorder)
debugLine = append(debugLine, parser)
}
return &DebuglineInfo{
debugLine,
}
}

View File

@ -0,0 +1,237 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
. "ios-wrapper/pkg/leb128"
)
type ImportSymbol struct {
name string
dylib string
segment uint32
segment_offset uint32
address uint64
file_address uint64
// push number
pnum uint32
stub uint64
}
func (sym *ImportSymbol) Name() string {
return sym.name
}
func (sym *ImportSymbol) Dylib() string {
return sym.dylib
}
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 (mc *MachoContext) CollectLazyBindSymbols() []*ImportSymbol {
if mc.dyldinfo == nil {
return mc.CollectLazyBindSymbolsModern()
} else {
return mc.CollectLazyBindSymbolsLegacy()
}
}
// New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectLazyBindSymbolsModern() []*ImportSymbol {
var buf []byte
for _, cmd := range mc.Linkedits() {
if (cmd.Cmd() != LC_DYLD_CHAINED_FIXUPS) {
continue
}
count_offset := int64(cmd.Dataoff()) + 16
mc.file.WriteAt([]byte{0, 0, 0, 0}, count_offset)
symbol_offset := int64(0x4000)
mc.file.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0}, symbol_offset)
buf = mc.buf[cmd.Dataoff():cmd.Dataoff() + cmd.Datasize()]
break
}
r := bytes.NewReader(buf)
rr := bufio.NewReader(r)
var fixups_version int32
var starts_offset int32
var imports_offset int32
var symbols_offset int32
var imports_count int32
var imports_format int32
var symbols_format int32
binary.Read(rr, mc.byteorder, &fixups_version)
binary.Read(rr, mc.byteorder, &starts_offset)
binary.Read(rr, mc.byteorder, &imports_offset)
binary.Read(rr, mc.byteorder, &symbols_offset)
binary.Read(rr, mc.byteorder, &imports_count)
binary.Read(rr, mc.byteorder, &imports_format)
binary.Read(rr, mc.byteorder, &symbols_format)
fmt.Printf(
"HMMGE %x %x\n",
imports_offset,
imports_count,
)
return []*ImportSymbol{}
}
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner
func (mc *MachoContext) CollectLazyBindSymbolsLegacy() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
end := start + size
if size == 0 {
return []*ImportSymbol{}
}
// clear this whole section to 0x00 BIND_OPCODE_DONE
dummy := []byte{
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
}
// make LINK EDIT section writable
// mc.file.WriteAt([]byte{0x03}, int64(0x3f8))
// set number of symbols to 0
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x444))
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x44c))
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x48c))
mc.file.WriteAt(dummy, int64(start))
buf := bytes.NewBuffer(mc.buf[start:end])
offset := uint(0)
var syms []*ImportSymbol
var sym ImportSymbol
// symoffset := offset
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
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 = ""
}
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)
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
default:
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
}

View File

@ -0,0 +1,268 @@
package macho
import (
"fmt"
"io"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
if mc.Is64bit() {
mc.file.Seek(int64(Header_size_64), io.SeekStart)
} else {
mc.file.Seek(int64(Header_size), io.SeekStart)
}
for _, cmd := range mc.commands {
if cmd.Cmd() == LC_CODE_SIGNATURE {
continue
}
log.WithFields(log.Fields{
"cmd": cmd.Cmd(),
"cmdsize": cmd.Cmdsize(),
"name": cmd.Cmdname(),
}).Trace("Rewrite Load command")
mc.file.Write(cmd.Serialize(mc))
}
}
// Remove Codesign data from Mach-O binary
// There are many caveats to this problem.
// Mostly related to how codesign-allocate works.
// Removing codesign data at the end of __LINKEDIT sement
// is probably easier than removing them at an abitrary location.
//
// Assuming the codesign data is at the end of __LINKEDIT segment.
// The binary is probably signed at the last step, which is common.
//
// CODE_SIGNATURE load commands points to the codesign data offset and size.
// __LINKEDIT section points to data offset and size.
// We have:
// linkedit = (section*) LC_SEGMENT.section[0] // name="__LINKEDIT"
// codesign = (linkedit_data_command*) LC_CODE_SIGNATURE
// BinarySize = { f.seek(0, SEEKEND); return f.tell() }
//
// linkedit->fileoff + linkedit->filesize == codesign->dataOff + codesign->dataSize
// linkedit->fileoff + linkedit->filesize == BinarySize
//
// To remove the codesign data, we truncate the file to remove the codesign data.
// Then we update the linkedit->filesize to linkedit->filesize - codesign->dataSize
// We also fix the header to not read the LC_CODE_SIGNATURE,
// because codesign is the last step, the last load command is LC_CODE_SIGNATURE.
// Fix header->ncmds -= 1, header->sizeofcmds -= sizeof(linkedit_data_command)
//
// There's one more caveat, as mentioned by `insert_dylib`. Codesign data is aligned
// by 0x10. So it would have some padding before codesign data. Now that we have
// removed the data, but the padding still exists and is not prefered by codesign-allocate.
// To fix this issue, **again** we assume that the previous section is the String table
// (referenced by symtab) and increase the size of the string table to the end of data.
//
// A better approach could be an extended search for section pointing to the previous data.
// But general cases are String table.
//
// Implementation warnings:
// We have two implementation designs.
// - Re-parse the load commands and edit the value at offset
// - Use parsed load commands data, edit the values, and rewrite header + load commands
func (mc *MachoContext) RemoveCodesign() bool {
if !mc.WriteEnabled() {
return false
}
linkedit := mc.FindSegment("__LINKEDIT")
if linkedit == nil {
log.Warn("The binary has no __LINKEDIT Segment")
return false
}
var codesign *LinkEdit
for _, cmd := range mc.Linkedits() {
if cmd.Cmd() == LC_CODE_SIGNATURE {
codesign = cmd
break
}
}
if codesign == nil {
log.Warn("The binary is not signed, no LC_CODE_SIGNATURE found")
return false
}
filesize := uint64(len(mc.buf))
linkedit_end := uint64(linkedit.Fileoff()) + linkedit.Filesize()
codesign_end := uint64(codesign.Dataoff() + codesign.Datasize())
// linkedit is the last item in Mach-O binary
if linkedit_end != filesize {
log.Panic("Link edit is not the last item")
return false
}
// codesign is the last in linkedit
if linkedit_end != codesign_end {
log.Panic("Code sign data is not the last item in link edit segment")
return false
}
linkedit_newsize := linkedit.Filesize() - uint64(codesign.Datasize())
if (mc.symtab.stroff+mc.symtab.strsize)%0x10 == 0 {
// codesign requires padding of 0x10
// if strtab+strsize & 0xff < 0x8, it will pad til 0x10
// when removed codesign, we truncate the file to offset at 0x8 rather at 0x10
} else {
linkedit_newsize -= 8
}
mc.file.Truncate(int64(uint64(linkedit.Fileoff()) + linkedit_newsize))
mc.symtab.strsize = uint32(linkedit.Fileoff()+linkedit_newsize) - mc.symtab.stroff
// fix linkedit section
if mc.Is64bit() {
linkedit.(*Segment64).filesize = linkedit_newsize
} else {
linkedit.(*Segment32).filesize = uint32(linkedit_newsize)
}
// rewrite load commands
mc.UpdateHeaderRemoveLcmd(codesign.Cmdsize())
rewriteLoadcommandsWithoutCodesignature(mc)
// erase old LC_CODE_SIGNATURE data
old_codesign_lcmd_offset := func() uint64 {
loadcmd_size := int64(mc.Header().sizeofcmds)
if mc.Is64bit() {
return uint64(loadcmd_size) + Header_size_64
} else {
return uint64(loadcmd_size) + Header_size
}
}()
// size of codesign = sizeof linkedit_data_command = 4 * 4
mc.file.WriteAt(make([]byte, 4*4), int64(old_codesign_lcmd_offset))
// mc.file.Seek(old_codesign_lcmd_offset, io.SeekStart)
// mc.file.Write(make([]byte, 4*4))
return true
}
func (mc *MachoContext) RemoveInitFunctions() bool {
if mc.WriteEnabled() {
for _, ptr := range mc.InitFunctions() {
if mc.Is64bit() {
mc.file.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0}, int64(ptr.offset))
} else {
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(ptr.offset))
}
log.WithFields(log.Fields{
"offset": fmt.Sprintf("0x%x", ptr.Offset()),
"value": fmt.Sprintf("0x%x", ptr.Value()),
}).Debug("Remove init pointer")
}
return true
}
return false
}
func (mc *MachoContext) RemoveUnnecessaryInfo() bool {
for _, command := range mc.commands {
switch command.(type) {
case *LinkEdit:
var le = command.(*LinkEdit)
if le.Cmdname() != "LC_FUNCTION_STARTS" && le.Cmdname() != "LC_DATA_IN_CODE" {
continue
}
var start int64 = int64(le.dataoff)
var end int64 = start + int64(le.datasize)
for i := start; i < end; i++ {
mc.file.WriteAt([]byte{0}, i)
}
continue
default:
continue
}
}
return false
}
func (mc *MachoContext) RemoveClassicSymbol() bool {
return false
}
func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) {
var offset uint64
payload := lcmd.Serialize(mc)
if uint64(len(payload)) > mc.PaddingSize() {
log.WithFields(log.Fields{
"cmd": lcmd,
"len(cmd)": len(payload),
"available": mc.PaddingSize(),
}).Panic("Not enough space to add load command")
}
if mc.Is64bit() {
offset = Header_size_64 + uint64(mc.header.sizeofcmds)
} else {
offset = Header_size + uint64(mc.header.sizeofcmds)
}
log.WithFields(log.Fields{
"cmd": lcmd.Cmdname(),
"size": len(payload),
"offset": offset,
}).Debug("Adding Load Command")
mc.file.WriteAt(payload, int64(offset))
mc.UpdateHeaderAddLcmd(uint32(len(payload)))
}
func (mc *MachoContext) AddDylib(dylib string) {
if mc.DylibExisted(dylib) {
log.WithFields(log.Fields{
"dylib": dylib,
}).Debug("Adding dylib but existed")
return
}
name := []byte(dylib)
name = append(name, 0)
dylib_lcmd := Dylib{
c: LoadCmd{cmd: LC_LOAD_DYLIB, cmdsize: 0},
name: name,
nameoff: 24,
timestamp: 0,
current_version: 0,
compatibility_version: 0,
}
mc.AddLoadCmd(&dylib_lcmd)
}
func (mc *MachoContext) AddRPath(rpath string) {
if mc.RPathExisted(rpath) {
log.WithFields(log.Fields{
"rpath": rpath,
}).Debug("Adding rpath but existed")
return
}
path := []byte(rpath)
path = append(path, 0)
rpath_lcmd := RPath{
c: LoadCmd{cmd: LC_RPATH, cmdsize: 0},
offset: 12,
path: path,
}
mc.AddLoadCmd(&rpath_lcmd)
}
func (mc *MachoContext) UpdateHeaderAddLcmd(size uint32) {
if mc.WriteEnabled() {
mc.header.ncmds += 1
mc.header.sizeofcmds += size
mc.file.WriteAt(mc.header.Serialize(mc), 0)
}
}
func (mc *MachoContext) UpdateHeaderRemoveLcmd(size uint32) {
if mc.WriteEnabled() {
mc.header.ncmds -= 1
mc.header.sizeofcmds -= size
mc.file.WriteAt(mc.header.Serialize(mc), 0)
}
}

View File

@ -0,0 +1,13 @@
package macho
import (
"fmt"
)
type ParseError struct {
Expect string
}
func (e *ParseError) Error() string {
return fmt.Sprintf("Parse Error: %s", e.Expect)
}

View File

@ -0,0 +1,384 @@
package macho
import (
"bytes"
"encoding/binary"
"fmt"
)
type Section interface {
// SegName() []byte
SectName() []byte
Addr() uint64
Size() uint64
Offset() uint32
Type() uint8
Attribute() uint32
}
type Segment interface {
SegName() []byte
Vmaddr() uint64
Vmsize() uint64
Fileoff() uint64
Filesize() uint64
Flags() uint32
Sections() []Section
}
type Segment32 struct {
c LoadCmd
segname []byte
vmaddr uint32
vmsize uint32
fileoff uint32
filesize uint32
maxprot uint32
initprot uint32
nsects uint32
flags uint32
sections []*Section32
}
func (lcmd *Segment32) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Segment32) Cmdsize() uint32 {
segment_size := uint32(56)
section_size := uint32(4*9 + 16*2)
return segment_size + section_size*lcmd.nsects
}
func (lcmd *Segment32) Cmdname() string {
var s string
s += fmt.Sprintf(
"%s %s vm[vmaddr:0x%x vmsize:0x%x] [fileoff:0x%x filesize:0x%x]",
lcmd.c.Cmdname(),
string(lcmd.segname),
lcmd.vmaddr,
lcmd.vmsize,
lcmd.fileoff,
lcmd.filesize,
)
for i, section := range lcmd.sections {
s += fmt.Sprintf("\n %d. %s [addr:0x%x size:0x%x offset:0x%x]",
i, string(section.sectname),
section.addr, section.size, section.offset,
)
}
return s
}
func (lcmd *Segment32) SegName() []byte {
return lcmd.segname
}
func (lcmd *Segment32) Vmaddr() uint64 {
return uint64(lcmd.vmaddr)
}
func (lcmd *Segment32) Vmsize() uint64 {
return uint64(lcmd.vmaddr)
}
func (lcmd *Segment32) Fileoff() uint64 {
return uint64(lcmd.fileoff)
}
func (lcmd *Segment32) Filesize() uint64 {
return uint64(lcmd.filesize)
}
func (lcmd *Segment32) Flags() uint32 {
return lcmd.flags
}
func (lcmd *Segment32) Sections() []Section {
var sections []Section
for _, section := range lcmd.sections {
sections = append(sections, section)
}
return sections
}
func (lcmd *Segment32) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
buf.Write(lcmd.segname)
binary.Write(buf, mc.byteorder, lcmd.vmaddr)
binary.Write(buf, mc.byteorder, lcmd.vmsize)
binary.Write(buf, mc.byteorder, lcmd.fileoff)
binary.Write(buf, mc.byteorder, lcmd.filesize)
binary.Write(buf, mc.byteorder, lcmd.maxprot)
binary.Write(buf, mc.byteorder, lcmd.initprot)
binary.Write(buf, mc.byteorder, lcmd.nsects)
binary.Write(buf, mc.byteorder, lcmd.flags)
for _, section := range lcmd.sections {
binary.Write(buf, mc.byteorder, section.Serialize(mc))
}
return buf.Bytes()
}
func (lcmd *Segment32) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
lcmd.segname = r.Next(16)
binary.Read(r, mc.byteorder, &lcmd.vmaddr)
binary.Read(r, mc.byteorder, &lcmd.vmsize)
binary.Read(r, mc.byteorder, &lcmd.fileoff)
binary.Read(r, mc.byteorder, &lcmd.filesize)
binary.Read(r, mc.byteorder, &lcmd.maxprot)
binary.Read(r, mc.byteorder, &lcmd.initprot)
binary.Read(r, mc.byteorder, &lcmd.nsects)
binary.Read(r, mc.byteorder, &lcmd.flags)
for i := uint32(0); i < lcmd.nsects; i++ {
section_buf := make([]byte, 68)
r.Read(section_buf)
var section Section32
section.Deserialize(mc, section_buf)
lcmd.sections = append(lcmd.sections, &section)
}
}
type Segment64 struct {
c LoadCmd
segname []byte
vmaddr uint64
vmsize uint64
fileoff uint64
filesize uint64
maxprot uint32
initprot uint32
nsects uint32
flags uint32
sections []*Section64
}
func (lcmd *Segment64) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Segment64) Cmdsize() uint32 {
segment_size := uint32(72)
section_size := uint32(8*2 + 4*8 + 16*2)
return segment_size + section_size*lcmd.nsects
}
func (lcmd *Segment64) Cmdname() string {
var s string
s += fmt.Sprintf(
"%s %s vm[vmaddr:0x%x vmsize:0x%x] [fileoff:0x%x filesize:0x%x]",
lcmd.c.Cmdname(),
string(lcmd.segname),
lcmd.vmaddr,
lcmd.vmsize,
lcmd.fileoff,
lcmd.filesize,
)
for i, section := range lcmd.sections {
s += fmt.Sprintf("\n %d. %s [addr:0x%x size:0x%x offset:0x%x]",
i, string(section.sectname),
section.addr, section.size, section.offset,
)
}
return s
}
func (lcmd *Segment64) SegName() []byte {
return lcmd.segname
}
func (lcmd *Segment64) Vmaddr() uint64 {
return lcmd.vmaddr
}
func (lcmd *Segment64) Vmsize() uint64 {
return lcmd.vmaddr
}
func (lcmd *Segment64) Fileoff() uint64 {
return lcmd.fileoff
}
func (lcmd *Segment64) Filesize() uint64 {
return lcmd.filesize
}
func (lcmd *Segment64) Flags() uint32 {
return lcmd.flags
}
func (lcmd *Segment64) Sections() []Section {
var sections []Section
for _, section := range lcmd.sections {
sections = append(sections, section)
}
return sections
}
func (lcmd *Segment64) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
buf.Write(lcmd.segname)
binary.Write(buf, mc.byteorder, lcmd.vmaddr)
binary.Write(buf, mc.byteorder, lcmd.vmsize)
binary.Write(buf, mc.byteorder, lcmd.fileoff)
binary.Write(buf, mc.byteorder, lcmd.filesize)
binary.Write(buf, mc.byteorder, lcmd.maxprot)
binary.Write(buf, mc.byteorder, lcmd.initprot)
binary.Write(buf, mc.byteorder, lcmd.nsects)
binary.Write(buf, mc.byteorder, lcmd.flags)
for _, section := range lcmd.sections {
binary.Write(buf, mc.byteorder, section.Serialize(mc))
}
return buf.Bytes()
}
func (lcmd *Segment64) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
lcmd.segname = r.Next(16)
binary.Read(r, mc.byteorder, &lcmd.vmaddr)
binary.Read(r, mc.byteorder, &lcmd.vmsize)
binary.Read(r, mc.byteorder, &lcmd.fileoff)
binary.Read(r, mc.byteorder, &lcmd.filesize)
binary.Read(r, mc.byteorder, &lcmd.maxprot)
binary.Read(r, mc.byteorder, &lcmd.initprot)
binary.Read(r, mc.byteorder, &lcmd.nsects)
binary.Read(r, mc.byteorder, &lcmd.flags)
for i := uint32(0); i < lcmd.nsects; i++ {
section_buf := make([]byte, 80)
r.Read(section_buf)
var section Section64
section.Deserialize(mc, section_buf)
lcmd.sections = append(lcmd.sections, &section)
}
}
type Section32 struct {
sectname []byte
segname []byte
addr uint32
size uint32
offset uint32
align uint32
reloff uint32
nreloc uint32
flags uint32
reserved1 uint32
reserved2 uint32
}
func (sec *Section32) SectName() []byte {
return sec.sectname
}
func (sec *Section32) Addr() uint64 {
return uint64(sec.addr)
}
func (sec *Section32) Size() uint64 {
return uint64(sec.size)
}
func (sec *Section32) Offset() uint32 {
return sec.offset
}
func (sec *Section32) Type() uint8 {
return uint8(sec.flags & 0xff)
}
func (sec *Section32) Attribute() uint32 {
return sec.flags & 0xffffff00
}
func (section *Section32) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
buf.Write(section.sectname)
buf.Write(section.segname)
binary.Write(buf, mc.byteorder, section.addr)
binary.Write(buf, mc.byteorder, section.size)
binary.Write(buf, mc.byteorder, section.offset)
binary.Write(buf, mc.byteorder, section.align)
binary.Write(buf, mc.byteorder, section.reloff)
binary.Write(buf, mc.byteorder, section.nreloc)
binary.Write(buf, mc.byteorder, section.flags)
binary.Write(buf, mc.byteorder, section.reserved1)
binary.Write(buf, mc.byteorder, section.reserved2)
return buf.Bytes()
}
func (section *Section32) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
section.sectname = r.Next(16)
section.segname = r.Next(16)
binary.Read(r, mc.byteorder, &section.addr)
binary.Read(r, mc.byteorder, &section.size)
binary.Read(r, mc.byteorder, &section.offset)
binary.Read(r, mc.byteorder, &section.align)
binary.Read(r, mc.byteorder, &section.reloff)
binary.Read(r, mc.byteorder, &section.nreloc)
binary.Read(r, mc.byteorder, &section.flags)
binary.Read(r, mc.byteorder, &section.reserved1)
binary.Read(r, mc.byteorder, &section.reserved2)
}
type Section64 struct {
sectname []byte
segname []byte
addr uint64
size uint64
offset uint32
align uint32
reloff uint32
nreloc uint32
flags uint32
reserved1 uint32
reserved2 uint32
reserved3 uint32
}
func (sec *Section64) SectName() []byte {
return sec.sectname
}
func (sec *Section64) Addr() uint64 {
return sec.addr
}
func (sec *Section64) Size() uint64 {
return sec.size
}
func (sec *Section64) Offset() uint32 {
return sec.offset
}
func (sec *Section64) Type() uint8 {
return uint8(sec.flags & 0xff)
}
func (sec *Section64) Attribute() uint32 {
return sec.flags & 0xffffff00
}
func (section *Section64) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
buf.Write(section.sectname)
buf.Write(section.segname)
binary.Write(buf, mc.byteorder, section.addr)
binary.Write(buf, mc.byteorder, section.size)
binary.Write(buf, mc.byteorder, section.offset)
binary.Write(buf, mc.byteorder, section.align)
binary.Write(buf, mc.byteorder, section.reloff)
binary.Write(buf, mc.byteorder, section.nreloc)
binary.Write(buf, mc.byteorder, section.flags)
binary.Write(buf, mc.byteorder, section.reserved1)
binary.Write(buf, mc.byteorder, section.reserved2)
binary.Write(buf, mc.byteorder, section.reserved3)
return buf.Bytes()
}
func (section *Section64) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
section.sectname = r.Next(16)
section.segname = r.Next(16)
binary.Read(r, mc.byteorder, &section.addr)
binary.Read(r, mc.byteorder, &section.size)
binary.Read(r, mc.byteorder, &section.offset)
binary.Read(r, mc.byteorder, &section.align)
binary.Read(r, mc.byteorder, &section.reloff)
binary.Read(r, mc.byteorder, &section.nreloc)
binary.Read(r, mc.byteorder, &section.flags)
binary.Read(r, mc.byteorder, &section.reserved1)
binary.Read(r, mc.byteorder, &section.reserved2)
binary.Read(r, mc.byteorder, &section.reserved3)
}

View File

@ -0,0 +1,483 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
. "ios-wrapper/pkg/ios"
)
// A Serializable interface
// Serialize to bytes and Deserialize to struct
// with MachoContext to know the byteorder
type Serializable interface {
Serialize(mc *MachoContext) []byte
Deserialize(mc *MachoContext, buf []byte)
}
// macho_header and macho_header64
type Header struct {
magic uint32
cputype uint32
cpusubtype uint32
filetype uint32
ncmds uint32
sizeofcmds uint32
flags uint32
reserved uint32
}
func (h *Header) Cputype() uint32 {
return h.cputype
}
func (h *Header) Cpusubtype() uint32 {
return h.cpusubtype
}
func (h *Header) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, h.magic)
binary.Write(buf, mc.byteorder, h.cputype)
binary.Write(buf, mc.byteorder, h.cpusubtype)
binary.Write(buf, mc.byteorder, h.filetype)
binary.Write(buf, mc.byteorder, h.ncmds)
binary.Write(buf, mc.byteorder, h.sizeofcmds)
binary.Write(buf, mc.byteorder, h.flags)
if mc.Is64bit() {
binary.Write(buf, mc.byteorder, h.reserved)
}
return buf.Bytes()
}
func (h *Header) Deserialize(mc *MachoContext, r *bufio.Reader) {
binary.Read(r, mc.byteorder, &h.magic)
binary.Read(r, mc.byteorder, &h.cputype)
binary.Read(r, mc.byteorder, &h.cpusubtype)
binary.Read(r, mc.byteorder, &h.filetype)
binary.Read(r, mc.byteorder, &h.ncmds)
binary.Read(r, mc.byteorder, &h.sizeofcmds)
binary.Read(r, mc.byteorder, &h.flags)
if mc.Is64bit() {
binary.Read(r, mc.byteorder, &h.reserved)
}
}
type LoadCommand interface {
Serializable
Cmd() uint32
Cmdsize() uint32
Cmdname() string
}
// Stores the common value of load command
// Raw stores the rest of the command
// where no struct defined for cmd
type LoadCmd struct {
cmd uint32
cmdsize uint32
Raw []byte
}
func (lcmd *LoadCmd) Cmd() uint32 {
return lcmd.cmd
}
func (lcmd *LoadCmd) Cmdsize() uint32 {
return lcmd.cmdsize
}
func (lcmd *LoadCmd) Cmdname() string {
switch lcmd.cmd {
case LC_RPATH:
return "LC_RPATH"
case LC_DYLD_INFO:
return "LC_DYLD_INFO"
case LC_DYLD_INFO_ONLY:
return "LC_DYLD_INFO_ONLY"
case LC_CODE_SIGNATURE:
return "LC_CODE_SIGNATURE"
case LC_LAZY_LOAD_DYLIB:
return "LC_LAZY_LOAD_DYLIB"
case LC_LOAD_DYLIB:
return "LC_LOAD_DYLIB"
case LC_ID_DYLIB:
return "LC_ID_DYLIB"
case LC_REEXPORT_DYLIB:
return "LC_REEXPORT_DYLIB"
case LC_ENCRYPTION_INFO:
return "LC_ENCRYPTION_INFO"
case LC_ENCRYPTION_INFO_64:
return "LC_ENCRYPTION_INFO_64"
case LC_DYSYMTAB:
return "LC_DYSYMTAB"
case LC_LOAD_WEAK_DYLIB:
return "LC_LOAD_WEAK_DYLIB"
case LC_SEGMENT:
return "LC_SEGMENT"
case LC_SEGMENT_64:
return "LC_SEGMENT_64"
case LC_MAIN:
return "LC_MAIN"
case LC_FUNCTION_STARTS:
return "LC_FUNCTION_STARTS"
case LC_DATA_IN_CODE:
return "LC_DATA_IN_CODE"
case LC_SYMTAB:
return "LC_SYMTAB"
default:
// TODO: Update
return fmt.Sprintf("LC_DONT_KNOW_0x%x", lcmd.Cmd())
}
}
func (lcmd *LoadCmd) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.Raw)
return buf.Bytes()
}
func (lcmd *LoadCmd) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.cmd)
binary.Read(r, mc.byteorder, &lcmd.cmdsize)
lcmd.Raw = make([]byte, lcmd.cmdsize-8)
r.Read(lcmd.Raw)
}
type RPath struct {
c LoadCmd
offset uint32
path []byte
}
func (lcmd *RPath) Cmd() uint32 {
return LC_RPATH
}
func (lcmd *RPath) Cmdsize() uint32 {
raw_cmd_size := 8 + 4 + len(lcmd.path) + 1
if raw_cmd_size%8 == 0 {
return uint32(raw_cmd_size)
}
padded_cmd_size := raw_cmd_size + (8 - (raw_cmd_size % 8))
return uint32(padded_cmd_size)
}
func (lcmd *RPath) Cmdname() string {
return fmt.Sprintf("%s %s", lcmd.c.Cmdname(), string(lcmd.path))
}
func (lcmd *RPath) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.offset)
binary.Write(buf, mc.byteorder, []byte(lcmd.path))
buf.WriteByte(0)
if buf.Len()%8 != 0 {
bytes_padded := 8 - (buf.Len() % 8)
binary.Write(buf, mc.byteorder, make([]byte, bytes_padded))
}
return buf.Bytes()
}
func (lcmd *RPath) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.offset)
lcmd.path, _ = r.ReadBytes(0)
lcmd.path = bytes.Trim(lcmd.path, "\x00")
}
type Dylib struct {
c LoadCmd
nameoff uint32
name []byte
timestamp uint32
current_version uint32
compatibility_version uint32
}
func (lcmd *Dylib) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Dylib) Cmdsize() uint32 {
raw_cmd_size := 8 + 16 + len(lcmd.name) + 1
if raw_cmd_size%8 == 0 {
return uint32(raw_cmd_size)
}
padded_cmd_size := raw_cmd_size + (8 - (raw_cmd_size % 8))
return uint32(padded_cmd_size)
}
func (lcmd *Dylib) Cmdname() string {
return fmt.Sprintf("%s %s", lcmd.c.Cmdname(), string(lcmd.name))
}
func (lcmd *Dylib) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.nameoff)
binary.Write(buf, mc.byteorder, lcmd.timestamp)
binary.Write(buf, mc.byteorder, lcmd.current_version)
binary.Write(buf, mc.byteorder, lcmd.compatibility_version)
binary.Write(buf, mc.byteorder, lcmd.name)
buf.WriteByte(0)
// size must align with 8
if buf.Len()%8 != 0 {
bytes_padded := 8 - (buf.Len() % 8)
binary.Write(buf, mc.byteorder, make([]byte, bytes_padded))
}
return buf.Bytes()
}
func (lcmd *Dylib) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.nameoff)
binary.Read(r, mc.byteorder, &lcmd.timestamp)
binary.Read(r, mc.byteorder, &lcmd.current_version)
binary.Read(r, mc.byteorder, &lcmd.compatibility_version)
lcmd.name, _ = r.ReadBytes(0)
lcmd.name = bytes.Trim(lcmd.name, "\x00")
}
type EncryptionInfo struct {
c LoadCmd
cryptoff uint32
cryptsize uint32
cryptid uint32
pad uint32
}
func (lcmd *EncryptionInfo) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *EncryptionInfo) Cmdsize() uint32 {
if lcmd.Cmd() == LC_ENCRYPTION_INFO {
return uint32(8 + 4*3)
} else {
return uint32(8 + 4*4)
}
}
func (lcmd *EncryptionInfo) Cmdname() string {
return fmt.Sprintf("%s start:0x%x size:0x%x encrypted:%d",
lcmd.c.Cmdname(), lcmd.cryptoff, lcmd.cryptsize, lcmd.cryptid)
}
func (lcmd *EncryptionInfo) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.cryptoff)
binary.Write(buf, mc.byteorder, lcmd.cryptsize)
binary.Write(buf, mc.byteorder, lcmd.cryptid)
if mc.Is64bit() {
binary.Write(buf, mc.byteorder, lcmd.pad)
}
return buf.Bytes()
}
func (lcmd *EncryptionInfo) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.cryptoff)
binary.Read(r, mc.byteorder, &lcmd.cryptsize)
binary.Read(r, mc.byteorder, &lcmd.cryptid)
if mc.Is64bit() {
binary.Read(r, mc.byteorder, &lcmd.pad)
}
}
type EntryPoint struct {
c LoadCmd
entryoff uint64
stacksize uint64
}
func (lcmd *EntryPoint) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *EntryPoint) Cmdsize() uint32 {
return uint32(8 + 8*2)
}
func (lcmd *EntryPoint) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *EntryPoint) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.entryoff)
binary.Write(buf, mc.byteorder, lcmd.stacksize)
return buf.Bytes()
}
func (lcmd *EntryPoint) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.entryoff)
binary.Read(r, mc.byteorder, &lcmd.stacksize)
}
type DyldInfo struct {
c LoadCmd
rebase_off uint32
rebase_size uint32
bind_off uint32
bind_size uint32
weak_bind_off uint32
weak_bind_size uint32
lazy_bind_off uint32
lazy_bind_size uint32
export_off uint32
export_size uint32
}
func (lcmd *DyldInfo) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *DyldInfo) Cmdsize() uint32 {
return uint32(8 + 4*10)
}
func (lcmd *DyldInfo) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *DyldInfo) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.rebase_off)
binary.Write(buf, mc.byteorder, lcmd.rebase_size)
binary.Write(buf, mc.byteorder, lcmd.bind_off)
binary.Write(buf, mc.byteorder, lcmd.bind_size)
binary.Write(buf, mc.byteorder, lcmd.weak_bind_off)
binary.Write(buf, mc.byteorder, lcmd.weak_bind_size)
binary.Write(buf, mc.byteorder, lcmd.lazy_bind_off)
binary.Write(buf, mc.byteorder, lcmd.lazy_bind_size)
binary.Write(buf, mc.byteorder, lcmd.export_off)
binary.Write(buf, mc.byteorder, lcmd.export_size)
return buf.Bytes()
}
func (lcmd *DyldInfo) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.rebase_off)
binary.Read(r, mc.byteorder, &lcmd.rebase_size)
binary.Read(r, mc.byteorder, &lcmd.bind_off)
binary.Read(r, mc.byteorder, &lcmd.bind_size)
binary.Read(r, mc.byteorder, &lcmd.weak_bind_off)
binary.Read(r, mc.byteorder, &lcmd.weak_bind_size)
binary.Read(r, mc.byteorder, &lcmd.lazy_bind_off)
binary.Read(r, mc.byteorder, &lcmd.lazy_bind_size)
binary.Read(r, mc.byteorder, &lcmd.export_off)
binary.Read(r, mc.byteorder, &lcmd.export_size)
}
type LinkEdit struct {
c LoadCmd
dataoff uint32
datasize uint32
}
func (lcmd *LinkEdit) Dataoff() uint32 {
return lcmd.dataoff
}
func (lcmd *LinkEdit) Datasize() uint32 {
return lcmd.datasize
}
func (lcmd *LinkEdit) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *LinkEdit) Cmdsize() uint32 {
return uint32(8 + 4*2)
}
func (lcmd *LinkEdit) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *LinkEdit) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.dataoff)
binary.Write(buf, mc.byteorder, lcmd.datasize)
return buf.Bytes()
}
func (lcmd *LinkEdit) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.dataoff)
binary.Read(r, mc.byteorder, &lcmd.datasize)
}
type Symtab struct {
c LoadCmd
symoff uint32
nsyms uint32
stroff uint32
strsize uint32
}
func (lcmd *Symtab) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Symtab) Cmdsize() uint32 {
return uint32(8 + 4*4)
}
func (lcmd *Symtab) Cmdname() string {
return fmt.Sprintf("%s symoff:0x%x nsyms:%d stroff:0x%x strsize:0x%x",
lcmd.c.Cmdname(), lcmd.symoff, lcmd.nsyms, lcmd.stroff, lcmd.strsize)
}
func (lcmd *Symtab) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.symoff)
binary.Write(buf, mc.byteorder, lcmd.nsyms)
binary.Write(buf, mc.byteorder, lcmd.stroff)
binary.Write(buf, mc.byteorder, lcmd.strsize)
return buf.Bytes()
}
func (lcmd *Symtab) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.symoff)
binary.Read(r, mc.byteorder, &lcmd.nsyms)
binary.Read(r, mc.byteorder, &lcmd.stroff)
binary.Read(r, mc.byteorder, &lcmd.strsize)
}

View File

@ -0,0 +1,265 @@
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
dyldinfo *DyldInfo
}
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)
break
case LC_SYMTAB:
lcmd := new(Symtab)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.symtab = 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
}

View File

@ -0,0 +1,111 @@
package macho
import (
"bytes"
"encoding/binary"
"io"
)
const (
N_STAB uint8 = 0xe0 /* if any of these bits set, a symbolic debugging entry */
N_PEXT uint8 = 0x10 /* private external symbol bit */
N_TYPE uint8 = 0x0e /* mask for the type bits */
N_EXT uint8 = 0x01 /* external symbol bit, set for external symbols */
// Values for N_TYPE bits of the n_type field.
N_UNDF uint8 = 0x0 /* undefined, n_sect == NO_SECT */
N_ABS uint8 = 0x2 /* absolute, n_sect == NO_SECT */
N_SECT uint8 = 0xe /* defined in section number n_sect */
N_PBUD uint8 = 0xc /* prebound undefined (defined in a dylib) */
N_INDR uint8 = 0xa /* indirect */
NO_SECT uint8 = 0 /* symbol is not in any section */
)
type Symbol struct {
name string
address uint64
}
func (sym *Symbol) Name() string {
return sym.name
}
func (sym *Symbol) Address() uint64 {
return sym.address
}
func (mc *MachoContext) CollectSymbols() []*Symbol {
// for referencing section
// sections := []Section{}
// for _, segment := range mc.segments {
// for _, section := range segment.Sections() {
// sections = append(sections, section)
// }
// }
symbols := []*Symbol{}
if mc.symtab.nsyms == 0 {
return []*Symbol{}
}
symtab_buffer := func() *bytes.Buffer {
start := mc.symtab.symoff
if mc.Is64bit() {
end := start + mc.symtab.nsyms*16
return bytes.NewBuffer(mc.buf[start:end])
} else {
end := start + mc.symtab.nsyms*12
return bytes.NewBuffer(mc.buf[start:end])
}
}()
strtable := func() *bytes.Reader {
start := mc.symtab.stroff
end := start + mc.symtab.strsize
return bytes.NewReader(mc.buf[start:end])
}()
for i := uint32(0); i < mc.symtab.nsyms; i++ {
var strx uint32
var flags uint8
var sect uint8
var desc uint16
var value32 uint32
var value64 uint64
binary.Read(symtab_buffer, mc.byteorder, &strx)
binary.Read(symtab_buffer, mc.byteorder, &flags)
binary.Read(symtab_buffer, mc.byteorder, &sect)
binary.Read(symtab_buffer, mc.byteorder, &desc)
if mc.Is64bit() {
binary.Read(symtab_buffer, mc.byteorder, &value64)
} else {
// always use value64
binary.Read(symtab_buffer, mc.byteorder, &value32)
value64 = uint64(value32)
}
mangled_name := func() string {
strtable.Seek(int64(strx), io.SeekStart)
name := bytes.NewBufferString("")
for {
b, _ := strtable.ReadByte()
if b == 0 {
break
}
name.WriteByte(b)
}
return name.String()
}()
// fmt.Printf("0x%x %s\n", value64, mangled_name)
symbols = append(symbols, &Symbol{
name: mangled_name,
address: value64,
})
}
return symbols
}

View File

@ -0,0 +1,197 @@
package macho
import (
"bytes"
"encoding/binary"
"fmt"
"os"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
func (mc *MachoContext) ArchName() string {
if mc.header.cputype == 12 && mc.header.cpusubtype == 9 {
return "armv7"
}
if mc.header.cputype == 12 && mc.header.cpusubtype == 11 {
return "armv7s"
}
if mc.header.cputype == 0x100000C && mc.header.cpusubtype == 0 {
return "arm64"
}
if mc.header.cputype == 0x100000C && mc.header.cpusubtype == 2 {
return "arm64e"
}
if mc.header.cputype == 7 && mc.header.cpusubtype == 3 {
return "i386"
}
if mc.header.cputype == 0x1000007 && mc.header.cpusubtype == 3 {
return "x86_64"
}
if mc.header.cputype == 0x1000007 && mc.header.cpusubtype == 0x80000003 {
return "x86_64-lib64"
}
return fmt.Sprintf("arch-%x-%x", mc.header.cputype, mc.header.cpusubtype)
}
func MachosFromFiles(files []string) ([]*MachoContext, error) {
var r []*MachoContext
for _, filename := range files {
var mc MachoContext
f, _ := os.OpenFile(filename, os.O_RDWR, 0644)
err := mc.ParseFile(f, 0)
if err != nil {
return nil, err
}
r = append(r, &mc)
}
return r, nil
}
func CheckDuplicateArch(machos []*MachoContext) bool {
for i := 0; i < len(machos)-1; i++ {
for j := i + 1; j < len(machos); j++ {
i_cputype := machos[i].Header().Cputype()
j_cputype := machos[j].Header().Cputype()
i_cpusubtype := machos[i].Header().Cpusubtype()
j_cpusubtype := machos[j].Header().Cpusubtype()
if i_cputype == j_cputype &&
i_cpusubtype == j_cpusubtype {
log.WithFields(log.Fields{
"cputype": i_cputype,
"cpusubtype": i_cpusubtype,
}).Warn("Duplicate Mach-O Arch")
return true
}
}
}
return false
}
func (mc *MachoContext) PaddingSize() uint64 {
if mc.Is64bit() {
return mc.entryoff - (Header_size_64 + uint64(mc.header.sizeofcmds))
} else {
return mc.entryoff - (Header_size + uint64(mc.header.sizeofcmds))
}
}
func (mc *MachoContext) DylibExisted(name string) bool {
// simple check
// Advanced check requires expansion of @rpath
for _, dylib := range mc.dylibs {
dylib_ := bytes.Trim(dylib.name, "\x00")
match := bytes.Compare(dylib_, []byte(name)) == 0
log.WithFields(log.Fields{
"left": dylib_,
"right": []byte(name),
"match": match,
}).Trace("Dylib check")
if match {
return true
}
}
return false
}
func (mc *MachoContext) RPathExisted(path string) bool {
// simple check
// Advanced check requires expansion of @rpath
for _, rpath := range mc.rpaths {
rpath_ := bytes.Trim(rpath.path, "\x00")
match := bytes.Compare(rpath_, []byte(path)) == 0
log.WithFields(log.Fields{
"left": rpath_,
"right": []byte(path),
"match": match,
}).Trace("Rpath check")
if match {
return true
}
}
return false
}
func (mc *MachoContext) FindSection(name string) Section {
for _, segment := range mc.segments {
for _, section := range segment.Sections() {
sectname := bytes.Trim(section.SectName(), "\x00")
if bytes.Compare(sectname, []byte(name)) == 0 {
return section
}
}
}
return nil
}
func (mc *MachoContext) FindSegment(name string) Segment {
for _, segment := range mc.segments {
sectname := bytes.Trim(segment.SegName(), "\x00")
if bytes.Compare(sectname, []byte(name)) == 0 {
return segment
}
}
return nil
}
func (mc *MachoContext) Cut(offset uint64, size uint64) []byte {
return mc.buf[offset : offset + size];
}
// INIT POINTER
type InitPointer struct {
offset uint64
low uint32
high uint32
}
func (ptr *InitPointer) Offset() uint64 {
return ptr.offset
}
func (ptr *InitPointer) Value() uint64 {
return (uint64(ptr.high) << 32) | uint64(ptr.low)
}
func (ptr *InitPointer) String() string {
return fmt.Sprintf("0x%x -> 0x%x",
ptr.offset, (uint64(ptr.high)<<32)|uint64(ptr.low))
}
func (mc *MachoContext) InitFunctions() []InitPointer {
var valid_sections []Section
for _, segment := range mc.segments {
for _, section := range segment.Sections() {
if section.Type() == S_MOD_INIT_FUNC_POINTERS {
valid_sections = append(valid_sections, section)
}
}
}
var funcs []InitPointer
for _, section := range valid_sections {
offset := uint64(section.Offset())
size := section.Size()
data := mc.buf[offset : offset+size]
// TODO: assert len(data) // mc.pointersize
count := size / uint64(mc.pointersize)
data_buf := bytes.NewBuffer(data)
for i := uint64(0); i < count; i++ {
var fun InitPointer
fun.offset = offset + i*uint64(mc.pointersize)
binary.Read(data_buf, mc.byteorder, &fun.low)
if mc.Is64bit() {
binary.Read(data_buf, mc.byteorder, &fun.high)
}
funcs = append(funcs, fun)
}
}
return funcs
}

View File

@ -0,0 +1,40 @@
package leb128
import (
"bytes"
)
// Read unsigned leb128, no error
func ReadULEB(buf *bytes.Buffer) (uint, uint) {
result := uint(0)
shift := uint(0)
bytes_read := uint(0)
for {
b, _ := buf.ReadByte()
bytes_read += 1
result |= uint(b&0b0111_1111) << shift
shift += 7
if (b & 0b1000_0000) == 0 {
return result, bytes_read
}
}
}
// Read signed leb128, no error
func ReadSLEB(buf *bytes.Buffer) (int, uint) {
result := int(0)
shift := uint(0)
bytes_read := uint(0)
for {
b, _ := buf.ReadByte()
bytes_read += 1
result |= int(b&0b0111_1111) << shift
shift += 7
if (b & 0b1000_0000) == 0 {
if b&0x40 != 0 {
return result | (-1 << shift), bytes_read
}
return result, bytes_read
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020 IBM Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package version
var (
Version = "1.1"
)