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
Calls superclass method
Metasm::ExeFormat#parse_parser_instruction
# 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