class Metasm::EncodedData
a String-like, with export/relocation informations added
Constants
- INITIAL_DATA
Attributes
string with raw data
hash, key = export name, value = offset within data - use add_export
to update
hash, key = offset, value = 1st export name
virtual size of data (all 0 by default, see fill
)
arbitrary pointer, often used when decoding immediates may be initialized with an export value
hash, key = offset within data, value = Relocation
virtual size of data (all 0 by default, see fill
)
virtual size of data (all 0 by default, see fill
)
Public Class Methods
returns the value val rounded up to next multiple of len
# File metasm/main.rb, line 1134 def self.align_size(val, len) return val if len == 0 ((val + len - 1) / len).to_i * len end
opts' keys in :reloc, :export, :virtsize, defaults to empty/empty/data.length
# File metasm/main.rb, line 1014 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
equivalent to dup << other, filters out Integers & nil
# File metasm/main.rb, line 1185 def +(other) raise ArgumentError if not other or other.kind_of?(Integer) dup << other end
concatenation of another EncodedData
(or nil/Integer/anything supporting String#<<)
# File metasm/main.rb, line 1140 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
slice
# File metasm/main.rb, line 1191 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
slice replacement, supports size change (shifts following relocs/exports) discards old exports/relocs from the overwritten space
# File metasm/main.rb, line 1225 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
# File metasm/main.rb, line 1027 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
rounds up virtsize to next multiple of len
# File metasm/main.rb, line 1128 def align(len, pattern=nil) @virtsize = EncodedData.align_size(@virtsize, len) fill(@virtsize, pattern) if pattern end
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 1097 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
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 156 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
# File metasm/main.rb, line 1035 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
returns a copy of itself, with reloc/export duped (but not deep)
# File metasm/main.rb, line 1062 def dup self.class.new @data.dup, :reloc => @reloc.dup, :export => @export.dup, :virtsize => @virtsize end
# File metasm/main.rb, line 1053 def empty? @virtsize == 0 end
# File metasm/main.rb, line 1057 def eos? ptr.to_i >= @virtsize end
fill virtual space by repeating pattern (String
) up to len expand self if len is larger than self.virtsize
# File metasm/main.rb, line 1122 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_choice
binding, false
# File metasm/main.rb, line 1085 def fixup(binding) fixup_choice(binding, false) end
fixup_choice
binding, true
# File metasm/main.rb, line 1090 def fixup!(binding) fixup_choice(binding, true) end
resolve relocations: calculate each reloc target using Expression#bind(binding)
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 1070 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
returns an ::Integer from self.ptr, advances ptr bytes from rawsize to virtsize = 0 ignores self.relocations
# File metasm/decode.rb, line 129 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
returns the offset where the relocation for target t is to be applied
# File metasm/main.rb, line 1115 def offset_of_reloc(t) t = Expression[t] @reloc.keys.find { |off| @reloc[off].target == t } end
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 1293 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
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 1305 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
# File metasm/main.rb, line 1008 def ptr=(p) @ptr = @export[p] || p end
returns the size of raw data, that is [data.length, last relocation end].max
# File metasm/main.rb, line 1045 def rawsize [@data.length, *@reloc.map { |off, rel| off + rel.length } ].max end
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 143 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
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 1110 def reloc_externals(interns = @export.keys) @reloc.values.map { |r| r.target.externals }.flatten.uniq - interns end