class Metasm::EncodedData

a String-like, with export/relocation informations added

Constants

INITIAL_DATA

Attributes

data[RW]

string with raw data

export[RW]

hash, key = export name, value = offset within data - use #add_export to update

inv_export[RW]

hash, key = offset, value = 1st export name

length[RW]

virtual size of data (all 0 by default, see fill)

ptr[R]

arbitrary pointer, often used when decoding immediates may be initialized with an export value

reloc[RW]

hash, key = offset within data, value = Relocation

size[RW]

virtual size of data (all 0 by default, see fill)

virtsize[RW]

virtual size of data (all 0 by default, see fill)

Public Class Methods

align_size(val, len) click to toggle source

returns the value val rounded up to next multiple of len

# File metasm/main.rb, line 1129
def self.align_size(val, len)
        return val if len == 0
        ((val + len - 1) / len).to_i * len
end
new(data=INITIAL_DATA.dup, opts={}) click to toggle source

opts' keys in :reloc, :export, :virtsize, defaults to empty/empty/data.length

# File metasm/main.rb, line 1009
def initialize(data=INITIAL_DATA.dup, opts={})
        if data.respond_to?(:force_encoding) and data.encoding.name != 'ASCII-8BIT' and data.length > 0
                puts "Forcing edata.data.encoding = BINARY at", caller if $DEBUG
                data = data.dup.force_encoding('binary')
        end
        @data     = data
        @reloc    = opts[:reloc]    || {}
        @export   = opts[:export]   || {}
        @inv_export = @export.invert
        @virtsize = opts[:virtsize] || @data.length
        @ptr = 0
end

Public Instance Methods

+(other) click to toggle source

equivalent to dup << other, filters out Integers & nil

# File metasm/main.rb, line 1180
def +(other)
        raise ArgumentError if not other or other.kind_of?(Integer)
        dup << other
end
<<(other) click to toggle source

concatenation of another EncodedData (or nil/Integer/anything supporting String#<<)

# File metasm/main.rb, line 1135
def <<(other)
        case other
        when nil
        when ::Integer
                fill
                @data = @data.to_str if not @data.kind_of? String
                @data << other
                @virtsize += 1
        when EncodedData
                fill if not other.data.empty?
                other.reloc.each  { |k, v| @reloc[k + @virtsize] = v  } if not other.reloc.empty?
                if not other.export.empty?
                        other.export.each { |k, v|
                                if @export[k] and @export[k] != v + @virtsize
                                        cf = (other.export.keys & @export.keys).find_all { |k_| other.export[k_] != @export[k_] - @virtsize }
                                        raise "edata merge: label conflict #{cf.inspect}"
                                end
                                @export[k] = v + @virtsize
                        }
                        other.inv_export.each { |k, v| @inv_export[@virtsize + k] = v }
                end
                if @data.empty?; @data = other.data.dup
                elsif other.empty?
                elsif not @data.kind_of?(String); @data = @data.to_str << other.data
                else @data << other.data
                end
                @virtsize += other.virtsize
        else
                fill
                if other.respond_to?(:force_encoding) and other.encoding.name != 'ASCII-8BIT' and other.length > 0
                        puts "Forcing edata.data.encoding = BINARY at", caller if $DEBUG
                        other = other.dup.force_encoding('binary')
                end
                if @data.empty?; @data = other.dup
                elsif other.empty?
                elsif not @data.kind_of?(String); @data = @data.to_str << other
                else @data << other
                end
                @virtsize += other.length
        end

        self
end
[](from, len=nil) click to toggle source

slice

# File metasm/main.rb, line 1186
def [](from, len=nil)
        if not len and from.kind_of? Range
                b = from.begin
                e = from.end
                b = @export[b] if @export[b]
                e = @export[e] if @export[e]
                b = b + @virtsize if b < 0
                e = e + @virtsize if e < 0
                len = e - b
                len += 1 if not from.exclude_end?
                from = b
        end
        from = @export[from] if @export[from]
        from = from + @virtsize if from < 0
        return if from > @virtsize or from < 0

        return @data[from] if not len
        len = @virtsize - from if from+len > @virtsize
        ret = EncodedData.new @data[from, len]
        ret.virtsize = len
        @reloc.each { |o, r|
                ret.reloc[o - from] = r if o >= from and o + r.length <= from+len
        }
        @export.each { |e_, o|
                ret.export[e_] = o - from if o >= from and o <= from+len             # XXX include end ?
        }
        @inv_export.each { |o, e_|
                ret.inv_export[o-from] = e_ if o >= from and o <= from+len
        }
        ret
end
[]=(from, len, val=nil) click to toggle source

slice replacement, supports size change (shifts following relocs/exports) discards old exports/relocs from the overwritten space

# File metasm/main.rb, line 1220
def []=(from, len, val=nil)
        if not val
                val = len
                len = nil
        end
        if not len and from.kind_of?(::Range)
                b = from.begin
                e = from.end
                b = @export[b] if @export[b]
                e = @export[e] if @export[e]
                b = b + @virtsize if b < 0
                e = e + @virtsize if e < 0
                len = e - b
                len += 1 if not from.exclude_end?
                from = b
        end
        from = @export[from] || from
        raise "invalid offset #{from}" if not from.kind_of?(::Integer)
        from = from + @virtsize if from < 0

        if not len
                val = val.chr if val.kind_of?(::Integer)
                len = val.length
        end
        raise "invalid slice length #{len}" if not len.kind_of?(::Integer) or len < 0

        if from >= @virtsize
                len = 0
        elsif from+len > @virtsize
                len = @virtsize-from
        end

        val = EncodedData.new << val

        # remove overwritten metadata
        @export.delete_if { |name, off| off > from and off < from + len }
        @reloc.delete_if { |off, rel| off - rel.length > from and off < from + len }
        # shrink/grow
        if val.length != len
                diff = val.length - len
                @export.keys.each { |name| @export[name] = @export[name] + diff if @export[name] > from }
                @inv_export.keys.each { |off| @inv_export[off+diff] = @inv_export.delete(off) if off > from }
                @reloc.keys.each { |off| @reloc[off + diff] = @reloc.delete(off) if off > from }
                if @virtsize >= from+len
                        @virtsize += diff
                end
        end

        @virtsize = from + val.length if @virtsize < from + val.length

        if from + len < @data.length  # patch real data
                val.fill
                @data[from, len] = val.data
        elsif not val.data.empty?     # patch end of real data
                @data << ([0].pack('C')*(from-@data.length)) if @data.length < from
                @data[from..-1] = val.data
        else                          # patch end of real data with fully virtual
                @data = @data[0, from]
        end
        val.export.each { |name, off| @export[name] = from + off }
        val.inv_export.each { |off, name| @inv_export[from+off] = name }
        val.reloc.each { |off, rel| @reloc[from + off] = rel }
end
add_export(label, off=@ptr, set_inv=false) click to toggle source
# File metasm/main.rb, line 1022
def add_export(label, off=@ptr, set_inv=false)
        @export[label] = off
        if set_inv or not @inv_export[off]
                @inv_export[off] = label
        end
        label
end
align(len, pattern=nil) click to toggle source

rounds up virtsize to next multiple of len

# File metasm/main.rb, line 1123
def align(len, pattern=nil)
        @virtsize = EncodedData.align_size(@virtsize, len)
        fill(@virtsize, pattern) if pattern
end
binding(base = nil) click to toggle source

returns a default binding suitable for use in fixup every export is expressed as base + offset base defaults to the first export name + its offset

# File metasm/main.rb, line 1092
def binding(base = nil)
        if not base
                key = @export.index(@export.values.min)
                return {} if not key
                base = (@export[key] == 0 ? key : Expression[key, :-, @export[key]])
        end
        binding = {}
        @export.each { |n, o| binding.update n => Expression.new(:+, o, base) }
        binding
end
decode_imm(type, endianness) click to toggle source

decodes an immediate value from self.ptr, advances ptr returns an Expression on relocation, or an ::Integer if ptr has a relocation but the type/endianness does not match, the reloc is ignored and a warning is issued TODO arg type => sign+len

# File metasm/decode.rb, line 150
def decode_imm(type, endianness)
        raise "invalid imm type #{type.inspect}" if not isz = Expression::INT_SIZE[type]
        if rel = @reloc[@ptr]
                if Expression::INT_SIZE[rel.type] == isz and rel.endianness == endianness
                        @ptr += rel.length
                        return rel.target
                end
                puts "W: Immediate type/endianness mismatch, ignoring relocation #{rel.target.inspect} (wanted #{type.inspect})" if $DEBUG
        end
        Expression.decode_imm(read(isz/8), type, endianness)
end
Also aliased as: decode_immediate
decode_immediate(type, endianness)
Alias for: decode_imm
del_export(label, off=@export[label]) click to toggle source
# File metasm/main.rb, line 1030
def del_export(label, off=@export[label])
        @export.delete label
        if e = @export.index(off)
                @inv_export[off] = e
        else
                @inv_export.delete off
        end
end
dup() click to toggle source

returns a copy of itself, with reloc/export duped (but not deep)

# File metasm/main.rb, line 1057
def dup
        self.class.new @data.dup, :reloc => @reloc.dup, :export => @export.dup, :virtsize => @virtsize
end
empty?() click to toggle source
# File metasm/main.rb, line 1048
def empty?
        @virtsize == 0
end
eos?() click to toggle source
# File metasm/main.rb, line 1052
def eos?
        ptr.to_i >= @virtsize
end
fill(len = @virtsize, pattern = [0].pack('C')) click to toggle source

fill virtual space by repeating pattern (String) up to len expand self if len is larger than self.virtsize

# File metasm/main.rb, line 1117
def fill(len = @virtsize, pattern = [0].pack('C'))
        @virtsize = len if len > @virtsize
        @data = @data.to_str.ljust(len, pattern) if len > @data.length
end
fixup(binding) click to toggle source

fixup_choice binding, false

# File metasm/main.rb, line 1080
def fixup(binding)
        fixup_choice(binding, false)
end
fixup!(binding) click to toggle source

fixup_choice binding, true

# File metasm/main.rb, line 1085
def fixup!(binding)
        fixup_choice(binding, true)
end
fixup_choice(binding, replace_target) click to toggle source

resolve relocations: calculate each reloc target using Metasm::Expression#bind if numeric, replace the raw data with the encoding of this value (+fill+s preceding data if needed) and remove the reloc if replace_target is true, the reloc target is replaced with its bound counterpart

# File metasm/main.rb, line 1065
def fixup_choice(binding, replace_target)
        return if binding.empty?
        @reloc.keys.each { |off|
                val = @reloc[off].target.bind(binding).reduce
                if val.kind_of? Integer
                        reloc = @reloc[off]
                        reloc.fixup(self, off, val)
                        @reloc.delete(off)  # delete only if not overflowed
                elsif replace_target
                        @reloc[off].target = val
                end
        }
end
get_byte() click to toggle source

returns an ::Integer from self.ptr, advances ptr bytes from rawsize to virtsize = 0 ignores self.relocations

# File metasm/decode.rb, line 123
def get_byte
        @ptr += 1
        if @ptr <= @data.length
                b = @data[ptr-1]
                b = b.unpack('C').first if b.kind_of? ::String       # 1.9
                b
        elsif @ptr <= @virtsize
                0
        end
end
offset_of_reloc(t) click to toggle source

returns the offset where the relocation for target t is to be applied

# File metasm/main.rb, line 1110
def offset_of_reloc(t)
        t = Expression[t]
        @reloc.keys.find { |off| @reloc[off].target == t }
end
patch(from, to, content) click to toggle source

replace a portion of self from/to may be Integers (offsets) or labels (from self.export) content is a String or an EncodedData, which will be inserted in the specified location (padded if necessary) raise if the string does not fit in.

# File metasm/main.rb, line 1288
def patch(from, to, content)
        from = @export[from] || from
        raise "invalid offset specification #{from}" if not from.kind_of? Integer
        to = @export[to] || to
        raise "invalid offset specification #{to}" if not to.kind_of? Integer
        raise EncodeError, 'cannot patch data: new content too long' if to - from < content.length
        self[from, content.length] = content
end
pattern_scan(pat, chunksz=nil, margin=nil) { |match_addr| ... } click to toggle source

returns a list of offsets where /pat/ can be found inside @data scan is done per chunk of chunksz bytes, with a margin for chunk-overlapping patterns yields each offset found, and only include it in the result if the block returns !false

# File metasm/main.rb, line 1300
def pattern_scan(pat, chunksz=nil, margin=nil)
        chunksz ||= 4*1024*1024 # scan 4MB at a time
        margin  ||= 65536        # add this much bytes at each chunk to find /pat/ over chunk boundaries
        pat = Regexp.new(Regexp.escape(pat)) if pat.kind_of?(::String)

        found = []
        chunkoff = 0
        while chunkoff < @data.length
                chunk = @data[chunkoff, chunksz+margin].to_str
                off = 0
                while match = chunk[off..-1].match(pat)
                        off += match.pre_match.length
                        m_l = match[0].length
                        break if off >= chunksz     # match fully in margin
                        match_addr = chunkoff + off
                        found << match_addr if not block_given? or yield(match_addr)
                        off += m_l
                end
                chunkoff += chunksz
        end
        found
end
ptr=(p) click to toggle source
# File metasm/main.rb, line 1003
def ptr=(p) @ptr = @export[p] || p end
rawsize() click to toggle source

returns the size of raw data, that is [data.length, last relocation end].max

# File metasm/main.rb, line 1040
def rawsize
        [@data.length, *@reloc.map { |off, rel| off + rel.length } ].max
end
read(len=@virtsize-@ptr) click to toggle source

reads len bytes from self.data, advances ptr bytes from rawsize to virtsize are returned as zeroes ignores self.relocations

# File metasm/decode.rb, line 137
def read(len=@virtsize-@ptr)
        vlen = len
        vlen = @virtsize-@ptr if len > @virtsize-@ptr
        str = (@ptr < @data.length) ? @data[@ptr, vlen] : ''
        str = str.to_str.ljust(vlen, "\0") if str.length < vlen
        @ptr += len
        str
end
reloc_externals(interns = @export.keys) click to toggle source

returns an array of variables that needs to be defined for a complete fixup ie the list of externals for all relocations

# File metasm/main.rb, line 1105
def reloc_externals(interns = @export.keys)
        @reloc.values.map { |r| r.target.externals }.flatten.uniq - interns
end