class Metasm::MachO

Constants

CIGAM
CIGAM64
CPU
FILETYPE
FLAGS
GENERIC_RELOC
IND_SYM_IDX
LOAD_COMMAND
MAGIC
MAGIC64
MAGICS
SEC_TYPE
SEG_PROT
SUBCPU
SUBCPUFLAG
SYM_SCOPE
SYM_STAB
SYM_TYPE
THREAD_FLAVOR

Attributes

commands[RW]
endianness[RW]
header[RW]
relocs[RW]
segments[RW]
size[RW]
source[RW]
symbols[RW]

Public Class Methods

new(cpu=nil) click to toggle source
Calls superclass method Metasm::ExeFormat.new
# File metasm/exe_format/macho.rb, line 533
def initialize(cpu=nil)
        super(cpu)
        @endianness ||= cpu ? cpu.endianness : :little
        @size ||= cpu ? cpu.size : 32
        @header = Header.new
        @commands = []
        @segments = []
end

Public Instance Methods

addr_to_fileoff(addr) click to toggle source
# File metasm/exe_format/macho.rb, line 570
def addr_to_fileoff(addr)
        s = @segments.find { |s_| s_.virtaddr <= addr and s_.virtaddr + s_.virtsize > addr } if addr
        addr - s.virtaddr + s.fileoff if s
end
assemble(*a) click to toggle source

assembles the hash self.source to a section array

# File metasm/exe_format/macho.rb, line 965
def assemble(*a)
        parse(*a) if not a.empty?
        @source.each { |k, v|
                raise "no segment named #{k} ?" if not s = @segments.find { |s_| s_.name == k }
                s.encoded << assemble_sequence(v, @cpu)
                v.clear
        }
end
cpu_from_headers() click to toggle source
# File metasm/exe_format/macho.rb, line 769
def cpu_from_headers
        case @header.cputype
        when 'I386'; Ia32.new
        when 'X86_64'; X86_64.new
        when 'POWERPC'; PowerPC.new
        when 'ARM'; ARM.new
        else raise "unsupported cpu #{@header.cputype}"
        end
end
decode() click to toggle source
# File metasm/exe_format/macho.rb, line 554
def decode
        decode_header
        @segments.each { |s| decode_segment(s) }
        decode_symbols
        decode_relocations
end
decode_byte(edata = @encoded) click to toggle source
# File metasm/exe_format/macho.rb, line 516
def decode_byte(edata = @encoded) edata.decode_imm( :u8, @endianness) end
decode_half(edata = @encoded) click to toggle source
# File metasm/exe_format/macho.rb, line 517
def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end
decode_header() click to toggle source

decodes the Mach header from the current offset in self.encoded

# File metasm/exe_format/macho.rb, line 543
def decode_header
        @header.decode self
        @header.ncmds.times { @commands << LoadCommand.decode(self) }
        @commands.each { |cmd|
                e = cmd.data
                case cmd.cmd
                when 'SEGMENT', 'SEGMENT_64'; @segments << e
                end
        }
end
decode_relocations() click to toggle source
# File metasm/exe_format/macho.rb, line 615
def decode_relocations
        @relocs = []
        indsymtab = []
        @commands.each { |cmd|
                if cmd.cmd == 'DYSYMTAB'
                        @encoded.ptr = cmd.data.extreloff
                        cmd.data.nextrel.times { @relocs << Relocation.decode(self) }
                        @encoded.ptr = cmd.data.locreloff
                        cmd.data.nlocrel.times { @relocs << Relocation.decode(self) }
                        @encoded.ptr = cmd.data.indirectsymoff
                        cmd.data.nindirectsyms.times { indsymtab << decode_word }
                end
        }
        @segments.each { |seg|
                seg.sections.each { |sec|
                        @encoded.ptr = sec.reloff
                        sec.nreloc.times { @relocs << Relocation.decode(self) }

                        case sec.type
                        when 'NON_LAZY_SYMBOL_POINTERS', 'LAZY_SYMBOL_POINTERS'
                                edata = seg.encoded
                                off = sec.offset - seg.fileoff
                                (sec.size / sizeof_xword).times { |i|
                                        sidx = indsymtab[sec.res1+i]
                                        if not sidx
                                                puts "W: osx: invalid symbol pointer index #{i} ?" if $VERBOSE
                                                next
                                        end
                                        case IND_SYM_IDX[sidx]
                                        when 'INDIRECT_SYMBOL_LOCAL' # base reloc: add delta from prefered image base
                                                edata.ptr = off
                                                addr = decode_xword(edata)
                                                if s = segment_at(addr)
                                                        label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}")
                                                        seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[label], "u#@size".to_sym, @endianness)
                                                end
                                        when 'INDIRECT_SYMBOL_ABS'   # nothing
                                        else
                                                sym = @symbols[sidx]
                                                seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[sym.name],"u#@size".to_sym, @endianness)
                                        end
                                        off += sizeof_xword
                                }
                        when 'SYMBOL_STUBS'
                                # TODO next unless arch == 386 and sec.attrs & SELF_MODIFYING_CODE and sec.res2 == 5

                                edata = seg.encoded
                                edata.data = edata.data.to_str.dup
                                off = sec.offset - seg.fileoff + 1
                                (sec.size / 5).times { |i|
                                        sidx = indsymtab[sec.res1+i]
                                        if not sidx
                                                puts "W: osx: invalid symbol stub index #{i} ?" if $VERBOSE
                                                next
                                        end
                                        case IND_SYM_IDX[sidx]
                                        when 'INDIRECT_SYMBOL_LOCAL' # base reloc: add delta from prefered image base
                                                edata.ptr = off
                                                addr = decode_word(edata)
                                                if s = segment_at(addr)
                                                        label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}")
                                                        seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[label, :-, Expression[seg.virtaddr, :+, off+4].reduce], :u32, @endianness)
                                                end
                                        when 'INDIRECT_SYMBOL_ABS'   # nothing
                                        else
                                                seg.encoded[off-1] = 0xe9
                                                sym = @symbols[sidx]
                                                seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[sym.name, :-, Expression[seg.virtaddr, :+, off+4].reduce], :u32, @endianness)
                                        end
                                        off += 5
                                }

                        end
                }
        }
        seg = nil
        @relocs.each { |r|
                if r.extern == 1
                        sym = @symbols[r.symbolnum]
                        seg = @segments.find { |sg| sg.virtaddr <= r.address and sg.virtaddr + sg.virtsize > r.address } unless seg and seg.virtaddr <= r.address and seg.virtaddr + seg.virtsize > r.address
                        if not seg
                                puts "macho: reloc to unmapped space #{r.inspect} #{sym.inspect}" if $VERBOSE
                                next
                        end
                        seg.encoded.reloc[r.address - seg.virtaddr] = Metasm::Relocation.new(Expression[sym.name], :u32, @endianness)
                end
        }
end
decode_segment(s) click to toggle source
# File metasm/exe_format/macho.rb, line 704
def decode_segment(s)
        @encoded.add_export(s.name, s.fileoff)
        s.encoded = @encoded[s.fileoff, s.filesize]
        s.encoded.virtsize = s.virtsize
        s.sections.each { |ss|
                ss.encoded = @encoded[ss.offset, ss.size]
                s.encoded.add_export(ss.name, ss.offset - s.fileoff)
        }
end
decode_symbols() click to toggle source
# File metasm/exe_format/macho.rb, line 590
def decode_symbols
        @symbols = []
        ep_count = 0
        @commands.each { |cmd|
                e = cmd.data
                case cmd.cmd
                when 'SYMTAB'
                        @encoded.ptr = e.stroff
                        buf = @encoded.read e.strsize
                        @encoded.ptr = e.symoff
                        e.nsyms.times { @symbols << Symbol.decode(self, buf) }
                when 'THREAD', 'UNIXTHREAD'
                        ep_count += 1
                        ep = cmd.data.entrypoint(self)
                        next if not seg = segment_at(ep)
                        seg.encoded.add_export("entrypoint#{"_#{ep_count}" if ep_count >= 2 }")
                end
        }
        @symbols.each { |s|
                next if s.value == 0 or not s.name
                next if not seg = segment_at(s.value)
                seg.encoded.add_export(s.name)
        }
end
decode_word(edata = @encoded) click to toggle source
# File metasm/exe_format/macho.rb, line 518
def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end
decode_xword(edata= @encoded) click to toggle source
# File metasm/exe_format/macho.rb, line 519
def decode_xword(edata= @encoded) edata.decode_imm((@size == 32 ? :u32 : :u64), @endianness) end
each_section() { |encoded, virtaddr| ... } click to toggle source
# File metasm/exe_format/macho.rb, line 714
def each_section(&b)
        @segments.each { |s| yield s.encoded, s.virtaddr }
end
encode(type=nil) click to toggle source
# File metasm/exe_format/macho.rb, line 779
def encode(type=nil)
        @encoded = EncodedData.new

        init_header_cpu

        if false and maybeyoureallyneedthis
        segz = LoadCommand::SEGMENT.new
        segz.name = '__PAGEZERO'
        segz.encoded = EncodedData.new
        segz.encoded.virtsize = 0x1000
        segz.initprot = segz.maxprot = 0
        @segments.unshift segz
        end

        # TODO sections -> segments
        @segments.each { |seg|
                cname = (@size == 64 ? 'SEGMENT_64' : 'SEGMENT')
                if not @commands.find { |cmd| cmd.cmd == cname and cmd.data == seg }
                        cmd = LoadCommand.new
                        cmd.cmd = cname
                        cmd.data = seg
                        @commands << cmd
                end
        }

        binding = {}
        @encoded << @header.encode(self)

        first = @segments.find { |seg| seg.encoded.rawsize > 0 }

        first.virtsize = new_label('virtsize')
        first.filesize = new_label('filesize')

        hlen = @encoded.length
        @commands.each { |cmd| @encoded << cmd.encode(self) }
        binding[@header.sizeofcmds] = @encoded.length - hlen if @header.sizeofcmds.kind_of? String

        # put header in first segment
        first.encoded = @encoded << first.encoded

        @encoded = EncodedData.new

        addr = @encoded.length
        @segments.each { |seg|
                seg.encoded.align 0x1000
                binding[seg.virtaddr] = addr
                binding[seg.virtsize] = seg.encoded.length if seg.filesize.kind_of? String
                binding[seg.fileoff] = @encoded.length
                binding[seg.filesize] = seg.encoded.rawsize if seg.filesize.kind_of? String
                binding.update seg.encoded.binding(addr)
                @encoded << seg.encoded[0, seg.encoded.rawsize]
                @encoded.align 0x1000
                addr += seg.encoded.length
        }

        @encoded.fixup! binding
        @encoded.data
end
encode_byte(val) click to toggle source
# File metasm/exe_format/macho.rb, line 512
def encode_byte(val)        Expression[val].encode( :u8, @endianness) end
encode_half(val) click to toggle source
# File metasm/exe_format/macho.rb, line 513
def encode_half(val)        Expression[val].encode(:u16, @endianness) end
encode_word(val) click to toggle source
# File metasm/exe_format/macho.rb, line 514
def encode_word(val)        Expression[val].encode(:u32, @endianness) end
encode_xword(val) click to toggle source
# File metasm/exe_format/macho.rb, line 515
def encode_xword(val)       Expression[val].encode((@size == 32 ? :u32 : :u64), @endianness) end
fileoff_to_addr(foff) click to toggle source
# File metasm/exe_format/macho.rb, line 575
def fileoff_to_addr(foff)
        if s = @segments.find { |s_| s_.fileoff <= foff and s_.fileoff + s_.filesize > foff }
                s.virtaddr + module_address + foff - s.fileoff
        end
end
get_default_entrypoints() click to toggle source
# File metasm/exe_format/macho.rb, line 765
def get_default_entrypoints
        @commands.find_all { |cmd| cmd.cmd == 'THREAD' or cmd.cmd == 'UNIXTHREAD' }.map { |cmd| cmd.data.entrypoint(self) }
end
init_disassembler() click to toggle source
Calls superclass method Metasm::ExeFormat#init_disassembler
# File metasm/exe_format/macho.rb, line 726
        def init_disassembler
                d = super()
                case @cpu.shortname
                when 'ia32', 'x64'
                        old_cp = d.c_parser
                        d.c_parser = nil
                        d.parse_c <<EOC
void *dlsym(int, char *);       // has special callback
// standard noreturn, optimized by gcc
void __attribute__((noreturn)) exit(int);
void abort(void) __attribute__((noreturn));
EOC
                        d.function[Expression['_dlsym']] = d.function[Expression['dlsym']] = dls = @cpu.decode_c_function_prototype(d.c_parser, 'dlsym')
                        d.function[Expression['_exit']] = d.function[Expression['exit']] = @cpu.decode_c_function_prototype(d.c_parser, 'exit')
                        d.function[Expression['abort']] = @cpu.decode_c_function_prototype(d.c_parser, 'abort')
                        d.c_parser = old_cp

                        dls.btbind_callback = lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth|
                                sz = @cpu.size/8
                                raise 'dlsym call error' if not dasm.decoded[calladdr]
                                if @cpu.shortname == 'x64'
                                        arg2 = :rsi
                                else
                                        arg2 = Indirection.new(Expression[:esp, :+, 2*sz], sz, calladdr)
                                end
                                fnaddr = dasm.backtrace(arg2, calladdr, :include_start => true, :maxdepth => maxdepth)
                                if fnaddr.kind_of?(::Array) and fnaddr.length == 1 and s = dasm.decode_strz(fnaddr.first, 64) and s.length > sz
                                        bind = bind.merge @cpu.register_symbols[0] => Expression[s]
                                end
                                bind
                        }

                        df = d.function[:default] = @cpu.disassembler_default_func
                        df.backtrace_binding[@cpu.register_symbols[4]] = Expression[@cpu.register_symbols[4], :+, @cpu.size/8]
                        df.btbind_callback = nil
                end
                d
        end
init_header_cpu() click to toggle source
# File metasm/exe_format/macho.rb, line 861
def init_header_cpu
        @header.cputype ||= case @cpu.shortname
                            when 'ia32'; 'I386'
                            when 'x64'; 'X86_64'
                            when 'powerpc'; 'POWERPC'
                            when 'arm'; 'ARM'
                            end
end
module_address() click to toggle source
# File metasm/exe_format/macho.rb, line 581
def module_address
        @segments.map { |s_| s_.virtaddr }.min || 0
end
module_size() click to toggle source
# File metasm/exe_format/macho.rb, line 585
def module_size
        return 0 if not sz = @segments.map { |s_| s_.virtaddr + s_.virtsize }.max
        sz - module_address
end
parse_init() click to toggle source
Calls superclass method Metasm::ExeFormat#parse_init
# File metasm/exe_format/macho.rb, line 838
def parse_init
        # allow the user to specify a section, falls back to .text if none specified
        if not defined? @cursource or not @cursource
                @cursource = Object.new
                class << @cursource
                        attr_accessor :exe
                        def <<(*a)
                                t = Preprocessor::Token.new(nil)
                                t.raw = '.text'
                                exe.parse_parser_instruction t
                                exe.cursource.send(:<<, *a)
                        end
                end
                @cursource.exe = self
        end

        @source ||= {}

        init_header_cpu               # for '.entrypoint'

        super()
end
parse_parser_instruction(instr) click to toggle source

handles macho meta-instructions

syntax:

.section "<name>" [<perms>]
  change current section (where normal instruction/data are put)
  perms = list of 'r' 'w' 'x', may be prefixed by 'no'
  shortcuts: .text .data .rodata .bss
.entrypoint [<label>]
  defines the program entrypoint to the specified label / current location
# File metasm/exe_format/macho.rb, line 880
def parse_parser_instruction(instr)
        readstr = lambda {
                @lexer.skip_space
                t = nil
                raise instr, "string expected, found #{t.raw.inspect if t}" if not t = @lexer.readtok or (t.type != :string and t.type != :quoted)
                t.value || t.raw
        }
        check_eol = lambda {
                @lexer.skip_space
                t = nil
                raise instr, "eol expected, found #{t.raw.inspect if t}" if t = @lexer.nexttok and t.type != :eol
        }

        case instr.raw.downcase
        when '.text', '.data', '.rodata', '.bss'
                sname = instr.raw.upcase.sub('.', '__')
                if not @segments.find { |s| s.kind_of? LoadCommand::SEGMENT and s.name == sname }
                        s = LoadCommand::SEGMENT.new
                        s.name = sname
                        s.encoded = EncodedData.new
                        s.initprot = case sname
                                when '__TEXT'; %w[READ EXECUTE]
                                when '__DATA', '__BSS'; %w[READ WRITE]
                                when '__RODATA'; %w[READ]
                                end
                        s.maxprot = %w[READ WRITE EXECUTE]
                        @segments << s
                end
                @cursource = @source[sname] ||= []
                check_eol[] if instr.backtrace  # special case for magic @cursource

        when '.section'
                # .section <section name|"section name"> [(no)wxalloc] [base=<expr>]
                sname = readstr[]
                if not s = @segments.find { |s_| s_.name == sname }
                        s = LoadCommand::SEGMENT.new
                        s.name = sname
                        s.encoded = EncodedData.new
                        s.initprot = %w[READ]
                        s.maxprot = %w[READ WRITE EXECUTE]
                        @segments << s
                end
                loop do
                        @lexer.skip_space
                        break if not tok = @lexer.nexttok or tok.type != :string
                        case @lexer.readtok.raw.downcase
                        when /^(no)?(r)?(w)?(x)?$/
                                ar = []
                                ar << 'READ' if $2
                                ar << 'WRITE' if $3
                                ar << 'EXECINSTR' if $4
                                if $1; s.initprot -= ar
                                else   s.initprot |= ar
                                end
                        else raise instr, 'unknown specifier'
                        end
                end
                @cursource = @source[sname] ||= []
                check_eol[]

        when '.entrypoint'    # XXX thread-specific
                # ".entrypoint <somelabel/expression>" or ".entrypoint" (here)
                @lexer.skip_space
                if tok = @lexer.nexttok and tok.type == :string
                        raise instr if not entrypoint = Expression.parse(@lexer)
                else
                        entrypoint = new_label('entrypoint')
                        @cursource << Label.new(entrypoint, instr.backtrace.dup)
                end
                if not cmd = @commands.find { |cmd_| cmd_.cmd == 'THREAD' or cmd_.cmd == 'UNIXTHREAD' }
                        cmd = LoadCommand.new
                        cmd.cmd = 'UNIXTHREAD'      # UNIXTHREAD creates a stack
                        cmd.data = LoadCommand::THREAD.new
                        cmd.data.ctx = {}
                        cmd.data.flavor = 'NEW_THREAD_STATE'        # XXX i386 specific
                        @commands << cmd
                end
                cmd.data.set_entrypoint(self, entrypoint)
                check_eol[]

        else super(instr)
        end
end
section_info() click to toggle source
# File metasm/exe_format/macho.rb, line 718
def section_info
        ret = []
        @segments.each { |seg|
                ret.concat seg.sections.map { |s| [s.name, s.addr, s.size, s.type] }
        }
        ret
end
segment_at(addr) click to toggle source

return the segment containing address, set seg.encoded.ptr to the correct offset

# File metasm/exe_format/macho.rb, line 562
def segment_at(addr)
        return if not addr or addr <= 0
        if seg = @segments.find { |seg_| addr >= seg_.virtaddr and addr < seg_.virtaddr + seg_.virtsize }
                seg.encoded.ptr = addr - seg.virtaddr
                seg
        end
end
sizeof_byte() click to toggle source
# File metasm/exe_format/macho.rb, line 520
def sizeof_byte ; 1 ; end
sizeof_half() click to toggle source
# File metasm/exe_format/macho.rb, line 521
def sizeof_half ; 2 ; end
sizeof_word() click to toggle source
# File metasm/exe_format/macho.rb, line 522
def sizeof_word ; 4 ; end
sizeof_xword() click to toggle source
# File metasm/exe_format/macho.rb, line 523
def sizeof_xword ; @size == 32 ? 4 : 8 ; end