class Metasm::WasmFile

WebAssembly leb integer encoding taken from dex.rb

Constants

EXTERNAL_KIND
MAGIC
OPCODE_IMM_COUNT

begin WTF

SECTION_NAME
TYPE

Attributes

code_info[RW]
data[RW]
element[RW]
endianness[RW]
export[RW]
function_body[RW]
function_signature[RW]
global[RW]
header[RW]
import[RW]
memory[RW]
modules[RW]
start_function_index[RW]
table[RW]
type[RW]

Public Class Methods

new(endianness=:little) click to toggle source
Calls superclass method Metasm::ExeFormat::new
# File metasm/exe_format/wasm.rb, line 95
def initialize(endianness=:little)
        @endianness = endianness
        @encoded = EncodedData.new
        super()
end

Public Instance Methods

cpu_from_headers() click to toggle source
# File metasm/exe_format/wasm.rb, line 366
def cpu_from_headers
        WebAsm.new(self)
end
decode() click to toggle source
# File metasm/exe_format/wasm.rb, line 182
def decode
        decode_header
        while @encoded.ptr < @encoded.length
                @modules << Module.decode(self)
        end
        @modules.each { |m|
                @encoded.add_export(new_label("module_#{m.id}"), m.raw_offset)
                f = "decode_module_#{m.id.to_s.downcase}"
                send(f, m) if respond_to?(f)
        }
        func_imports = @import.to_a.find_all { |i| i[:kind] == 'function' }
        export.to_a.each { |e|
                next if e[:kind] != 'function'       # TODO resolve init_offset for globals etc?
                idx = e[:index] - func_imports.length
                next if not fb = function_body.to_a[idx]
                @encoded.add_export(new_label(e[:field]), fb[:init_offset], true)
        }
        # bytecode start addr => { :local_var => [], :params => [], :ret => [] }
        # :local_var absent for external code (imported funcs)
        @code_info = {}
        import.to_a.each { |i|
                next unless i[:kind] == 'function'
                @code_info["#{i[:module]}_#{i[:field]}"] = { :params => i[:type][:params], :ret => i[:type][:ret] }
        }
        function_body.to_a.each { |fb|
                @code_info[fb[:init_offset]] = { :local_var => fb[:local_var], :params => fb[:type][:params], :ret => fb[:type][:ret] }
        }
        global_idx = import.to_a.find_all { |i| i[:kind] == 'global' }.length - 1
        global.to_a.each { |g|
                @code_info[g[:init_offset]] = { :local_var => [], :params => [], :ret => [g[:type]] }
                @encoded.add_export new_label("global_#{global_idx += 1}_init"), g[:init_offset]
        }
        element.to_a.each { |e|
                @code_info[e[:init_offset]] = { :local_var => [], :params => [], :ret => ['i32'] }
        }
        data.to_a.each { |d|
                @code_info[d[:init_offset]] = { :local_var => [], :params => [], :ret => ['i32'] }
        }
end
decode_header() click to toggle source
# File metasm/exe_format/wasm.rb, line 177
def decode_header
        @header = Header.decode(self)
        @modules = []
end
decode_limits(edata=@encoded) click to toggle source
# File metasm/exe_format/wasm.rb, line 139
def decode_limits(edata=@encoded)
        flags = decode_uleb(edata)
        out = { :initial_size => decode_uleb(edata) }
        out[:maximum] = decode_uleb(edata) if flags & 1
        out
end
decode_module_0(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 354
def decode_module_0(m)
        # id == 0 for not well-known modules
        # the module name is encoded at start of payload (uleb name length + actual name)
        m.name = m.edata.read(decode_uleb(m.edata))
        f = "decode_module_0_#{m.name.downcase}"
        send(f, m) if respond_to?(f)
end
decode_module_0_name(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 362
def decode_module_0_name(m)
        # TODO parse stored names of local variables etc
end
decode_module_code(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 314
def decode_module_code(m)
        @function_body = []
        idx = 0
        decode_uleb(m.edata).times {
                local_vars = []
                body_size = decode_uleb(m.edata)     # size of local defs + bytecode (in bytes)
                next_ptr = m.edata.ptr + body_size
                decode_uleb(m.edata).times {         # nr of local vars types
                        n_vars_of_this_type = decode_uleb(m.edata)  # nr of local vars of this type
                        type = decode_type(m.edata) # actual type
                        n_vars_of_this_type.times {
                                local_vars << type
                        }
                }
                code_offset = m.raw_offset + m.edata.ptr     # bytecode comes next
                m.edata.ptr = next_ptr
                @function_body << { :local_var => local_vars, :init_offset => code_offset }
                @function_body.last[:type] = @function_signature[idx] if function_signature
                @encoded.add_export new_label("function_#{@function_body.length-1}"), @function_body.last[:init_offset]
                idx += 1
        }
end
decode_module_data(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 337
def decode_module_data(m)
        @data = []
        decode_uleb(m.edata).times {
                idx = decode_uleb(m.edata)
                initoff = read_code_until_end(m)
                data_len = decode_uleb(m.edata)
                data_start_ptr = m.raw_offset + m.edata.ptr
                data = m.edata.read(data_len)
                data_end_ptr = m.raw_offset + m.edata.ptr

                @data << { :index => idx, :init_offset => initoff, :data => data }
                @encoded.add_export new_label("data_#{@data.length-1}_init_addr"), initoff
                @encoded.add_export new_label("data_#{@data.length-1}_start"), data_start_ptr
                @encoded.add_export new_label("data_#{@data.length-1}_end"), data_end_ptr
        }
end
decode_module_element(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 300
def decode_module_element(m)
        @element = []
        decode_uleb(m.edata).times {
                seg = { :table_index => decode_uleb(m.edata),
                        :init_offset => read_code_until_end(m),
                        :elems => [] }
                decode_uleb(m.edata).times {
                        seg[:elems] << decode_uleb(m.edata)
                }
                @element << seg
                @encoded.add_export new_label("element_#{@element.length-1}_init_addr"), @element.last[:init_offset]
        }
end
decode_module_export(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 284
def decode_module_export(m)
        @export = []
        decode_uleb(m.edata).times {
                flen = decode_uleb(m.edata)
                fld = m.edata.read(flen)
                kind = decode_uleb(m.edata)
                kind = EXTERNAL_KIND[kind] || kind
                index = decode_uleb(m.edata)
                @export << { :field => fld, :kind => kind, :index => index }
        }
end
decode_module_function(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 253
def decode_module_function(m)
        @function_signature = []
        idx = 0
        decode_uleb(m.edata).times {
                @function_signature << @type[decode_uleb(m.edata)]
                @function_body[idx][:type] = @function_signature[idx] if function_body
                idx += 1
        }
end
decode_module_global(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 277
def decode_module_global(m)
        @global = []
        decode_uleb(m.edata).times {
                @global << { :type => decode_type(m.edata), :mutable => decode_uleb(m.edata), :init_offset => read_code_until_end(m) }
        }
end
decode_module_import(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 229
def decode_module_import(m)
        @import = []
        decode_uleb(m.edata).times {
                i = {}
                i[:module] = m.edata.read(decode_uleb(m.edata))
                i[:field] = m.edata.read(decode_uleb(m.edata))
                kind = decode_uleb(m.edata)
                i[:kind] = EXTERNAL_KIND[kind] || kind
                case i[:kind]
                when 'function'
                        i[:type] = @type[decode_uleb(m.edata)]      # XXX keep index only, in case @type is not yet known ?
                when 'table'
                        i[:type] = decode_type(m.edata)
                        i[:limits] = decode_limits(m.edata)
                when 'memory'
                        i[:limits] = decode_limits(m.edata)
                when 'global'
                        i[:type] = decode_type(m.edata)
                        i[:mutable] = decode_uleb(m.edata)
                end
                @import << i
        }
end
decode_module_memory(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 270
def decode_module_memory(m)
        @memory = []
        decode_uleb(m.edata).times {
                @memory << { :limits => decode_limits(m.edata) }
        }
end
decode_module_start(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 296
def decode_module_start(m)
        @start_function_index = decode_uleb(m.edata)
end
decode_module_table(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 263
def decode_module_table(m)
        @table = []
        decode_uleb(m.edata).times {
                @table << { :type => decode_type(m.edata), :limits => decode_limits(m.edata) }
        }
end
decode_module_type(m) click to toggle source
# File metasm/exe_format/wasm.rb, line 222
def decode_module_type(m)
        @type = []
        decode_uleb(m.edata).times {
                @type << decode_type(m.edata)
        }
end
decode_sleb(ed = @encoded) click to toggle source
# File metasm/exe_format/wasm.rb, line 90
def decode_sleb(ed = @encoded) decode_uleb(ed, true) end
decode_type(edata=@encoded) click to toggle source
# File metasm/exe_format/wasm.rb, line 101
def decode_type(edata=@encoded)
        form = decode_sleb(edata)
        type = TYPE[form] || "unk_type_#{form}"
        if type == 'func'
                type = { :params => [], :ret => [] }
                decode_uleb(edata).times {
                        type[:params] << decode_type(edata)
                }
                decode_uleb(edata).times {
                        type[:ret] << decode_type(edata)
                }
        end
        type
end
decode_u4(edata = @encoded) click to toggle source
# File metasm/exe_format/wasm.rb, line 67
def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end
decode_uleb(ed = @encoded, signed=false) click to toggle source
# File metasm/exe_format/wasm.rb, line 78
def decode_uleb(ed = @encoded, signed=false)
        v = s = 0
        while s < 10*7
                b = ed.read(1).unpack('C').first.to_i
                v |= (b & 0x7f) << s
                s += 7
                break if (b&0x80) == 0
        end
        v = Expression.make_signed(v, s) if signed
        v
end
each_section() { |encoded, 0| ... } click to toggle source
# File metasm/exe_format/wasm.rb, line 391
def each_section
        yield @encoded, 0
end
encode_sleb(val) click to toggle source
# File metasm/exe_format/wasm.rb, line 89
def encode_sleb(val) encode_uleb(val, true) end
encode_u4(val) click to toggle source
# File metasm/exe_format/wasm.rb, line 66
def encode_u4(val) Expression[val].encode(:u32, @endianness) end
encode_uleb(val, signed=false) click to toggle source
# File metasm/exe_format/wasm.rb, line 69
def encode_uleb(val, signed=false)
        v = val
        out = EncodedData.new
        while v > 0x7f or v < -0x40 or (signed and v > 0x3f)
                out << Expression[0x80 | (v&0x7f)].encode(:u8, @endianness)
                v >>= 7
        end
        out << Expression[v & 0x7f].encode(:u8, @endianness)
end
get_default_entrypoints() click to toggle source
# File metasm/exe_format/wasm.rb, line 395
def get_default_entrypoints
        global.to_a.map { |g| g[:init_offset] } +
        element.to_a.map { |e| e[:init_offset] } +
        data.to_a.map { |d| d[:init_offset] } +
        function_body.to_a.map { |f| f[:init_offset] }
end
get_function_nr(nr) click to toggle source

return the nth function body use the @function_body array and the @import array

# File metasm/exe_format/wasm.rb, line 127
def get_function_nr(nr)
        func_imports = @import.to_a.find_all { |i| i[:kind] == 'function' }
        return func_imports[nr] if nr < func_imports.length
        nr -= func_imports.length
        @function_body[nr]
end
get_global_nr(nr) click to toggle source

return the nth global use the @global array and the @import array

# File metasm/exe_format/wasm.rb, line 118
def get_global_nr(nr)
        glob_imports = @import.to_a.find_all { |i| i[:kind] == 'global' }
        return glob_imports[nr] if nr < glob_imports.length
        nr -= glob_imports.length
        @global[nr]
end
init_disassembler() click to toggle source
Calls superclass method Metasm::ExeFormat#init_disassembler
# File metasm/exe_format/wasm.rb, line 370
def init_disassembler
        dasm = super()
        function_body.to_a.each { |fb|
                v = []
                fb[:local_var].map { |lv| type_to_s(lv) }.each { |lv|
                        v.last && lv == v.last.last ? v.last << lv : v << [lv]
                }
                v.map! { |sublist|
                        # i32 ; i32 ; i32 ; i32 ; i32 ; i32 ; i64  ->  5 * i32 ; i64
                        sublist.length > 3 ? "#{sublist.length} * #{sublist.first}" : sublist.join(' ; ')
                }
                dasm.add_comment fb[:init_offset], "proto: #{fb[:type] ? type_to_s(fb[:type]) : 'unknown'}"
                dasm.add_comment fb[:init_offset], "vars: #{v.join(' ; ')}"
        }
        global.to_a.each { |g|
                dasm.add_comment g[:init_offset], "type: #{type_to_s(g[:type])}"
        }
        dasm.function[:default] = @cpu.disassembler_default_func
        dasm
end
read_code_until_end(m=nil) click to toggle source

wtf read wasm bytecode until reaching the end opcode return the byte offset

# File metasm/exe_format/wasm.rb, line 149
def read_code_until_end(m=nil)
        if m
                raw_offset = m.raw_offset + m.edata.ptr
                edata = m.edata
        else
                edata = @encoded
        end

        while op = edata.decode_imm(:u8, @endianness)
                case op
                when 0xb
                        # end opcode
                        return raw_offset
                when 0xe
                        # indirect branch wtf
                        decode_uleb(edata).times { decode_uleb(edata) }
                        decode_uleb(edata)
                when 0x43
                        edata.read(4)
                when 0x44
                        edata.read(8)
                else
                        OPCODE_IMM_COUNT[op].times { decode_uleb(edata) }
                end
        end
        raw_offset
end
sizeof_u4() click to toggle source
# File metasm/exe_format/wasm.rb, line 68
def sizeof_u4 ; 4 ; end
type_to_s(t) click to toggle source
# File metasm/exe_format/wasm.rb, line 134
def type_to_s(t)
        return t unless t.kind_of?(::Hash)
        (t[:ret].map { |tt| type_to_s(tt) }.join(', ') << ' f(' << t[:params].map { |tt| type_to_s(tt) }.join(', ') << ')').strip
end