# File samples/bindiff.rb, line 399 def colorize_blocks(a1, a2) b1 = @dasm1.decoded[a1].block b2 = @dasm2.decoded[a2].block common_start = common_end = 0 match_block(b1, b2) { |a, b| common_start = a ; common_end = b } b1.list[0..-1-common_end].zip(b2.list[0..-1-common_end]).each { |di1, di2| next if not di1 or not di2 @dasmcol1[di1.address] = @dasmcol2[di2.address] = [:same, :similar, :badarg, :badop][match_instr(di1, di2)] } b1.list[-common_end..-1].zip(b2.list[-common_end..-1]).each { |di1, di2| next if not di1 or not di2 @dasmcol1[di1.address] = @dasmcol2[di2.address] = [:same, :similar, :badarg, :badop][match_instr(di1, di2)] } end
# File samples/bindiff.rb, line 179 def create_func(dasm, a) h = {} todo = [a] while a = todo.pop next if h[a] h[a] = [] dasm.decoded[a].block.each_to_samefunc(dasm) { |ta| next if not dasm.di_at(ta) todo << ta h[a] << ta } end h end
# File samples/bindiff.rb, line 203 def create_func_stats(dasm, a, g) s = {} s[:blocks] = g.length s[:edges] = 0 # nr of edges s[:leaves] = 0 # nr of nodes with no successor s[:ext_calls] = 0 # nr of jumps out_of_func s[:loops] = 0 # nr of jump back todo = [a] done = [] while aa = todo.pop next if done.include? aa done << aa todo.concat g[aa] s[:edges] += g[aa].length s[:leaves] += 1 if g[aa].empty? dasm.decoded[aa].block.each_to_otherfunc(dasm) { s[:ext_calls] += 1 } end # loop detection # find the longest distance to the root w/o loops g = g.dup while eliminate_one_loop(a, g) s[:loops] += 1 end s end
func addr => { funcblock => list of funcblock to }
# File samples/bindiff.rb, line 168 def create_funcs(dasm) f = {} dasm.entrypoints.to_a.each { |ep| dasm.function[ep] ||= DecodedFunction.new } dasm.function.each_key { |a| next if not dasm.di_at(a) f[a] = create_func(dasm, a) Gui.main_iter } f end
# File samples/bindiff.rb, line 194 def create_funcs_stats(f, dasm) fs = {} f.each { |a, g| fs[a] = create_func_stats(dasm, a, g) Gui.main_iter } fs end
# File samples/bindiff.rb, line 52 def curaddr1; @dasm1.gui.curaddr end
# File samples/bindiff.rb, line 53 def curaddr2; @dasm2.gui.curaddr end
# File samples/bindiff.rb, line 54 def curfunc1; @dasm1.find_function_start(curaddr1) end
# File samples/bindiff.rb, line 55 def curfunc2; @dasm2.find_function_start(curaddr2) end
# File samples/bindiff.rb, line 32 def dasm1; @dasm1 end
# File samples/bindiff.rb, line 33 def dasm1=(d) @dasm1 = d @func1 = nil @funcstat1 = nil @dasmcol1 = {} @dasm1.gui.bg_color_callback = lambda { |a1| COLORS[@dasmcol1[a1] || :default] } @match_func = nil end
# File samples/bindiff.rb, line 42 def dasm2; @dasm2 end
# File samples/bindiff.rb, line 43 def dasm2=(d) @dasm2 = d @func2 = nil @funcstat1 = nil @dasmcol2 = {} @dasm2.gui.bg_color_callback = lambda { |a2| COLORS[@dasmcol2[a2] || :default] } @match_func = nil end
# File samples/bindiff.rb, line 135 def disassemble(method=:disassemble) @func1 = @func2 = @funcstat1 = @funcstat2 = nil set_status('dasm 1') { @dasm1.send(method, curaddr1) @dasm1.gui.focus_addr(curaddr1, :graph) } set_status('dasm 2') { @dasm2.send(method, curaddr2) @dasm2.gui.focus_addr(curaddr2, :graph) } gui_update end
# File samples/bindiff.rb, line 126 def disassemble_all @func1 = @func2 = @funcstat1 = @funcstat2 = nil @dasm1.load_plugin 'dasm_all' @dasm2.load_plugin 'dasm_all' set_status('dasm_all 1') { @dasm1.dasm_all_section '.text' } set_status('dasm_all 2') { @dasm2.dasm_all_section '.text' } gui_update end
# File samples/bindiff.rb, line 234 def eliminate_one_loop(a, g) stack = [] index = {} reach_index = {} done = false curindex = 0 trajan = lambda { |e| index[e] = curindex reach_index[e] = curindex curindex += 1 stack << e g[e].each { |ne| if not index[ne] trajan[ne] break if done reach_index[e] = [reach_index[e], reach_index[ne]].min elsif stack.include? ne reach_index[e] = [reach_index[e], reach_index[ne]].min end } break if done if index[e] == reach_index[e] if (e == stack.last and not g[e].include? e) stack.pop next end # e is the entry in the loop, cut the loop here tail = reach_index.keys.find { |ee| reach_index[ee] == index[e] and g[ee].include? e } g[tail] -= [e] # patch g, but don't modify the original g value (ie -= instead of delete) done = true # one loop found & removed, try again end } trajan[a] done end
# File samples/bindiff.rb, line 56 def func1; @func1 ||= set_status('funcs 1') { create_funcs(@dasm1) } end
# File samples/bindiff.rb, line 57 def func2; @func2 ||= set_status('funcs 2') { create_funcs(@dasm2) } end
# File samples/bindiff.rb, line 58 def funcstat1; @funcstat1 ||= set_status('func stats 1') { create_funcs_stats(func1, @dasm1) } end
# File samples/bindiff.rb, line 59 def funcstat2; @funcstat2 ||= set_status('func stats 2') { create_funcs_stats(func2, @dasm2) } end
# File samples/bindiff.rb, line 65 def gui_update @dasm1.gui.gui_update rescue nil @dasm2.gui.gui_update rescue nil redraw end
# File samples/bindiff.rb, line 26 def initialize_widget(d1=nil, d2=nil) self.dasm1 = d1 if d1 self.dasm2 = d2 if d2 @status = nil end
# File samples/bindiff.rb, line 82 def keypress(key) case key when A keypress(D) keypress(f) keypress(i) when D disassemble_all when c disassemble when C disassemble(:disassemble_fast) when f funcstat1 funcstat2 when g inputbox('address to go', :text => Expression[@dasm1.gui.curaddr]) { |v| @dasm1.gui.focus_addr_autocomplete(v) @dasm2.gui.focus_addr_autocomplete(v) } when M show_match_funcs when m match_one_func(curfunc1, curfunc2) when r puts 'reload' load __FILE__ gui_update when Q Gui.main_quit end end
# File samples/bindiff.rb, line 117 def keypress_ctrl(key) case key when C disassemble(:disassemble_fast_deep) when r inputbox('code to eval') { |c| messagebox eval(c).inspect[0, 512], 'eval' } end end
# File samples/bindiff.rb, line 363 def match_block(b1, b2) # 0 = perfect match (same opcodes, same args) # 1 = same opcodes, same arg type # 2 = same opcodes, diff argtypes # 3 = some opcode difference # 4 = full block difference score = 0 score_div = [b1.list.length, b2.list.length].max.to_f common_start = 0 common_end = 0 # basic diff-style: compare start while it's good, then end, then whats left # should handle most simples cases well len = [b1.list.length, b2.list.length].min while common_start < len and (s = match_instr(b1.list[common_start], b2.list[common_start])) <= 1 score += s / score_div common_start += 1 end while common_start+common_end < len and (s = match_instr(b1.list[-1-common_end], b2.list[-1-common_end])) <= 1 score += s / score_div common_end += 1 end # TODO improve the middle part matching (allow insertions/suppressions/swapping) b1.list[common_start..-1-common_end].zip(b2.list[common_start..-1-common_end]).each { |di1, di2| score += match_instr(di1, di2) / score_div } yield(common_start, common_end) if block_given? # used by colorize_blocks score += (b1.list.length - b2.list.length).abs * 3 / score_div # instr count difference -> +3 per instr score end
return how much match a func in d1 and a func in d2
# File samples/bindiff.rb, line 327 def match_func(a1, a2, do_colorize=true, verb=true) f1 = func1[a1] f2 = func2[a2] raise "dasm1 has no function at #{Expression[a1]}" if not f1 raise "dasm2 has no function at #{Expression[a2]}" if not f2 todo1 = [a1] todo2 = [a2] done1 = [] done2 = [] score = 0.0 # average of the (local best) match_block scores score += 0.01 if @dasm1.get_label_at(a1) != @dasm2.get_label_at(a2) # for thunks score_div = [f1.length, f2.length].max.to_f # XXX this is stupid and only good for perfect matches (and even then it may fail) # TODO handle block split etc (eg instr-level diff VS block-level) while a1 = todo1.shift next if done1.include? a1 t = todo2.map { |a| [a, match_block(@dasm1.decoded[a1].block, @dasm2.decoded[a].block)] } a2 = t.sort_by { |a, s| s }.first if not a2 break end score += a2[1] / score_div a2 = a2[0] done1 << a1 done2 << a2 todo1.concat f1[a1] todo2.concat f2[a2] todo2 -= done2 colorize_blocks(a1, a2) if do_colorize end score += (f1.length - f2.length).abs * 3 / score_div # block count difference -> +3 per block score end
# File samples/bindiff.rb, line 278 def match_funcs @match_funcs ||= {} layout_match = {} set_status('match func layout') { funcstat1.each { |a, s| next if @match_funcs[a] layout_match[a] = [] funcstat2.each { |aa, ss| layout_match[a] << aa if s == ss } Gui.main_iter } } set_status('match funcs') { # refine the layout matching with actual function matching already_matched = [] layout_match.each { |f1, list| puts "matching #{Expression[f1]}" if $VERBOSE begin f2 = (list - already_matched).sort_by { |f| match_func(f1, f, false, false) }.first if f2 already_matched << f2 score = match_func(f1, f2) @match_funcs[f1] = [f2, score] end rescue Interrupt puts 'abort this one' sleep 0.2 # allow a 2nd ^c do escalate end Gui.main_iter } } puts "matched #{@match_funcs.length} - unmatched #{func1.length - @match_funcs.length}" @match_funcs end
# File samples/bindiff.rb, line 416 def match_instr(di1, di2) if not di1 or not di2 or di1.opcode.name != di2.opcode.name 3 elsif di1.instruction.args.map { |a| a.class } != di2.instruction.args.map { |a| a.class } 2 elsif di1.instruction.to_s.gsub(/loc_\w+/, 'loc_') != di2.instruction.to_s.gsub(/loc_\w+/, 'loc_') # local labels TODO compare blocks targeted 1 else 0 end end
# File samples/bindiff.rb, line 318 def match_one_func(a1, a2) s = match_func(a1, a2) puts "match score: #{s}" @match_funcs ||= {} @match_funcs[a1] = [a2, s] gui_update end
# File samples/bindiff.rb, line 61 def paint draw_string_color(:black, @font_width, 3*@font_height, @status || 'idle') end
# File samples/bindiff.rb, line 273 def rematch_funcs @match_funcs = nil match_funcs end
# File samples/bindiff.rb, line 71 def set_status(st=nil) ost = @status @status = st redraw if block_given? ret = protect { yield } set_status ost ret end end
# File samples/bindiff.rb, line 149 def show_match_funcs match_funcs gui_update Gui.main_iter list = [['addr 1', 'addr 2', 'score']] f1 = func1.keys f2 = func2.keys match_funcs.each { |a1, (a2, s)| list << [(@dasm1.get_label_at(a1) || Expression[a1]), (@dasm2.get_label_at(a2) || Expression[a2]), '%.4f' % s] f1.delete a1 f2.delete a2 } f1.each { |a1| list << [(@dasm1.get_label_at(a1) || Expression[a1]), '?', 'nomatch'] } f2.each { |a2| list << ['?', (@dasm2.get_label_at(a2) || Expression[a2]), 'nomatch'] } listwindow("matches", list) { |i| @dasm1.gui.focus_addr i[0], nil, true ; @dasm2.gui.focus_addr i[1], nil, true } end
show in window 1 the match of the function found in win 2
# File samples/bindiff.rb, line 429 def sync1 c2 = curfunc2 if a1 = match_funcs.find_key { |k| match_funcs[k][0] == c2 } @dasm1.gui.focus_addr(a1) end end
# File samples/bindiff.rb, line 436 def sync2 if a2 = match_funcs[curfunc1] @dasm2.gui.focus_addr(a2[0]) end end