194 lines
5.8 KiB
Ruby
194 lines
5.8 KiB
Ruby
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
# Copyright (C) 2008 Yoann GUILLOT
|
|
#
|
|
# Licence is LGPL, see LICENCE in the top-level directory
|
|
|
|
|
|
#
|
|
# This file tries to handle simple self-modifying code patterns
|
|
#
|
|
|
|
module SMC
|
|
|
|
# a copy-on-write copy of dasm address space (continuous segment only)
|
|
class CoWData
|
|
attr_accessor :startaddr, :data
|
|
def initialize(dasm)
|
|
@dasm = dasm
|
|
@startaddr = 0
|
|
@data = ''
|
|
end
|
|
|
|
# return a substring, either from the local cache or from dasm
|
|
# handles overlap
|
|
def [](addr, len)
|
|
if @data.empty?
|
|
s, e = @dasm.get_section_at(addr)
|
|
return if not s
|
|
return s.read(len)
|
|
end
|
|
raddr = addr - @base
|
|
rstart = @startaddr - @base
|
|
if raddr >= rstart and raddr+len <= rstart+@data.length
|
|
@data[0, raddr+len-rstart]
|
|
else
|
|
s, e = @dasm.get_section_at(addr)
|
|
return if not s
|
|
obuf = s.read(len)
|
|
len = obuf.length
|
|
if raddr < rstart and raddr+len > rstart
|
|
olen = [raddr+len-rstart, @data.length].min
|
|
obuf[rstart-raddr, olen] = @data[0, olen]
|
|
elsif raddr < rstart+@data.length and raddr+len > rstart+@data.length
|
|
obuf[0, rstart+@data.length-raddr] = @data[raddr-rstart, rstart+@data.length-raddr]
|
|
end
|
|
obuf
|
|
end
|
|
end
|
|
|
|
# set a substring value in the cache
|
|
def []=(addr, len, newdata)
|
|
raise 'len mismatch' if len != newdata.length
|
|
if @data.empty?
|
|
@base = @startaddr = addr
|
|
@data << newdata
|
|
return
|
|
end
|
|
raddr = addr - @base
|
|
rstart = @startaddr - @base
|
|
if raddr+newdata.length < rstart
|
|
s, e = @dasm.get_section_at(addr)
|
|
raise if not s
|
|
obuf = s.read(rstart-(raddr+newdata.length))
|
|
raise if obuf.length != rstart-(raddr+newdata.length)
|
|
newdata += obuf
|
|
elsif raddr > rstart+@data.length
|
|
s, e = @dasm.get_section_at(@startaddr+@data.length)
|
|
raise if not s
|
|
obuf = s.read(raddr-(rstart+@data.length))
|
|
raise if obuf.length != raddr-(rstart+@data.length)
|
|
@data += obuf
|
|
end
|
|
if raddr < rstart
|
|
@data = newdata + @data[raddr+newdata.length-rstart..-1].to_s
|
|
@startaddr = addr
|
|
else
|
|
@data[raddr-rstart, newdata.length] = newdata
|
|
end
|
|
end
|
|
end
|
|
|
|
VirtSections = {}
|
|
|
|
# try to emulate the byte modifications
|
|
# creates a new virtual section in dasm holding decoded data
|
|
# adds the virtual section to the dasm, stores the addresses in VirtSections[dasm]
|
|
# returns true if successful
|
|
def self.emu(dasm, addr)
|
|
puts "emulate SMC @#{Metasm::Expression[addr]}" if $VERBOSE
|
|
|
|
writer = nil
|
|
dasm.each_xref(addr, :w) { |xr| writer = xr.origin }
|
|
return if not dasm.decoded[writer].kind_of? Metasm::DecodedInstruction
|
|
|
|
a_pre, a_entry, a_cond, a_out, loop_bd = find_loop(dasm, writer)
|
|
return if not a_pre
|
|
|
|
# expression checking if we get out of the loop
|
|
loop_again_cond = dasm.cpu.get_jump_condition(dasm.decoded[a_cond])
|
|
loop_again_cond = Expression[:'!', loop_again_cond] if dasm.decoded[a_cond].next_addr != a_out
|
|
|
|
init_bd = {}
|
|
loop_bd.keys.grep(Symbol).each { |reg|
|
|
bt = dasm.backtrace(reg, a_pre, :include_start => true)
|
|
init_bd[reg] = bt.first if bt.length == 1 and bt.first != Metasm::Expression::Unknown and bt.first != Metasm::Expression[reg]
|
|
}
|
|
|
|
# reject non-determinist memory write
|
|
loop_bd.delete_if { |k, v| k.kind_of? Metasm::Indirection and not dasm.get_section_at(k.pointer.bind(init_bd).reduce) }
|
|
|
|
cow_data = CoWData.new(dasm)
|
|
|
|
puts "emulation running..." if $VERBOSE
|
|
pre_bd = init_bd
|
|
loop do
|
|
# the effects of the loop
|
|
post_bd = loop_bd.inject({}) { |bd, (k, v)|
|
|
if k.kind_of? Metasm::Indirection
|
|
k = k.bind(pre_bd).reduce_rec
|
|
raise "bad ptr #{k}" if not dasm.get_section_at(k.pointer.reduce)
|
|
end
|
|
bd.update k => Metasm::Expression[v.bind(pre_bd).reduce]
|
|
}
|
|
|
|
# the indirections used by the loop
|
|
# read mem from cow_data
|
|
# ignores stacked indirections & keys
|
|
ind_bd = {}
|
|
post_bd.values.map { |v| v.expr_indirections }.flatten.uniq.each { |ind|
|
|
p = ind.pointer.reduce
|
|
raise "bad loop read #{ind}" if not p.kind_of? Integer
|
|
ind_bd[ind] = Metasm::Expression.decode_imm(cow_data[p, ind.len], "u#{ind.len*8}".to_sym, dasm.cpu.endianness)
|
|
}
|
|
|
|
post_bd.each { |k, v|
|
|
next if not k.kind_of? Metasm::Indirection
|
|
cow_data[k.pointer.reduce, k.len] = Metasm::Expression.encode_imm(v.bind(ind_bd).reduce, "u#{k.len*8}".to_sym, dasm.cpu.endianness)
|
|
}
|
|
|
|
break if loop_again_cond.bind(post_bd).reduce == 0
|
|
|
|
pre_bd = post_bd
|
|
pre_bd.delete_if { |k, v| not k.kind_of? Symbol }
|
|
end
|
|
|
|
puts "emulation done (#{cow_data.data.length} bytes)" if $VERBOSE
|
|
|
|
VirtSections[dasm] ||= {}
|
|
newbase = "smc#{VirtSections[dasm].length}"
|
|
VirtSections[dasm][addr] = newbase
|
|
dasm.add_section(Metasm::EncodedData.new(cow_data.data), newbase)
|
|
dasm.comment[Metasm::Expression[newbase]] = "SelfModifyingCode from #{dasm.decoded[writer]}"
|
|
|
|
true
|
|
end
|
|
|
|
# find the loop containing addr
|
|
# only trivial loops handled
|
|
# returns [loop start, last instr before loop, loop conditionnal jump, 1st instr after loop, loop binding]
|
|
def self.find_loop(dasm, addr)
|
|
b = dasm.decoded[addr].block
|
|
return if not b.to_normal.to_a.include? b.address
|
|
b1 = b2 = b
|
|
|
|
pre = (b1.from_normal - [b2.list.last.address]).first
|
|
first = b1.address
|
|
last = b2.list.last.address
|
|
post = (b2.to_normal - [b1.address]).first
|
|
loop_bd = dasm.code_binding(first, post)
|
|
|
|
[pre, first, last, post, loop_bd]
|
|
end
|
|
|
|
# redirects the code flow from addr to the decoded section
|
|
def self.redirect(dasm, addr)
|
|
return if not VirtSections[dasm] or not newto = Metasm::Expression[VirtSections[dasm][addr]]
|
|
dasm.each_instructionblock { |b|
|
|
next if not b.to_normal.include? addr
|
|
b.to_normal.map! { |tn| dasm.normalize(tn) == addr ? newto : tn }
|
|
dasm.addrs_todo << [newto, b.list.last.address]
|
|
}
|
|
end
|
|
end
|
|
|
|
if defined? dasm
|
|
# setup the smc callbacks
|
|
list = []
|
|
dasm.callback_selfmodifying = lambda { |addr| list << addr }
|
|
dasm.callback_finished = lambda {
|
|
while addr = list.pop
|
|
SMC.emu(dasm, addr) and SMC.redirect(dasm, addr)
|
|
end
|
|
}
|
|
end
|