# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory require 'metasm/exe_format/main' require 'metasm/encode' require 'metasm/decode' module Metasm # BFLT is the binary flat format used by the uClinux # from examining a v4 binary, it looks like the header is discarded and the file is mapped from 0x40 to memory address 0 (wrt relocations) class Bflt < ExeFormat MAGIC = 'bFLT' FLAGS = { 1 => 'RAM', 2 => 'GOTPIC', 4 => 'GZIP' } attr_accessor :header, :text, :data, :reloc, :got class Header < SerialStruct mem :magic, 4 words :rev, :entry, :data_start, :data_end, :bss_end, :stack_size, :reloc_start, :reloc_count, :flags mem :pad, 6*4 fld_bits(:flags, FLAGS) def decode(exe) super(exe) case @magic when MAGIC else raise InvalidExeFormat, "Bad bFLT signature #@magic" end if @rev >= 0x01000000 and (@rev & 0x00f0ffff) == 0 puts "Bflt: probable wrong endianness, retrying" if $VERBOSE exe.endianness = { :big => :little, :little => :big }[exe.endianness] exe.encoded.ptr -= 4*16 super(exe) end end def set_default_values(exe) @magic ||= MAGIC @rev ||= 4 @entry ||= 0x40 @data_start ||= 0x40 + exe.text.length if exe.text @data_end ||= @data_start + exe.data.data.length if exe.data @bss_end ||= @data_start + exe.data.length if exe.data @stack_size ||= 0x1000 @reloc_start ||= @data_end @reloc_count ||= exe.reloc.length @flags ||= [] super(exe) end end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end def sizeof_word ; 4 ; end attr_accessor :endianness def initialize(cpu = nil) @endianness = cpu ? cpu.endianness : :little @header = Header.new @text = EncodedData.new @data = EncodedData.new super(cpu) end def decode_header @encoded.ptr = 0 @header.decode(self) @encoded.add_export(new_label('entrypoint'), @header.entry) end def decode decode_header @text = @encoded[0x40...@header.data_start] @data = @encoded[@header.data_start...@header.data_end] @data.virtsize += @header.bss_end - @header.data_end if @header.flags.include?('GZIP') # TODO gzip raise 'bFLT decoder: gzip format not supported' end @reloc = [] @encoded.ptr = @header.reloc_start @header.reloc_count.times { @reloc << decode_word } if @header.rev == 2 @reloc.map! { |r| r & 0x3fff_ffff } end decode_interpret_relocs end def decode_interpret_relocs textsz = @header.data_start-0x40 @reloc.each { |r| # where the reloc is if r < textsz section = @text off = section.ptr = r else section = @data off = section.ptr = r-textsz end # what it points to target = decode_word(section) if target < textsz target = label_at(@text, target, "xref_#{Expression[target]}") elsif target < @header.bss_end-0x40 target = label_at(@data, target-textsz, "xref_#{Expression[target]}") else puts "out of bounds reloc target #{Expression[target]} at #{Expression[r]}" if $VERBOSE next end section.reloc[off] = Relocation.new(Expression[target], :u32, @endianness) } end def encode create_relocation_table # TODO got, gzip if @header.flags.include? 'GZIP' puts "W: bFLT: clearing gzip flag" if $VERBOSE @header.flags.delete 'GZIP' end @encoded = EncodedData.new @encoded << @header.encode(self) binding = @text.binding(0x40).merge(@data.binding(@header.data_start)) @encoded << @text << @data.data @encoded.fixup! binding @encoded.reloc.clear @relocs.each { |r| @encoded << encode_word(r) } @encoded.data end def create_relocation_table @reloc = [] mapaddr = new_label('mapaddr') binding = @text.binding(mapaddr).merge(@data.binding(mapaddr)) [@text, @data].each { |section| base = 0x40 # XXX maybe 0 ? base = @header.data_start || base+@text.length if section == @data section.reloc.each { |o, r| if r.endianness == @endianness and [:u32, :a32, :i32].include? r.type and Expression[r.target.bind(binding), :-, mapaddr].reduce.kind_of? ::Integer @reloc << (base+o) else puts "bFLT: ignoring unsupported reloc #{r.inspect} at #{Expression[o]}" if $VERBOSE end } } end def parse_init @textsrc ||= [] @datasrc ||= [] @cursource ||= @textsrc super() end def parse_parser_instruction(instr) case instr.raw.downcase when '.text'; @cursource = @textsrc when '.data'; @cursource = @datasrc when '.entrypoint' # ".entrypoint " 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 @header.entry = entrypoint else super(instr) end end def assemble(*a) parse(*a) if not a.empty? @text << assemble_sequence(@textsrc, @cpu) @textsrc.clear @data << assemble_sequence(@datasrc, @cpu) @datasrc.clear self end def get_default_entrypoints ['entrypoint'] end def each_section yield @text, 0 yield @data, @header.data_start - @header.entry end def section_info [['.text', 0, @text.length, 'rx'], ['.data', @header.data_addr-0x40, @data.data.length, 'rw'], ['.bss', @header.data_end-0x40, @data.length-@data.data.length, 'rw']] end def module_symbols ['entrypoint', @header.entry-0x40] end end end