searches an object in the attributes of another anyobj.scan_for() => “anyobj.someattr['blabla']”
size of the eip buffer (in dwords)
allows us to be AutoExe.loaded
# File metasm/disassemble.rb, line 2219 def self.autoexe_load(f, &b) d = load(f, &b) d.program end
computes the difference beetween two ruby objects walks accessors, arrays and hashes
# File misc/objdiff.rb, line 10 def Object.diff(o1, o2) if o1.class == o2.class h = {} case o1 when Array, Hash if o1.kind_of? Array keys = (0...[o1.length, o2.length].max).to_a else keys = o1.keys | o2.keys end keys.each { |k| d = diff(o1[k], o2[k]) h["[#{k.inspect}]"] = d if not d.empty? } else a = ($diff_accessor_cache ||= {})[o1.class] ||= ( im = o1.class.public_instance_methods.map { |m| m.to_s }.grep(/^[a-z]/) (im & im.map { |m| m+'=' }).map { |m| m.chop }.find_all { |m| o1.instance_variable_get('@'+m) } ) if a.empty? return o1 == o2 ? h : [o1, o2] end a.each { |k| d = diff(o1.send(k), o2.send(k)) h['.' + k] = d if not d.empty? } end # simplify tree h.keys.each { |k| if h[k].kind_of? Hash and h[k].length == 1 v = h.delete k h[k + v.keys.first] = v.values.first end } h else [o1, o2] end end
# File samples/scan_pt_gnu_stack.rb, line 18 def _printadv(a) $stderr.print a.to_s.ljust(60)[-60, 60] + "\r" end
# File samples/scan_pt_gnu_stack.rb, line 15 def _puts(a) puts a.to_s.ljust(60) end
metasm dasm plugin walks all disassembled instructions referencing an address if the address is a label, update the instruction to use the label esp. useful after a disassemble_fast, with a .map file
# File samples/dasm-plugins/imm2off.rb, line 12 def addrtolabel bp = prog_binding.invert @decoded.each_value { |di| next if not di.kind_of?(DecodedInstruction) di.each_expr { |e| next unless e.kind_of?(Expression) if l = bp[e.lexpr] add_xref(e.lexpr, Xref.new(:addr, di.address)) e.lexpr = Expression[l] end if l = bp[e.rexpr] add_xref(e.rexpr, Xref.new(:addr, di.address)) e.rexpr = (e.lexpr ? Expression[l] : l) end } } nil end
get in interactive assembler mode
# File samples/metasm-shell.rb, line 65 def asm puts "[+] Metasm assembly shell" puts "type help for usage..\n\n" Readline.completion_proc = lambda { |line| %w[help exit quit].find_all { |w| line.downcase == w[0, line.length] } } Readline.completion_append_character = ' ' while line = Readline.readline('asm> ', true) case line when /^help(\W|$)/ puts "", "Type in opcodes to see their binary form", "You can use ';' to type multi-line stuff", "e.g. 'nop nop' will display \"\\x90\\x90\"", "", "exit/quit Quit the console", "help Show this screen", "" when /^(quit|exit)(\W|$)/ break else begin data = line.gsub(';', "\n") next if data.strip.empty? e_data = data.encode puts '"' + e_data.unpack('C*').map { |c| '\x%02x' % c }.join + '"' rescue Metasm::Exception => e puts "Error: #{e.class} #{e.message}" end end end puts end
backtraces the value of an expression from start_addr updates blocks backtracked_for if type is set uses backtrace_walk all values returned are from #backtrace_check_found (which may generate xrefs, labels, addrs to dasm) unless :no_check is specified options:
:include_start => start backtracking including start_addr :from_subfuncret => :origin => origin to set for xrefs when resolution is successful :orig_expr => initial expression :type => xref type (:r, :w, :x, :addr) when :x, the results are added to #addrs_todo :len => xref len (for :r/:w) :snapshot_addr => addr (or array of) where the backtracker should stop if a snapshot_addr is given, values found are ignored if continuing the backtrace does not get to it (eg maxdepth/unk_addr/end) :maxdepth => maximum number of blocks to backtrace :detached => true if backtracking type :x and the result should not have from = origin set in @addrs_todo :max_complexity{_data} => maximum complexity of the expression before aborting its backtrace :log => Array, will be updated with the backtrace evolution :only_upto => backtrace only to update bt_for for current block & previous ending at only_upto :no_check => don't use backtrace_check_found (will not backtrace indirection static values) :terminals => array of symbols with constant value (stop backtracking if all symbols in the expr are terminals) (only supported with no_check)
# File metasm/disassemble.rb, line 1469 def backtrace(expr, start_addr, nargs={}) include_start = nargs.delete :include_start from_subfuncret = nargs.delete :from_subfuncret origin = nargs.delete :origin origexpr = nargs.delete :orig_expr type = nargs.delete :type len = nargs.delete :len snapshot_addr = nargs.delete(:snapshot_addr) || nargs.delete(:stopaddr) maxdepth = nargs.delete(:maxdepth) || @backtrace_maxblocks detached = nargs.delete :detached max_complexity = nargs.delete(:max_complexity) || @backtrace_maxcomplexity max_complexity_data = nargs.delete(:max_complexity) || @backtrace_maxcomplexity_data bt_log = nargs.delete :log # array to receive the ongoing backtrace info only_upto = nargs.delete :only_upto no_check = nargs.delete :no_check terminals = nargs.delete(:terminals) || [] raise ArgumentError, "invalid argument to backtrace #{nargs.keys.inspect}" if not nargs.empty? expr = Expression[expr] origexpr = expr if origin == start_addr start_addr = normalize(start_addr) di = @decoded[start_addr] if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) puts " not backtracking stack address #{expr}" if debug_backtrace return [] end if type == :r or type == :w max_complexity = max_complexity_data maxdepth = @backtrace_maxblocks_data if backtrace_maxblocks_data and maxdepth > @backtrace_maxblocks_data end if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, di, origin, type, len, maxdepth, detached, snapshot_addr)) # no need to update backtracked_for return vals elsif maxdepth <= 0 return [Expression::Unknown] end # create initial backtracked_for if type and origin == start_addr and di btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-1) btt.address = di.address btt.exclude_instr = true if not include_start btt.from_subfuncret = true if from_subfuncret and include_start btt.detached = true if detached di.block.backtracked_for |= [btt] end @callback_prebacktrace[] if callback_prebacktrace # list of Expression/Integer result = [] puts "backtracking #{type} #{expr} from #{di || Expression[start_addr || 0]} for #{@decoded[origin]}" if debug_backtrace or $DEBUG bt_log << [:start, expr, start_addr] if bt_log backtrace_walk(expr, start_addr, include_start, from_subfuncret, snapshot_addr, maxdepth) { |ev, expr_, h| expr = expr_ case ev when :unknown_addr, :maxdepth puts " backtrace end #{ev} #{expr}" if debug_backtrace result |= [expr] if not snapshot_addr @addrs_todo << [expr, (detached ? nil : origin)] if not snapshot_addr and type == :x and origin when :end if not expr.kind_of? StoppedExpr oldexpr = expr expr = backtrace_emu_blockup(h[:addr], expr) puts " backtrace up #{Expression[h[:addr]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr if expr != oldexpr and not snapshot_addr and vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, nil, origin, type, len, maxdepth-h[:loopdetect].length, detached, snapshot_addr)) result |= vals next end end puts " backtrace end #{ev} #{expr}" if debug_backtrace if not snapshot_addr result |= [expr] btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) btt.detached = true if detached @decoded[h[:addr]].block.backtracked_for |= [btt] if @decoded[h[:addr]] @function[h[:addr]].backtracked_for |= [btt] if @function[h[:addr]] and h[:addr] != :default @addrs_todo << [expr, (detached ? nil : origin)] if type == :x and origin end when :stopaddr if not expr.kind_of? StoppedExpr oldexpr = expr expr = backtrace_emu_blockup(h[:addr], expr) puts " backtrace up #{Expression[h[:addr]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr end puts " backtrace end #{ev} #{expr}" if debug_backtrace result |= ((expr.kind_of?(StoppedExpr)) ? expr.exprs : [expr]) when :loop next false if expr.kind_of? StoppedExpr t = h[:looptrace] oldexpr = t[0][0] next false if expr == oldexpr # unmodifying loop puts " bt loop at #{Expression[t[0][1]]}: #{oldexpr} => #{expr} (#{t.map { |z| Expression[z[1]] }.join(' <- ')})" if debug_backtrace bt_log << [:loop, expr, oldexpr, t.map { |z| z[1] }] if bt_log false when :up next false if only_upto and h[:to] != only_upto next expr if expr.kind_of? StoppedExpr oldexpr = expr expr = backtrace_emu_blockup(h[:from], expr) puts " backtrace up #{Expression[h[:from]]}->#{Expression[h[:to]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace bt_log << [:up, expr, oldexpr, h[:from], h[:to]] if bt_log if expr != oldexpr and vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, @decoded[h[:from]], origin, type, len, maxdepth-h[:loopdetect].length, detached, snapshot_addr)) if snapshot_addr expr = StoppedExpr.new vals next expr else result |= vals bt_log << [:found, vals, h[:from]] if bt_log next false end end if origin and type # update backtracked_for update_btf = lambda { |btf, new_btt| # returns true if btf was modified if i = btf.index(new_btt) btf[i] = new_btt if btf[i].maxdepth < new_btt.maxdepth else btf << new_btt end } btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) btt.detached = true if detached if x = di_at(h[:from]) update_btf[x.block.backtracked_for, btt] end if x = @function[h[:from]] and h[:from] != :default update_btf[x.backtracked_for, btt] end if x = di_at(h[:to]) btt = btt.dup btt.address = x.address btt.from_subfuncret = true if h[:sfret] == :subfuncret if backtrace_check_funcret(btt, h[:from], h[:real_to] || h[:to]) puts " function returns to caller" if debug_backtrace next false end if not update_btf[x.block.backtracked_for, btt] puts " already backtraced" if debug_backtrace next false end end end expr when :di, :func next if expr.kind_of? StoppedExpr if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) puts " not backtracking stack address #{expr}" if debug_backtrace next false end oldexpr = expr case ev when :di h[:addr] = h[:di].address expr = backtrace_emu_instr(h[:di], expr) bt_log << [ev, expr, oldexpr, h[:di], h[:addr]] if bt_log and expr != oldexpr when :func expr = backtrace_emu_subfunc(h[:func], h[:funcaddr], h[:addr], expr, origin, maxdepth-h[:loopdetect].length) if snapshot_addr and snapshot_addr == h[:funcaddr] # XXX recursiveness detection needs to be fixed puts " backtrace: recursive function #{Expression[h[:funcaddr]]}" if debug_backtrace next false end bt_log << [ev, expr, oldexpr, h[:funcaddr], h[:addr]] if bt_log and expr != oldexpr end puts " backtrace #{h[:di] || Expression[h[:funcaddr]]} #{oldexpr} => #{expr}" if debug_backtrace and expr != oldexpr if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, h[:di], origin, type, len, maxdepth-h[:loopdetect].length, detached, snapshot_addr)) if snapshot_addr expr = StoppedExpr.new vals else result |= vals bt_log << [:found, vals, h[:addr]] if bt_log next false end elsif expr.complexity > max_complexity puts " backtrace aborting, expr too complex" if debug_backtrace next false end expr else raise ev.inspect end } puts ' backtrace result: ' + result.map { |r| Expression[r] }.join(', ') if debug_backtrace result end
returns an array of expressions, or nil if expr needs more backtrace it needs more backtrace if expr.externals include a Symbol != :unknown (symbol == register value) if it need no more backtrace, expr's indirections are recursively resolved xrefs are created, and di args are updated (immediate => label) if type is :x, addrs_todo is updated, and if di starts a block, expr is checked to see if it may be a subfunction return value
expr indirection are solved by first finding the value of the pointer, and then rebacktracking for write-type access detached is true if type is :x and from should not be set in addrs_todo (indirect call flow, eg external function callback) if the backtrace ends pre entrypoint, returns the value encoded in the raw binary XXX global variable (modified by another function), exported data, multithreaded app.. TODO handle memory aliasing (mov ebx, eax ; write [ebx] ; read [eax]) TODO trace expr evolution through backtrace, to modify immediates to an expr involving label names TODO mov [ptr], imm ; <…> ; jmp [ptr] => rename imm as loc_XX
eg. mov eax, 42 ; add eax, 4 ; jmp eax => mov eax, some_label-4
# File metasm/disassemble.rb, line 1773 def backtrace_check_found(expr, di, origin, type, len, maxdepth, detached, snapshot_addr=nil) # only entrypoints or block starts called by a :saveip are checked for being a function # want to execute [esp] from a block start if type == :x and di and di == di.block.list.first and @cpu.backtrace_is_function_return(expr, @decoded[origin]) and ( # which is an entrypoint.. (not di.block.from_normal and not di.block.from_subfuncret) or # ..or called from a saveip (bool = false ; di.block.each_from_normal { |fn| bool = true if @decoded[fn] and @decoded[fn].opcode.props[:saveip] } ; bool)) # now we can mark the current address a function start # the actual return address will be found later (we tell the caller to continue the backtrace) addr = di.address l = auto_label_at(addr, 'sub', 'loc', 'xref') if not f = @function[addr] f = @function[addr] = DecodedFunction.new puts "found new function #{l} at #{Expression[addr]}" if $VERBOSE end f.finalized = false if @decoded[origin] f.return_address ||= [] f.return_address |= [origin] @decoded[origin].add_comment "endsub #{l}" # TODO add_xref (to update the comment on rename_label) end f.backtracked_for |= @decoded[addr].block.backtracked_for.find_all { |btt| not btt.address } end return if need_backtrace(expr) if snapshot_addr return if expr.expr_externals(true).find { |ee| ee.kind_of?(Indirection) } end puts "backtrace #{type} found #{expr} from #{di} orig #{@decoded[origin] || Expression[origin] if origin}" if debug_backtrace result = backtrace_value(expr, maxdepth) # keep the ori pointer in the results to emulate volatile memory (eg decompiler prefers this) #result << expr if not type # XXX returning multiple values for nothing is too confusing, TODO fix decompiler result.uniq! # create xrefs/labels result.each { |e| backtrace_found_result(e, di, type, origin, len, detached) } if type and origin result end
checks if the BacktraceTrace is a call to a known subfunction returns true and updates self.addrs_todo
# File metasm/disassemble.rb, line 1680 def backtrace_check_funcret(btt, funcaddr, instraddr) if di = @decoded[instraddr] and @function[funcaddr] and btt.type == :x and not btt.from_subfuncret and @cpu.backtrace_is_function_return(btt.expr, @decoded[btt.origin]) and retaddr = backtrace_emu_instr(di, btt.expr) and not need_backtrace(retaddr) puts " backtrace addrs_todo << #{Expression[retaddr]} from #{di} (funcret)" if debug_backtrace di.block.add_to_subfuncret normalize(retaddr) if @decoded[funcaddr].kind_of? DecodedInstruction # check that all callers :saveip returns (eg recursive call that was resolved # before we found funcaddr was a function) @decoded[funcaddr].block.each_from_normal { |fm| if fdi = di_at(fm) and fdi.opcode.props[:saveip] and not fdi.block.to_subfuncret backtrace_check_funcret(btt, funcaddr, fm) end } end if not @function[funcaddr].finalized # the function is not fully disassembled: arrange for the retaddr to be # disassembled only after the subfunction is finished # for that we walk the code from the call, mark each block start, and insert the sfret # just before the 1st function block address in @addrs_todo (which is pop()ed by dasm_step) faddrlist = [] todo = [] di.block.each_to_normal { |t| todo << normalize(t) } while a = todo.pop next if faddrlist.include? a or not get_section_at(a) faddrlist << a if @decoded[a].kind_of? DecodedInstruction @decoded[a].block.each_to_samefunc(self) { |t| todo << normalize(t) } end end idx = @addrs_todo.index(@addrs_todo.find { |r, i, sfr| faddrlist.include? normalize(r) }) || -1 @addrs_todo.insert(idx, [retaddr, instraddr, true]) else @addrs_todo << [retaddr, instraddr, true] end true end end
applies a location binding
# File metasm/disassemble.rb, line 1734 def backtrace_emu_blockup(addr, expr) (ab = @address_binding[addr]) ? Expression[expr.bind(ab).reduce] : expr end
applies one decodedinstruction to an expression
# File metasm/disassemble.rb, line 1723 def backtrace_emu_instr(di, expr) @cpu.backtrace_emu(di, expr) end
applies one subfunction to an expression
# File metasm/disassemble.rb, line 1728 def backtrace_emu_subfunc(func, funcaddr, calladdr, expr, origin, maxdepth) bind = func.get_backtrace_binding(self, funcaddr, calladdr, expr, origin, maxdepth) Expression[expr.bind(bind).reduce] end
creates xrefs, updates addrs_todo, updates instr args
# File metasm/disassemble.rb, line 1944 def backtrace_found_result(expr, di, type, origin, len, detached) n = normalize(expr) fallthrough = true if type == :x and o = di_at(origin) and not o.opcode.props[:stopexec] and n == o.block.list.last.next_addr # delay_slot add_xref(n, Xref.new(type, origin, len)) if origin != :default and origin != Expression::Unknown and not fallthrough unk = true if n == Expression::Unknown add_xref(n, Xref.new(:addr, di.address)) if di and di.address != origin and not unk base = { nil => 'loc', 1 => 'byte', 2 => 'word', 4 => 'dword', 8 => 'qword' }[len] || 'xref' base = 'sub' if @function[n] n = Expression[auto_label_at(n, base, 'xref') || n] if not fallthrough n = Expression[n] # update instr args # TODO trace expression evolution to allow handling of # mov eax, 28 ; add eax, 4 ; jmp eax # => mov eax, (loc_xx-4) if di and not unk and expr != n # and di.address == origin @cpu.replace_instr_arg_immediate(di.instruction, expr, n) end if @decoded[origin] and not unk @cpu.backtrace_found_result(self, @decoded[origin], expr, type, len) end # add comment if type and @decoded[origin] # and not @decoded[origin].instruction.args.include? n @decoded[origin].add_comment "#{type}#{len}:#{n}" if not fallthrough end # check if target is a string if di and type == :r and (len == 1 or len == 2) and s = get_section_at(n) l = s[0].inv_export[s[0].ptr] case len when 1; str = s[0].read(32).unpack('C*') when 2; str = s[0].read(64).unpack('v*') end str = str.inject('') { |str_, c| case c when 0x20..0x7e, \n, \r, \t; str_ << c else break str_ end } if str.length >= 4 di.add_comment "#{'L' if len == 2}#{str.inspect}" str = 'a_' + str.downcase.delete('^a-z0-9')[0, 12] if str.length >= 8 and l[0, 5] == 'byte_' rename_label(l, @program.new_label(str)) end end end # XXX all this should be done in backtrace() { <here> } if type == :x and origin if detached o = @decoded[origin] ? origin : di ? di.address : nil # lib function callback have origin == libfuncname, so we must find a block somewhere else origin = nil @decoded[o].block.add_to_indirect(normalize(n)) if @decoded[o] and not unk else @decoded[origin].block.add_to_normal(normalize(n)) if @decoded[origin] and not unk end @addrs_todo << [n, origin] end end
returns the array of values pointed by the indirection at its invocation (ind.origin) first resolves the pointer using #backtrace_value, if it does not point in edata keep the original pointer then backtraces from ind.origin until it finds an :w xref origin if no :w access is found, returns the value encoded in the raw section data TODO handle unaligned (partial?) writes
# File metasm/disassemble.rb, line 1843 def backtrace_indirection(ind, maxdepth) if not ind.origin puts "backtrace_ind: no origin for #{ind}" if $VERBOSE return [ind] end ret = [] decode_imm = lambda { |addr, len| edata = get_edata_at(addr) if edata Expression[ edata.decode_imm("u#{8*len}".to_sym, @cpu.endianness) ] else Expression::Unknown end } # resolve pointers (they may include Indirections) backtrace_value(ind.target, maxdepth).each { |ptr| # find write xrefs to the ptr refs = [] each_xref(ptr, :w) { |x| # XXX should be rebacktracked on new xref next if not @decoded[x.origin] refs |= [x.origin] } if ptr != Expression::Unknown if refs.empty? if get_section_at(ptr) # static data, newer written : return encoded value ret |= [decode_imm[ptr, ind.len]] next else # unknown pointer : backtrace the indirection, hope it solves itself initval = ind end else # wait until we find a write xref, then backtrace the written value initval = true end # wait until we arrive at an xref'ing instruction, then backtrace the written value backtrace_walk(initval, ind.origin, true, false, nil, maxdepth-1) { |ev, expr, h| case ev when :unknown_addr, :maxdepth, :stopaddr puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtrace ret |= [Expression::Unknown] when :end if not refs.empty? and (expr == true or not need_backtrace(expr)) if expr == true # found a path avoiding the :w xrefs, read the encoded initial value ret |= [decode_imm[ptr, ind.len]] else bd = expr.expr_indirections.inject({}) { |h_, i| h_.update i => decode_imm[i.target, i.len] } ret |= [Expression[expr.bind(bd).reduce]] end else # unknown pointer, backtrace did not resolve... ret |= [Expression::Unknown] end when :di di = h[:di] if expr == true next true if not refs.include? di.address # find the expression to backtrace: assume this is the :w xref from this di writes = get_xrefs_rw(di) writes = writes.find_all { |x_type, x_ptr, x_len| x_type == :w and x_len == ind.len } if writes.length != 1 puts "backtrace_ind: incompatible xrefs to #{ptr} from #{di}" if $DEBUG ret |= [Expression::Unknown] next false end expr = Indirection.new(writes[0][1], ind.len, di.address) end expr = backtrace_emu_instr(di, expr) # may have new indirections... recall bt_value ? #if not need_backtrace(expr) if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) false else expr end when :func next true if expr == true # XXX expr = backtrace_emu_subfunc(h[:func], h[:funcaddr], h[:addr], expr, ind.origin, maxdepth-h[:loopdetect].length) #if not need_backtrace(expr) if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) false else expr end end } } ret end
# File metasm/disassemble.rb, line 1738 def backtrace_update_function_binding(addr, func=@function[addr], retaddrs=func.return_address) @cpu.backtrace_update_function_binding(self, addr, func, retaddrs) end
returns an array of expressions with Indirections resolved (recursive with #backtrace_indirection)
# File metasm/disassemble.rb, line 1822 def backtrace_value(expr, maxdepth) # array of expression with all indirections resolved result = [Expression[expr.reduce]] # solve each indirection sequentially, clone expr for each value (aka cross-product) result.first.expr_indirections.uniq.each { |i| next_result = [] backtrace_indirection(i, maxdepth).each { |rr| next_result |= result.map { |e| Expression[e.bind(i => rr).reduce] } } result = next_result } result.uniq end
backup the executable file
# File samples/dasm-plugins/patch_file.rb, line 11 def backup_program_file f = @program.filename if File.exist?(f) and not File.exist?(f + '.bak') File.open(f + '.bak', 'wb') { |wfd| File.open(f, 'rb') { |rfd| while buf = rfd.read(1024*1024) wfd.write buf end } } end end
# File samples/dasm-plugins/bookmark.rb, line 97 def bookmark_addrs(list, color) al = [list].flatten.uniq gui.session_append("dasm.bookmark_addrs(#{list.inspect}, #{color.inspect})") @bookmarklist |= [al.min] al.each { |a| @bookmarkcolor[a] = color } gui.gui_update end
# File samples/dasm-plugins/bookmark.rb, line 109 def bookmark_delete(list) @bookmarklist -= list list.each { |a| @bookmarkcolor.delete a } end
# File samples/dasm-plugins/bookmark.rb, line 113 def bookmark_delete_color(col) @bookmarkcolor.delete_if { |k, v| if v == col ; @bookmarklist.delete k ; true end } end
# File samples/dasm-plugins/bookmark.rb, line 104 def bookmark_delete_function(addr) return if not fa = find_function_start(addr) list = function_blocks(fa).map { |k, v| block_at(k).list.map { |di| di.address } }.flatten bookmark_delete list end
an api to bookmark a function
# File samples/dasm-plugins/bookmark.rb, line 92 def bookmark_function(addr, color) return if not fa = find_function_start(addr) list = function_blocks(fa).map { |k, v| block_at(k).list.map { |di| di.address } }.flatten bookmark_addrs list, color end
# File misc/tcp_proxy_hex.rb, line 14 def bouncepkt(clt, srv, timeout=nil) s2c = '' c2s = '' loop do break if not IO.select([clt, srv], nil, nil, timeout) while srv and s2c.length < 1024*16 and IO.select([srv], nil, nil, 0) str = (srv.read(1) rescue nil) if not str or str.empty? srv = false else s2c << str end end while clt and c2s.length < 1024*16 and IO.select([clt], nil, nil, 0) str = (clt.read(1) rescue nil) if not str or str.empty? clt = false else c2s << str end end if clt and s2c.length > 0 and IO.select(nil, [clt], nil, 0) puts Time.now.strftime('s -> c %H:%M:%S') s2c.hexdump(:fmt => ['c', 'a']) clt.write s2c s2c.replace '' end if srv and c2s.length > 0 and IO.select(nil, [srv], nil, 0) puts Time.now.strftime('c -> s %H:%M:%S') c2s.hexdump(:fmt => ['c', 'a']) srv.write c2s c2s.replace '' end break if not clt or not srv end end
# File misc/lint.rb, line 42 def compile_warn(tg) r, w = IO.pipe('binary') if !fork r.close $stderr.reopen w $stdout.reopen '/dev/null' exec 'ruby', '-v', '-c', tg exit! else w.close end r end
read a binary file, print a signature for all symbols found
# File samples/generate_libsigs.rb, line 24 def create_sig(exe) func = case exe when COFFArchive; :create_sig_arch when PE, COFF; :create_sig_coff when ELF; :create_sig_elf else raise 'unsupported file format' end send(func, exe) { |sym, edata| sig = edata.data.unpack('H*').first edata.reloc.each { |o, r| # TODO if the reloc points to a known func (eg WinMain), keep the info sz = r.length sig[2*o, 2*sz] = '.' * sz * 2 } next if sig.gsub('.', '').length < 2*$min_sigbytes puts sym sig.scan(/.{1,78}/) { |s| puts ' ' + s } } end
handle coff archives
# File samples/generate_libsigs.rb, line 47 def create_sig_arch(exe) exe.members.each { |m| next if m.name == '/' or m.name == '//' obj = m.exe rescue next create_sig(obj) } end
scan a pe/coff file
# File samples/generate_libsigs.rb, line 80 def create_sig_coff(coff) if coff.kind_of? PE # dll # dll # TODO else coff.symbols.to_a.compact.each { |sym| next if sym.type != 'FUNCTION' next if not sym.sec_nr.kind_of? Integer data = coff.sections[sym.sec_nr-1].encoded off = sym.value len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length yield sym.name, data[off, len] } end end
scan an elf file
# File samples/generate_libsigs.rb, line 56 def create_sig_elf(elf) elf.symbols.each { |sym| next if sym.type != 'FUNC' or sym.shndx == 'UNDEF' if elf.header.type == 'REL' next if not data = elf.sections[sym.shndx].encoded off = sym.value else next if not seg = elf.segments.find { |s| s.type == 'LOAD' and sym.value >= s.vaddr and sym.value < s.vaddr+s.memsz } next if not data = seg.encoded off = sym.value - seg.vaddr end len = sym.size if len == 0 len = data.export.find_all { |k, o| o > off and k !~ /_uuid/ }.transpose[1].to_a.min || data.length len -= off len = 256 if len > 256 end yield sym.name, data[off, len] } end
metasm dasm plugin: retrieve a section section, and disassemble everything it can, skipping existing code and nops usage: load the plugin, then call (ruby snipped): dasm.dasm_all_section '.text'
# File samples/dasm-plugins/dasm_all.rb, line 9 def dasm_all(addrstart, length, method=:disassemble_fast_deep) s = get_section_at(addrstart) return if not s s = s[0] boff = s.ptr off = 0 while off < length if di = di_at(addrstart + off) off += di.bin_length elsif @decoded[addrstart+off] off += 1 else s.ptr = boff+off maydi = cpu.decode_instruction(s, 0) if not maydi off += 1 elsif maydi.instruction.to_s =~ /nop|lea (.*), \[\1(?:\+0)?\]|mov (.*), \2|int 3/ off += maydi.bin_length else puts "dasm_all: found #{Expression[addrstart+off]}" if $VERBOSE send(method, addrstart+off) end end Gui.main_iter if gui and off & 15 == 0 end count = 0 off = 0 while off < length addr = addrstart+off if di = di_at(addr) if di.block_head? b = di.block if not @function[addr] and b.from_subfuncret.to_a.empty? and b.from_normal.to_a.empty? l = auto_label_at(addr, 'sub_orph') puts "dasm_all: found orphan function #{l}" @function[addrstart+off] = DecodedFunction.new @function[addrstart+off].finalized = true detect_function_thunk(addr) count += 1 end end off += di.bin_length else off += 1 end Gui.main_iter if gui and off & 15 == 0 end puts "found #{count} orphan functions" if $VERBOSE gui.gui_update if gui end
# File samples/dasm-plugins/dasm_all.rb, line 63 def dasm_all_section(name, method=:disassemble_fast_deep) section_info.each { |n, a, l, i| if name == n dasm_all(Expression[a].reduce, l, method) end } true end
# File metasm/disassemble.rb, line 2211 def decompile(*addr) decompiler.decompile(*addr) end
# File metasm/disassemble.rb, line 2214 def decompile_func(addr) decompiler.decompile_func(addr) end
# File metasm/disassemble.rb, line 2204 def decompiler parse_c '' if not c_parser @decompiler ||= Decompiler.new(self) end
# File metasm/disassemble.rb, line 2208 def decompiler=(dc) @decompiler = dc end
metasm dasm plugin: try to demangle all labels as c++ names, add them as comment if successful
# File samples/dasm-plugins/demangle_cpp.rb, line 10 def demangle_all_cppnames cnt = 0 prog_binding.each { |name, addr| cname = name.sub(/^thunk_/, '') if dname = demangle_cppname(cname) cnt += 1 add_comment(addr, dname) each_xref(addr, :x) { |xr| if di = di_at(xr.origin) di.add_comment dname di.comment.delete "x:#{name}" end } end } cnt end
dumps the source, optionnally including data yields (defaults puts) each line
# File metasm/disassemble.rb, line 2019 def dump(dump_data=true, &b) b ||= lambda { |l| puts l } @sections.sort_by { |addr, edata| addr.kind_of?(::Integer) ? addr : 0 }.each { |addr, edata| addr = Expression[addr] if addr.kind_of? ::String blockoffs = @decoded.values.grep(DecodedInstruction).map { |di| Expression[di.block.address, :-, addr].reduce if di.block_head? }.grep(::Integer).sort.reject { |o| o < 0 or o >= edata.length } b[@program.dump_section_header(addr, edata)] if not dump_data and edata.length > 16*1024 and blockoffs.empty? b["// [#{edata.length} data bytes]"] next end unk_off = 0 # last off displayed # blocks.sort_by { |b| b.addr }.each { |b| while unk_off < edata.length if unk_off == blockoffs.first blockoffs.shift di = @decoded[addr+unk_off] if unk_off != di.block.edata_ptr b["\n// ------ overlap (#{unk_off-di.block.edata_ptr}) ------"] elsif di.block.from_normal.kind_of? ::Array b["\n"] end dump_block(di.block, &b) unk_off += [di.block.bin_length, 1].max unk_off = blockoffs.first if blockoffs.first and unk_off > blockoffs.first else next_off = blockoffs.first || edata.length if dump_data or next_off - unk_off < 16 unk_off = dump_data(addr + unk_off, edata, unk_off, &b) else b["// [#{next_off - unk_off} data bytes]"] unk_off = next_off end end end } end
dumps a block of decoded instructions
# File metasm/disassemble.rb, line 2057 def dump_block(block, &b) b ||= lambda { |l| puts l } block = @decoded[block].block if @decoded[block] dump_block_header(block, &b) block.list.each { |di| b[di.show] } end
shows the xrefs/labels at block start
# File metasm/disassemble.rb, line 2065 def dump_block_header(block, &b) b ||= lambda { |l| puts l } xr = [] each_xref(block.address) { |x| case x.type when :x; xr << Expression[x.origin] when :r, :w; xr << "#{x.type}#{x.len}:#{Expression[x.origin]}" end } if not xr.empty? b["\n// Xrefs: #{xr[0, 8].join(' ')}#{' ...' if xr.length > 8}"] end if block.edata.inv_export[block.edata_ptr] and label_alias[block.address] b["\n"] if xr.empty? label_alias[block.address].each { |name| b["#{name}:"] } end if c = @comment[block.address] c = c.join("\n") if c.kind_of? ::Array c.each_line { |l| b["// #{l}"] } end end
dumps data/labels, honours @xrefs.len if exists dumps one line only stops on end of edata/@decoded/@xref returns the next offset to display TODO array-style data access
# File metasm/disassemble.rb, line 2092 def dump_data(addr, edata, off, &b) b ||= lambda { |l| puts l } if l = edata.inv_export[off] and label_alias[addr] l_list = label_alias[addr].sort l = l_list.pop || l l_list.each { |ll| b["#{ll}:"] } l = (l + ' ').ljust(16) else l = '' end elemlen = 1 # size of each element we dump (db by default) dumplen = -off % 16 # number of octets to dump dumplen = 16 if dumplen == 0 cmt = [] each_xref(addr) { |x| dumplen = elemlen = x.len if x.len == 2 or x.len == 4 cmt << " #{x.type}#{x.len}:#{Expression[x.origin]}" } cmt = " ; @#{Expression[addr]}" + cmt.sort[0, 6].join if r = edata.reloc[off] dumplen = elemlen = r.type.to_s[1..-1].to_i/8 end dataspec = { 1 => 'db ', 2 => 'dw ', 4 => 'dd ', 8 => 'dq ' }[elemlen] if not dataspec dataspec = 'db ' elemlen = 1 end l << dataspec # dup(?) if off >= edata.data.length dups = edata.virtsize - off @prog_binding.each_value { |a| tmp = Expression[a, :-, addr].reduce dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups } @xrefs.each_key { |a| tmp = Expression[a, :-, addr].reduce dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups } dups /= elemlen dups = 1 if dups < 1 b[(l + "#{dups} dup(?)").ljust(48) << cmt] return off + dups*elemlen end vals = [] edata.ptr = off dups = dumplen/elemlen elemsym = "u#{elemlen*8}".to_sym while edata.ptr < edata.data.length if vals.length > dups and vals.last != vals.first # we have a dup(), unread the last element which is different vals.pop addr = Expression[addr, :-, elemlen].reduce edata.ptr -= elemlen break end break if vals.length == dups and vals.uniq.length > 1 vals << edata.decode_imm(elemsym, @cpu.endianness) addr += elemlen if i = (1-elemlen..0).find { |i_| t = addr + i_ @xrefs[t] or @decoded[t] or edata.reloc[edata.ptr+i_] or edata.inv_export[edata.ptr+i_] } # i < 0 edata.ptr += i addr += i break end break if edata.reloc[edata.ptr-elemlen] end # line of repeated value => dup() if vals.length > 8 and vals.uniq.length == 1 b[(l << "#{vals.length} dup(#{Expression[vals.first]})").ljust(48) << cmt] return edata.ptr end # recognize strings vals = vals.inject([]) { |vals_, value| if (elemlen == 1 or elemlen == 2) case value when 0x20..0x7e, 0x0a, 0x0d if vals_.last.kind_of? ::String; vals_.last << value ; vals_ else vals_ << value.chr end else vals_ << value end else vals_ << value end } vals.map! { |value| if value.kind_of? ::String if value.length > 2 # or value == vals.first or value == vals.last # if there is no xref, don't care value.inspect else value.unpack('C*').map { |c| Expression[c] } end else Expression[value] end } vals.flatten! b[(l << vals.join(', ')).ljust(48) << cmt] edata.ptr end
# File misc/ppc_pdf2oplist.rb, line 100 def epilog puts "\n\t@field_shift = {" puts $field_shift.sort_by { |k, v| k.to_s }.enum_slice(6).map { |slc| "\t\t" + slc.map { |k, v| "#{k.inspect} => #{v}" }.join(', ') }.join(",\n") puts "\t}" puts "\n\t@field_mask = {" puts $field_mask.sort_by { |k, v| k.to_s }.enum_slice(6).map { |slc| "\t\t" + slc.map { |k, v| "#{k.inspect} => #{v > 1000 ? '0x%X' % v : v}" }.join(', ') }.join(",\n") puts "\t}" end
parse asm to a regexp, return the list of addresses matching
# File samples/dasm-plugins/findgadget.rb, line 47 def findgadget_asm(asm) pattern_scan(findgadget_asm_to_regex(asm)) end
metasm dasm plugin scan for a given asm instruction sequence (all encodings) add the G dasm-gui shortcut, the input change ';' for line splits
# File samples/dasm-plugins/findgadget.rb, line 11 def findgadget_asm_to_regex(asm) fullre = '' asm = asm.gsub(';', "\n") sc = Shellcode.new(@cpu) sc.parse asm sc.source.each { |i| case i when Data opts_edata = i.encode(@cpu.endianness) when Instruction opts_edata = @cpu.encode_instruction(sc, i) else raise "cant scan for #{i}" end opts_edata = [opts_edata] if opts_edata.kind_of?(EncodedData) opts_re = opts_edata.map { |ed| # Regexp.escape ed.data, with relocs replaced with '.' re = '' off = 0 ed.reloc.sort.each { |o, rel| re << Regexp.escape(ed.data[off...o]) re << ('.' * rel.length) off = o + rel.length } re << Regexp.escape(ed.data[off..-1]) } fullre << '(' << opts_re.join('|') << ')' } Regexp.new(fullre, Regexp::MULTILINE, 'n') end
# File samples/dasm-plugins/findgadget.rb, line 51 def findgadget_prompt gui.inputbox("source for the gadget - separate with ;") { |asm| lst = findgadget_asm(asm) list = [['address', 'section']] sections = section_info list += lst.map { |addr| # [name, addr, len, misc] if s = sections.find { |s_| s_[1] <= addr and s_[1] + s_[2] > addr } s = s[0] else s = '?' end [Expression[addr], s] } gui.listwindow("gadgetscan for #{asm}", list) { |args| gui.focus_addr(args[0]) } } end
iterates over all instructions of a function from a given entrypoint carries an object while walking, the object is yielded every instruction every block is walked only once, after all previous blocks are done (if possible) on a 'jz', a [:clone] event is yielded for every path beside the first on a juction (eg a -> b -> d, a -> c -> d), a [:merge] event occurs if froms have different objs event list:
[:di, <addr>, <decoded_instruction>, <object>] [:clone, <newaddr>, <oldaddr>, <object>] [:merge, <newaddr>, {<oldaddr1> => <object1>, <oldaddr2> => <object2>, ...}, <object1>] [:subfunc, <subfunc_addr>, <call_addr>, <object>]
all events should return an object :merge has a copy of object1 at the end so that uninterested callers can always return args if an event returns false, the trace stops for the current branch
# File metasm/disassemble.rb, line 1371 def function_walk(addr_start, obj_start) # addresses of instrs already seen => obj done = {} todo = [[addr_start, obj_start]] while hop = todo.pop addr, obj = hop next if done.has_key?(done) di = di_at(addr) next if not di if done.empty? dilist = di.block.list[di.block.list.index(di)..-1] else # new block, check all 'from' have been seen if not hop[2] # may retry later all_ok = true di.block.each_from_samefunc(self) { |fa| all_ok = false unless done.has_key?(fa) } if not all_ok todo.unshift([addr, obj, true]) next end end froms = {} di.block.each_from_samefunc(self) { |fa| froms[fa] = done[fa] if done[fa] } if froms.values.uniq.length > 1 obj = yield([:merge, addr, froms, froms.values.first]) next if obj == false end dilist = di.block.list end if dilist.each { |_di| break if done.has_key?(_di.address) # looped back into addr_start done[_di.address] = obj obj = yield([:di, _di.address, _di, obj]) break if obj == false # also return false for the previous 'if' } from = dilist.last.address if di.block.to_normal and di.block.to_normal[0] and di.block.to_subfuncret and di.block.to_subfuncret[0] # current instruction block calls into a subfunction obj = di.block.to_normal.map { |subf| yield([:subfunc, subf, from, obj]) }.first # propagate 1st subfunc result next if obj == false end wantclone = false di.block.each_to_samefunc(self) { |ta| if wantclone nobj = yield([:clone, ta, from, obj]) next if obj == false todo << [ta, nobj] else todo << [ta, obj] wantclone = true end } end end end
shows the preprocessor path to find a specific line usage: ruby chdr-find.rb 'regex pattern' list of files.h
# File misc/cheader-findpppath.rb, line 11 def gets l = $ungets $ungets = nil l || super() end
# File samples/dasm-plugins/export_graph_svg.rb, line 13 def graph_to_svg gw = gui.curview.dup class << gw attr_accessor :svgbuf, :svgcol def draw_color(col) col = @default_color_association.fetch(col, col) col = BasicColor.fetch(col, col) @svgcol = "##{col}" end def draw_line(x1, y1, x2, y2) bb(x1, y1, x2, y2) svgbuf << %Q{<line x1="#{x1}" y1="#{y1}" x2="#{x2}" y2="#{y2}" stroke="#{@svgcol}" />\n} end def draw_rectangle(x, y, w, h) bb(x, y, x+w, y+h) svgbuf << %Q{<rect x="#{x}" y="#{y}" width="#{w}" height="#{h}" fill="#{@svgcol}" />\n} end def draw_string(x, y, str) bb(x, y, x+str.length*@font_width, y+@font_height) stre = str.gsub('<', '<').gsub('>', '>') svgbuf << %Q{<text x="#{(0...str.length).map { |i| x+i*@font_width }.join(',')}" y="#{y+@font_height*0.7}" stroke="#{@svgcol}">#{stre}</text>\n} end def draw_rectangle_color(c, *a) draw_color(c) draw_rectangle(*a) end def draw_line_color(c, *a) draw_color(c) draw_line(*a) end def draw_string_color(c, *a) draw_color(c) draw_string(*a) end def focus?; false; end def view_x; @svgvx ||= @curcontext.boundingbox[0]-20; end def view_y; @svgvy ||= @curcontext.boundingbox[1]-20; end def width; @svgvw ||= (@curcontext ? (@curcontext.boundingbox[2]-@curcontext.boundingbox[0])*@zoom+20 : 800); end def height; @svgvh ||= (@curcontext ? (@curcontext.boundingbox[3]-@curcontext.boundingbox[1])*@zoom+20 : 600); end def svgcuraddr; @curcontext ? @curcontext.root_addrs.first : current_address; end # drawing bounding box (for the background rectangle) attr_accessor :bbx, :bby, :bbxm, :bbym def bb(x1, y1, x2, y2) @bbx = [x1, x2, @bbx].compact.min @bbxm = [x1, x2, @bbxm].compact.max @bby = [y1, y2, @bby].compact.min @bbym = [y1, y2, @bbym].compact.max end end ret = gw.svgbuf = '' gw.paint ret[0, 0] = <<EOS <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg xmlns="http://www.w3.org/2000/svg" font-family="courier, monospace"> <desc>Graph of #{get_label_at(gw.svgcuraddr) || Expression[gw.svgcuraddr]}</desc> <rect x="#{gw.bbx-10}" y="#{gw.bby-10}" width="#{gw.bbxm-gw.bbx+20}" height="#{gw.bbym-gw.bby+20}" fill="#{gw.draw_color(:background)}" />" EOS ret << %Q{</svg>} end
# File samples/dbg-plugins/heapscan.rb, line 166 def gui_show_array(addr) head = resolve(addr) e = @heap.xrchunksto[head].to_a.find { |ee| @heap.arrays[ee] and @heap.arrays[ee][head] } return if not e lst = @heap.arrays[e][head] if not st = @heap.chunk_struct[head] st = Metasm::C::Struct.new st.name = "array_#{'%x' % head}" st.members = [] (@heap.chunks[head] / 4).times { |i| n = "u#{i}" v = @memory[head+4*i, 4].unpack('L').first if @heap.chunks[v] t = Metasm::C::Pointer.new(Metasm::C::BaseType.new(:void)) else t = Metasm::C::BaseType.new(:int) end st.members << Metasm::C::Variable.new(n, t) } @heap.cp.toplevel.struct[st.name] ||= st end @heap.chunk_struct[head] = st $ghw.addr_struct = { head => @heap.cp.decode_c_struct(st.name, @memory, head) } if not st = lst.map { |l| @heap.chunk_struct[l] }.compact.first e = lst.first st = Metasm::C::Struct.new st.name = "elem_#{'%x' % head}" st.members = [] (@heap.chunks[e] / 4).times { |i| n = "u#{i}" v = @memory[e+4*i, 4].unpack('L').first if @heap.chunks[v] t = Metasm::C::Pointer.new(Metasm::C::BaseType.new(:void)) else t = Metasm::C::BaseType.new(:int) end st.members << Metasm::C::Variable.new(n, t) } @heap.cp.toplevel.struct[st.name] ||= st end lst.each { |l| @heap.chunk_struct[l] = st } lst.each { |aa| $ghw.addr_struct[aa] = @heap.cp.decode_c_struct(st.name, @memory, aa) } gui.parent_widget.mem.focus_addr(head, :graphheap) end
# File samples/dbg-plugins/heapscan.rb, line 135 def gui_show_list(addr) a = resolve(addr) #@heap.cp.parse("struct ptr { void *ptr; };") if not @heap.cp.toplevel.struct['ptr'] h = @heap.linkedlists[a] off = h.keys.first lst = h[off] if not st = lst.map { |l| @heap.chunk_struct[l] }.compact.first st = Metasm::C::Struct.new st.name = "list_#{'%x' % lst.first}" st.members = [] (@heap.chunks[lst.first] / 4).times { |i| n = "u#{i}" t = Metasm::C::BaseType.new(:int) if i == off/4 n = "next" t = Metasm::C::Pointer.new(st) end st.members << Metasm::C::Variable.new(n, t) } @heap.cp.toplevel.struct[st.name] = st end lst.each { |l| @heap.chunk_struct[l] = st } $ghw.addr_struct = {} lst.each { |aa| $ghw.addr_struct[aa] = @heap.cp.decode_c_struct(st.name, @memory, aa) } gui.parent_widget.mem.focus_addr(lst.first, :graphheap) end
# File samples/shellcode-dynlink.rb, line 104 def hash_name(sym) hash = 0 sym.each_byte { |char| hash = (((hash >> 0xd) | (hash << (32-0xd))) + char) & 0xffff_ffff } hash end
# File samples/dbg-plugins/heapscan.rb, line 49 def heap; @heap ; end
# File samples/dbg-plugins/heapscan.rb, line 50 def heap=(h) ; @heap = h ; end
# File samples/dbg-plugins/heapscan.rb, line 129 def heapscan_graph heapscan_time @heap.dump_graph heapscan_time 'graph.gv' end
# File samples/dbg-plugins/heapscan.rb, line 117 def heapscan_kernels heapscan_time @heap.find_kernels heapscan_time "#{@heap.kernels.length} kernels" end
# File samples/dbg-plugins/heapscan.rb, line 123 def heapscan_roots heapscan_time @heap.find_roots heapscan_time "#{@heap.roots.length} roots" end
# File samples/dbg-plugins/heapscan.rb, line 52 def heapscan_scan(xr=true) heaps = [] mmaps = [] libc = nil pr = os_process pr.mappings.each { |a, l, p, f| case f.to_s when /heap/ heaps << [a, l] when /libc[^a-zA-Z]/ libc ||= a if p == 'r-xp' when '' mmaps << [a, l] end } heapscan_time '' @disassembler.parse_c '' if pr and OS.current.name =~ /winos/ if OS.current.version[0] == 5 @heap = WindowsHeap.new(self) @heap.cp = @disassembler.c_parser @heap.cp.parse_file File.join($heapscan_dir, 'winheap.h') unless @heap.cp.toplevel.struct['_HEAP'] else @heap = Windows7Heap.new(self) @heap.cp = @disassembler.c_parser @heap.cp.parse_file File.join($heapscan_dir, 'winheap7.h') unless @heap.cp.toplevel.struct['_HEAP'] end @heap.heaps = heaps else @heap = LinuxHeap.new(self) @heap.cp = @disassembler.c_parser @heap.mmaps = mmaps @heap.scan_libc(libc) heapscan_time "libc!main_arena #{'%x' % @heap.main_arena_ptr}" end hsz = 0 (heaps + mmaps).each { |a, l| hsz += l @heap.range.update a => l } log "#{hsz/1024/1024}M heap" @heap.scan_chunks heapscan_time "#{@heap.chunks.length} chunks" return if not xr @heap.scan_chunks_xr heapscan_time "#{@heap.xrchunksto.length} src, #{@heap.xrchunksfrom.length} dst" end
# File samples/dbg-plugins/heapscan.rb, line 105 def heapscan_structs heapscan_time @heap.bucketize heapscan_time "#{@heap.buckets.length} buckets" @heap.find_arrays heapscan_time "#{@heap.allarrays.length} arrays (#{@heap.allarrays.flatten.length} elems)" @heap.find_linkedlists heapscan_time "#{@heap.alllists.length} lists (#{@heap.alllists.flatten.length} elems)" end
# File samples/dbg-plugins/heapscan.rb, line 41 def heapscan_time(s='') @heapscan_time ||= nil t = Time.now log s + ' %.2fs' % (t-@heapscan_time) if @heapscan_time and s != '' @heapscan_time = t Gui.main_iter if gui end
find immediate exprs in the instruction at addr, yield them
# File samples/dasm-plugins/c_constants.rb, line 11 def imm_to_const(addr) return if not di = di_at(addr) # TODO enter into memrefs ? di.instruction.args.grep(Expression).each { |a| i = a.reduce next if not i.kind_of? Integer next if not cstbase = yield(i) if c = imm_to_const_decompose(i, cstbase) di.add_comment c end } end
find the bitwise decomposition of imm into constants whose name include cstbase
# File samples/dasm-plugins/c_constants.rb, line 25 def imm_to_const_decompose(imm, cstbase) cstbase = /#{cstbase}/ if not cstbase.kind_of? Regexp dict = {} c_parser.lexer.definition.keys.grep(cstbase).each { |cst| if i = c_parser.macro_numeric(cst) dict[cst] = i end } c_parser.toplevel.symbol.each { |k, v| dict[k] = v if v.kind_of? Integer and k =~ cstbase } dict.delete_if { |k, v| imm & v != v } if cst = dict.index(imm) cst else # a => 1, b => 2, c => 4, all => 7: discard abc, keep 'all' dict.delete_if { |k, v| dict.find { |kk, vv| vv > v and vv & v == v } } dict.keys.join(' | ') if not dict.empty? end end
# File metasm/disassemble.rb, line 2007 def inspect "<Metasm::Disassembler @%x>" % object_id end
# File samples/shellcode-dynlink.rb, line 112 def lib_name(sym) raise "unknown libname for #{sym}" if not lib = Metasm::WindowsExports::EXPORT[sym] n = lib.downcase[0, 4].unpack('C*') n[0] + (n[1]<<8) + (n[2] << 16) + (n[3] << 24) end
this is a ruby code cleaner tool it passes its argument to ruby -v -c, which displays warnings (eg unused variable) it shows the incriminated line along the warning, to help identify false positives probably linux-only, and need ruby-1.9.1 or newer
# File misc/lint.rb, line 13 def lint(tg) if File.symlink?(tg) # nothing elsif File.directory?(tg) Dir.entries(tg).each { |ent| next if ent == '.' or ent == '..' ent = File.join(tg, ent) lint(ent) if File.directory?(ent) or ent =~ /\.rb$/ } else lint_file(tg) end end
# File misc/lint.rb, line 27 def lint_file(tg) flines = nil compile_warn(tg).each_line { |line| file, lineno, warn = line.split(/\s*:\s*/, 3) if file == tg if not flines puts "#{tg}:" flines = File.readlines(file) #File.open(file, 'rb') { |fd| fd.readlines } end puts " l.#{lineno}: #{warn.strip}: #{flines[lineno.to_i-1].strip.inspect}" end } puts if flines end
# File samples/r0trace.rb, line 245 def loadmod(mod=$drv) sh = DynLdr.openscmanagera(0, 0, DynLdr::SC_MANAGER_ALL_ACCESS) raise "cannot openscm" if (sh == 0) rh = DynLdr.createservicea(sh, mod, mod, DynLdr::SERVICE_ALL_ACCESS, DynLdr::SERVICE_KERNEL_DRIVER, DynLdr::SERVICE_DEMAND_START, DynLdr::SERVICE_ERROR_NORMAL, File.expand_path(mod), 0, 0, 0, 0, 0) if (DynLdr.startservicea(rh, 0, 0) == 0) raise "cannot start service" end DynLdr.CloseServiceHandle(rh) DynLdr.CloseServiceHandle(sh) end
A script to help finding performance bottlenecks:
$ ruby-prof myscript.rb
=> String#+ gets called 50k times and takes 30s
$ LOGCALLER='String#+' ruby -r bottleneck myscript.rb
=> String#+ called 40k times from: stuff.rb:42 in Myclass#uglymethod from stuff.rb:32 in Myclass#initialize
now you know what to rewrite
# File misc/bottleneck.rb, line 20 def log_caller(cls, meth, singleton=false, histlen=nil) histlen ||= ENV.fetch('LOGCALLER_MAXHIST', 16).to_i dec_meth = 'm_' + meth.to_s.gsub(/[^\w]/) { |c| c.unpack('H*')[0] } malias = dec_meth + '_log_caller' mcntr = '$' + dec_meth + '_counter' eval <<EOS #{cls.kind_of?(Class) ? 'class' : 'module'} #{cls} #{'class << self' if singleton} alias #{malias} #{meth} def #{meth}(*a, &b) #{mcntr}[caller[0, #{histlen}]] += 1 #{malias}(*a, &b) end #{'end' if singleton} end #{mcntr} = Hash.new(0) at_exit { total = #{mcntr}.inject(0) { |a, (k, v)| a+v } puts "\#{total} callers of #{cls} #{meth}:" #{mcntr}.sort_by { |k, v| -v }[0, 4].each { |k, v| puts " \#{'%.2f%%' % (100.0*v/total)} - \#{v} times from", k, '' } } EOS end
handle instruction aliases NOT WORKING should be implemented in the parser/displayer instead of opcode list manual work needed for eg conditionnal jumps
# File misc/ppc_pdf2oplist.rb, line 85 def make_alias(newop, newargs, oldop, oldargs) raise "unknown alias #{newop} => #{oldop}" if not op = $opcodes.reverse.find { |op_| op_[0] == oldop } op2 = op.dup op2[0] = newop oldargs.each_with_index { |oa, i| # XXX bcctr 4, 6 -> bcctr 4, 6, 0 => not the work if oa =~ /^[0-9]+$/ or oa =~ /^0x[0-9a-f]+$/ fld = op[2][i] op2[1] |= Integer(oa) << $field_shift[fld] end } puts "#\talias #{newop} #{newargs.join(', ')} -> #{oldop} #{oldargs.join(', ')}".downcase end
# File misc/ppc_pdf2oplist.rb, line 14 def make_instr(bins, bits, text) # calc bitfields length from their offset last = 32 bitlen = [] bits.reverse_each { |bit| bitlen.unshift last-bit last = bit } # the opcode binary value (w/o fields) bin = 0 fields = [] # parse the data bins.zip(bits, bitlen).each { |val, off, len| off = 32-(off+len) msk = (1 << len) - 1 case val when '/', '//', '///' # reserved field, value unspecified when /^\d+$/; bin |= val.to_i << off # constant field when /^[A-Za-z]+$/ fld = val.downcase.to_sym fld = "#{fld}_".to_sym while $field_mask[fld] and ($field_mask[fld] != msk or $field_shift[fld] != off) fields << fld $field_mask[fld] ||= msk $field_shift[fld] ||= off end } text.each { |txt| # fnabs FRT,FRB (Rc=0) curbin = bin curfields = fields.dup txt.sub!(' Rc=1)', ' (Rc=1)') if txt.include? 'fdiv.' # typo: fdiv. has no '(' if txt =~ /(.*\S)\s*\((\w+=.*)\)/ txt = $1 $2.split.each { |e| raise e if e !~ /(\w+)=(\d+)/ name, val = $1.downcase, $2.to_i raise "bad bit #{name} in #{txt}" if not fld = curfields.find { |fld_| fld_.to_s.delete('_') == name } curfields.delete fld curbin |= val << $field_shift[fld] } end opname, args = txt.split(/\s+/, 2) args = args.to_s.downcase.split(/\s*,\s*/).map { |arg| fld = curfields.find { |fld_| fld_.to_s.delete('_') == arg } ; curfields.delete fld ; fld } if args.include? nil and curfields.length == 2 and (curfields - [:ra, :d]).empty? args[args.index(nil)] = :ra_i16 curfields.clear elsif args.include? nil and curfields.length == 2 and (curfields - [:ra, :ds]).empty? args[args.index(nil)] = :ra_i16s curfields.clear elsif args.include? nil and curfields.length == 2 and (curfields - [:ra, :dq]).empty? args[args.index(nil)] = :ra_i16q curfields.clear elsif args.include? nil and curfields.length == 1 args[args.index(nil)] = curfields.shift end raise "bad args #{args.inspect} (#{curfields.inspect}) in #{txt}" if args.include? nil $opcodes << [opname, curbin, args] n = (opname.inspect << ',').ljust(10) + '0x%08X' % curbin n << ', ' if not args.empty? puts "\taddop " + n + args.map { |e| e.inspect }.join(', ') } end
# File samples/dasm-plugins/match_libsigs.rb, line 82 def match_libsigs(sigfile) ls = LibSignature.new(sigfile) count = 0 @sections.each { |b, s| count += ls.match(s.data) { |off, sym| set_label_at(b+off, sym) } } count end
returns true if the expression needs more backtrace it checks for the presence of a symbol (not :unknown), which means it depends on some register value
# File metasm/disassemble.rb, line 1754 def need_backtrace(expr, terminals=[]) return if expr.kind_of? ::Integer !(expr.externals.grep(::Symbol) - [:unknown] - terminals).empty? end
# File misc/cheader-findpppath.rb, line 17 def parse(root=false) want = false ret = [] while l = gets case l = l.strip when /^#if/ ret << l r = parse(true) if r.empty? ret.pop else want = true rr = r.pop ret.concat r.map { |l_| (l_[0,3] == '#el' ? ' ' : ' ') << l_ } ret << rr end when /^#el/ if not root $ungets = l break end ret << l r = parse want = true if not r.empty? ret.concat r when /^#endif/ if not root $ungets = l break end ret << l break when /#$srch/ #, /^#include/ want = true ret << l end end want ? ret : [] end
# File misc/ppc_pdf2oplist.rb, line 114 def parse_page(lines) # all instr defining pages include this return unless lines.find { |l| l.str =~ /Special Registers Altered|Memory Barrier Instructions|Data Cache Instructions/ } # sync L/dcbt ilist = [] # line buffer extended = false # concat lines with same y lines = lines.sort_by { |l| [-l.y, l.x] } lastline = nil lines.delete_if { |l| if lastline and lastline.y == l.y and ([lastline.fontx, lastline.fonty] == [l.fontx, l.fonty] or l.str =~ /^\s*$/) lastline.str << ' ' << l.str true else lastline = l false end } lines.each { |l| # search for the bit indices list if l.fonty < 7 and l.str =~ /^0 [\d ]+ 31\s*$/ and (ilist.last.str.split.length == l.str.split.length or ilist.last.str.split.length == l.str.split.length-1) $foundop = true bitindices = l.str.split.map { |i| i.to_i } # previous line is the binary encoding encoding = ilist.pop.str.split bitindices.pop if encoding.length < bitindices.length # previous line is the instruction text format ilist.pop if ilist.last.str =~ /\[POWER2? mnemonics?: (.*)\]/ text = [] text.unshift l while l = ilist.pop and l = l.str and (l =~ /,|\)$/ or text.empty?) ilist = [] make_instr(encoding, bitindices, text) elsif l.str.include? 'Special Registers Altered' if not $foundop puts ilist.map { |l_| "(#{l_.y}) #{l_.str}" } puts lines.map { |l_| "(#{l_.y}) #{l_.str}" } if ilist.empty? raise 'nofoundop' else $foundop = false end elsif l.str =~ /Extended:\s+Equivalent to:/ extended = true elsif extended if l.str.include? ',' and l.str =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(.*)/ and $opcodes.find { |op| op[0] == $3 } newop, newargs, exop, exargs = $1, $2, $3, $4 make_alias(newop, newargs.split(','), exop, exargs.split(',')) else extended = false end else ilist << l end } end
# File samples/dasm-plugins/patch_file.rb, line 48 def patch_instrs(addr, asmsrc) sc = Metasm::Shellcode.new(cpu, addr) # pfx needed for autorequire sc.assemble(asmsrc, cpu) sc.encoded.fixup! prog_binding # allow references to dasm labels in the shellcode raw = sc.encode_string if s = get_section_at(addr) and s[0].data.kind_of? VirtualFile s[0][s[0].ptr, raw.length] = raw elsif o = addr_to_fileoff(addr) # section too small, not loaded as a VirtFile backup_program_file File.open(@program.filename, 'rb+') { |fd| fd.pos = o fd.write raw } s[0][s[0].ptr, raw.length] = raw if s else return end b = split_block(addr) # clear what we had in the rewritten space raw.length.times { |rawoff| next if not di = di_at(addr+rawoff) di.block.list.each { |ldi| @decoded.delete ldi.address } } disassemble_fast(addr) if b if b and @decoded[addr] nb = @decoded[addr].block nb.from_normal = b.from_normal nb.from_subfuncret = b.from_subfuncret nb.from_indirect = b.from_indirect end true end
create a backup and reopen the backend VirtualFile RW
# File samples/dasm-plugins/patch_file.rb, line 25 def reopen_rw(addr=nil, edata=nil) if not edata sections.each { |k, v| reopen_rw(k, v) } return true end return if not File.writable?(@program.filename) backup_program_file if not edata.data.kind_of? VirtualFile # section too small, loaded as real String # force reopen as VFile (allow hexediting in gui) return if not off = addr_to_fileoff(addr) len = edata.data.length edata.data = VirtualFile.read(@program.filename, 'rb+').dup(off, len) else edata.data.fd.reopen @program.filename, 'rb+' end end
static resolution of indirections
# File metasm/disassemble.rb, line 1743 def resolve(expr) binding = Expression[expr].expr_indirections.inject(@old_prog_binding) { |binding_, ind| e = get_edata_at(resolve(ind.target)) return expr if not e binding_.merge ind => Expression[ e.decode_imm("u#{8*ind.len}".to_sym, @cpu.endianness) ] } Expression[expr].bind(binding).reduce end
dumps to stdout the path to find some targets ( array of objects to match with == )
# File misc/objscan.rb, line 22 def scan_for(targets, path='', done={}) done[object_id] = self if done.empty? if t = targets.find { |t_| self == t_ } puts "found #{t} at #{path}" end scan_iter { |v, p| case v when Integer, Symbol; next end p = path+p if done[v.object_id] puts "loop #{p} -> #{done[v.object_id]}" if $VERBOSE else done[v.object_id] = p v.scan_for(targets, p, done) end } end
# File misc/objscan.rb, line 10 def scan_iter case self when ::Array length.times { |i| yield self[i], "[#{i}]" } when ::Hash each { |k, v| yield v, "[#{k.inspect}]" ; yield k, "(key)" } else instance_variables.each { |i| yield instance_variable_get(i), ".#{i[1..-1]}" } end end
metasm dasm plugin: scan the memory for a 'ret' which could indicate the beginning of the current function (x86 only)
# File samples/dasm-plugins/scanfuncstart.rb, line 9 def scanfuncstart(addr) if o = (1..16).find { |off| @decoded[addr-off].kind_of? DecodedInstruction } and @decoded[addr-o].bin_length == o addr -= o end if @decoded[addr].kind_of? DecodedInstruction fs = find_function_start(addr) return fs if fs != addr end edata = get_edata_at(addr) if o = (1..1000).find { |off| @decoded[addr-off-1] or edata.data[edata.ptr-off-1] == \xcc or edata.data[edata.ptr-off-1] == \xc3 or edata.data[edata.ptr-off-3] == \xc2 } o -= @decoded[addr-o-1].bin_length-1 if @decoded[addr-o-1].kind_of? DecodedInstruction addr-o end end
metasm dasm plugin: scan for xrefs to the target address, incl. relative offsets (eg near call/jmp)
# File samples/dasm-plugins/scanxrefs.rb, line 8 def scanxrefs(target) ans = [] msk = (1 << cpu.size) - 1 sections.sort.each { |s_addr, edata| raw = edata.data.to_str (0..raw.length-4).each { |off| r = raw[off, 4].unpack('V').first ans << (s_addr + off) if (r + off+4 + s_addr) & msk == target or r == target } } ans end
prints the string in 80 cols with the first column filled with
pfx
# File misc/hexdiff.rb, line 19 def show(pfx, str) loop do if str.length > 79 len = 79 - str[0...79][/\S+$/].to_s.length len = 79 if len == 0 puts pfx + str[0...len] str = str[len..-1] else puts pfx + str break end end end
# File samples/dasm-plugins/cppobj_funcall.rb, line 14 def solve_indirect_call_set_struct(ptr, struct) struct = @c_parser.toplevel.struct[struct] if struct.kind_of? String raise 'no such struct' if not struct @indirect_call_struct[ptr] = struct end
# File samples/dasm-plugins/cppobj_funcall.rb, line 20 def solve_indirect_calls @decoded.values.grep(DecodedInstruction).each { |di| next if not di.opcode.props[:saveip] # only calls fptr = get_xrefs_x(di) next if fptr.to_a.length != 1 fptr = Expression[fptr.first].reduce_rec next if not fptr.kind_of? Indirection next if not fptr.pointer.lexpr.kind_of? Symbol next if not fptr.pointer.rexpr.kind_of? Integer obj = backtrace(fptr.pointer.lexpr, di.address) obj.delete Expression::Unknown next if obj.length != 1 obj = obj.first obj = Expression[obj].reduce_rec next if not obj.kind_of? Indirection obj = obj.pointer # vtable ptr -> object ptr if not struct = @indirect_call_struct[obj] struct = yield obj if block_given? solve_indirect_call_set_struct(obj, struct || :none) end if struct.kind_of? C::Struct and fld = struct.members.find { |m| struct.offsetof(c_parser, m) == fptr.pointer.rexpr } and fld.name di.add_comment "#{struct.name || obj}->#{fld.name}" di.comment.delete 'x:unknown' end } end
metasm dasm plugin walks all disassembled instructions referencing an address if this address points a C string, show that in the instruction comments esp. useful after a disassemble_fast
# File samples/dasm-plugins/stringsxrefs.rb, line 12 def stringsxrefs(maxsz = 32) @decoded.each_value { |di| next if not di.kind_of?(DecodedInstruction) di.instruction.args.grep(Expression).each { |e| if str = decode_strz(e) and str.length >= 4 and str =~ /^[\x20-\x7e]*$/ di.add_comment str[0, maxsz].inspect add_xref(normalize(e), Xref.new(:r, di.address, 1)) end } } nil end
# File tests/graph_layout.rb, line 34 def test_all test_layout <<EOS line -> 2 -> 3 -> 4 -> 5 -> 6 -> 7; EOS test_layout <<EOS sep1 -> 1; sep2 -> 2; sep3 -> 3; sep4 -> 4; sep5 -> 5; EOS test_layout <<EOS fork -> 2 -> 3; 2 -> 4; EOS test_layout <<EOS diamond -> 2 -> 3 -> 5; 2 -> 4 -> 5; EOS test_layout <<EOS ifthen -> 2 -> 3 -> 4; 2 -> 4; EOS test_layout <<EOS ufork -> 2 -> 3; 1 -> 2; EOS test_layout <<EOS multidiamond -> 2 -> 31 -> 32 -> 34 -> 5 -> 6 -> 8; 2 -> 41 -> 42 -> 44 -> 5 -> 7 -> 8; 41 -> 43 -> 44; 31 -> 33 -> 34; EOS test_layout <<EOS dmdout -> 2 -> 3a -> 4; 2 -> 3b -> 4; 3a -> 4a; 3b -> 4b; EOS test_layout <<EOS ifthenthen -> 2 -> 8; 2 -> 3 -> 8; 2 -> 4 -> 5 -> 8; 2 -> 6 -> 7 -> 8; EOS test_layout <<EOS multipod -> 2 -> 3; 2 -> 4; 2 -> 5; 2 -> 6; 2 -> 7; 2 -> 8; EOS test_layout <<EOS mplarge -> 1 -> 2; 1 -> 3333333333333333333333333333333333; EOS test_layout <<EOS multif -> 1 1 -> a2 -> a3 a2 -> a222222222 -> a3 1 -> b2 -> b3 b2 -> b222222222 -> b3 EOS test_layout <<EOS ifx -> 1 -> 2 -> 3 -> 4 -> 5 4 -> eeeeeeeeeeee -> 5 EOS test_layout <<EOS llll -> 1 -> 22222222222222222222222222 -> e 1 -> 33333333333333333333333333 -> e 1 -> 4444444444444444444444 -> e 1 -> 5 -> e 5 -> 5t -> e 1 -> 6 -> e 6 -> 6t -> e 1 -> 7 -> e 7 -> 7t -> e EOS test_layout <<EOS dangling -> 2 -> 11 -> 12 -> 13 -> 4; 2 -> 21 -> 22 -> 23 -> 4; 2 -> 31 -> 32 -> 33 -> 4; 21 -> 2z; 31 -> 3z; EOS test_layout <<EOS dangin -> 2 -> 11 -> 12 -> 13; 2 -> 21 -> 22 -> 13; 2 -> 31 -> 32 -> 33; 22 -> 33; 21 -> z; EOS test_layout <<EOS cascadeclean -> 2 -> 3 -> 4 -> 5 -> 6 -> 62 -> 52 -> 42 -> 32 -> 22 -> e; 2 -> 21 -> 22; 3 -> 31 -> 32; 4 -> 41 -> 42; 5 -> 51 -> 52; 6 -> 61 -> 62; EOS test_layout <<EOS cascade -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> e; 2 -> 21 -> e; 3 -> 31 -> e; 4 -> 41 -> e; 5 -> 51 -> e; 6 -> 61 -> e; EOS test_layout <<EOS rstair -> 2 -> 3 -> 4 -> 5 -> 6; 2 -> 4; 2 -> 5; 2 -> 6; EOS test_layout <<EOS drstair -> 2a -> 3 -> 4 -> 5 -> 6; drstair -> 2b -> 4; 2a -> 4; 2a -> 5; 2a -> 6; 2b -> 4; 2b -> 5; 2b -> 6; EOS test_layout <<EOS mrstair -> 2a -> 3a -> 4a -> 5a -> 6a; mrstair -> 2b -> 4a; 2a -> 4a; 2a -> 5a; 2a -> 6a; 2b -> 4a; 2b -> 5a; 2b -> 6a; 2a -> 3b -> 4b -> 5b -> 6b; 2a -> 4b; 2a -> 5b; 2a -> 6b; 2b -> 3b; 2b -> 4b; 2b -> 5b; 2b -> 6b; EOS test_layout <<EOS loop -> 2 -> 3 -> 4; 3 -> 2; EOS test_layout <<EOS loopbreak -> 2 -> 3 -> e; 2 -> 4 -> 5 -> 6 -> 8 -> e; 5 -> 7 -> 4; EOS test_layout <<EOS loopbreak2 -> 2 -> 3 -> e; 2 -> 4 -> 5 -> 6 -> 8 -> e; 5 -> 7 -> 4; 7 -> 8; EOS test_layout <<EOS unbalance -> 2 -> 3 -> 4 -> 5 -> e; 2 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> e; EOS test_layout <<EOS unbalance2 -> 2 -> 3 -> e; 2 -> 4 -> e; 2 -> 5 -> e; 2 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> e; EOS test_layout <<EOS unbalance3 -> 2 -> 3 -> e; 2 -> 4 -> e; 2 -> 5 -> e; 2 -> 6 -> e; 8 -> 9 -> e; 2 -> 7 -> e; EOS test_layout <<EOS disjoint -> 1 -> 2 -> 3 -> 4 -> 5 -> 6; l1 -> l2; l1 -> l3; l1 -> l4; l1 -> l5; l1 -> l6; EOS test_layout <<EOS lol -> 2 -> 31 -> 41 -> 5; 2 -> 32 -> 42 -> 5; 31 -> 42; 41 -> 32; EOS test_layout <<EOS nestloop -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> e; 6 -> 4; 7 -> 3; EOS test_layout <<EOS escape -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8; 2 -> 21; 4 -> 6; EOS test_layout <<EOS loophead -> 1 -> loophead; 2 -> 3 -> 2; 3 -> 4; 1 -> 4; EOS test_layout <<EOS 1 -> e1; l00pz -> 1 -> l00pz; l2 -> 2 -> l2; 2 -> e1; 2 -> e2; l3 -> 3 -> l3; 3 -> e2; EOS test_layout <<EOS 3loop -> 1 -> 3loop; 1 -> 2 -> 3 -> 2; 0 -> 00 -> 0 -> 2; EOS test_layout <<EOS foo -> 0 -> 1 0 -> 2 -> 3 -> 4 -> 5 4 -> 6 4 -> 7 -> 5 4 -> 8 -> 6 2 -> 1 -> 7 3 -> 1 -> 8 EOS test_layout <<EOS dang -> 2 -> 3 -> 4 -> 5 -> 6 -> 4; 2 -> 9; 5 -> 9; EOS test_layout <<EOS dang2 -> 2 -> 3 -> 4 -> 5 -> 6 -> 4 2 -> 9 5 -> 9 9 -> a -> 9 EOS test_layout <<EOS onlyloop -> onlyloop EOS rescue Interrupt end
# File tests/graph_layout.rb, line 13 def test_layout(lo) $cur ||= 0 $cur += 1 if $target.to_i != 0 return if $cur != $target else return if not lo.include? $target end if $target puts $cur, lo, '' if $VERBOSE w = Gui::Window.new ww = w.widget = Gui::GraphViewWidget.new(nil, nil) ww.grab_focus Gui.idle_add { ww.load_dot(lo) ww.curcontext.auto_arrange_boxes ww.zoom_all false } Gui.main end
# File metasm/disassemble.rb, line 2011 def to_s a = '' dump { |l| a << l << "\n" } a end
start tracing now, and stop only when @trace_terminate is set
# File samples/dbg-plugins/trace_func.rb, line 31 def trace @trace_subfuncs = true @trace_terminate = false id = [pc, 0, 0] trace_func_newtrace(id) trace_func_block(id) continue end
setup the initial breakpoint at func start
# File samples/dbg-plugins/trace_func.rb, line 13 def trace_func(addr, oneshot = false) @trace_terminate = false # distinguish different hits on the same function entry counter = 0 bp = bpx(addr, oneshot) { counter += 1 id = [disassembler.normalize(addr), counter, func_retaddr] trace_func_newtrace(id) trace_func_block(id) continue } if addr == pc del_bp bp if oneshot bp.action.call end end
a new block is added to a trace
# File samples/dbg-plugins/trace_func.rb, line 151 def trace_func_add_block(id, blockaddr) @trace_func_counter[id] += 1 if di = disassembler.di_at(blockaddr) di.add_comment "functrace #{@trace_func_counter[id]}" end end
we hit the beginning of a block we want to trace
# File samples/dbg-plugins/trace_func.rb, line 41 def trace_func_block(id) blockaddr = pc if b = trace_get_block(blockaddr) trace_func_add_block(id, blockaddr) if b.list.length == 1 trace_func_blockend(id, blockaddr) else bpx(b.list.last.address, true) { finished = trace_func_blockend(id, blockaddr) continue if not finished } end else # invalid opcode ? trace_func_blockend(id, blockaddr) end end
we are at the end of a traced block, find whats next
# File samples/dbg-plugins/trace_func.rb, line 60 def trace_func_blockend(id, blockaddr) if di = disassembler.di_at(pc) if end_stepout(di) and trace_func_istraceend(id, di) # trace ends there trace_func_finish(id) return true elsif di.opcode.props[:saveip] and not trace_func_entersubfunc(id, di) # call to a subfunction bpx(di.next_addr, true) { trace_func_block(id) continue } continue else singlestep { newaddr = pc trace_func_block(id) trace_func_linkdasm(di.address, newaddr) continue } end else # XXX should link in the dasm somehow singlestep { trace_func_block(id) continue } end false end
the tracer is on a subfunction call instruction, should it trace into or stepover ?
# File samples/dbg-plugins/trace_func.rb, line 182 def trace_func_entersubfunc(id, di) if trace_subfuncs @trace_func_subfunccache ||= {} if not target = @trace_func_subfunccache[di.address] # even if the target is dynamic, its module should be static if target = disassembler.get_xrefs_x(di)[0] @trace_func_subfunccache[di.address] = target = resolve(disassembler.normalize(target)) end end # check if the target subfunction is in the same module as the main # XXX should check against the list of loaded modules etc # XXX call thunk_foo -> jmp [other_module] true if target.kind_of? Integer and target & 0xffc0_0000 == id[0] & 0xffc0_0000 else false end end
the trace is finished
# File samples/dbg-plugins/trace_func.rb, line 159 def trace_func_finish(id) puts "finished tracing #{Expression[id[0]]}" end
the tracer is on a end-of-func instruction, should the trace end ?
# File samples/dbg-plugins/trace_func.rb, line 167 def trace_func_istraceend(id, di) if trace_subfuncs if @trace_terminate true elsif id[2] == 0 # trace VS func_trace elsif target = disassembler.get_xrefs_x(di)[0] # check the current return address against the one saved at trace start resolve(disassembler.normalize(target)) == id[2] end else true end end
update the blocks links in the disassembler
# File samples/dbg-plugins/trace_func.rb, line 102 def trace_func_linkdasm(from_addr, new_addr) di = disassembler.di_at(from_addr) ndi = disassembler.di_at(new_addr) return if not di # is it a subfunction return ? if end_stepout(di) and cdi = (1..8).map { |i| disassembler.di_at(new_addr - i) }.compact.find { |cdi_| cdi_.opcode.props[:saveip] and cdi_.next_addr == new_addr } cdi.block.add_to_subfuncret new_addr ndi.block.add_from_subfuncret cdi.address if ndi cdi.block.each_to_normal { |f| disassembler.function[f] ||= DecodedFunction.new if disassembler.di_at(f) } else di.block.add_to_normal new_addr ndi.block.add_from_normal from_addr if ndi end end
a new trace is about to begin
# File samples/dbg-plugins/trace_func.rb, line 129 def trace_func_newtrace(id) @trace_func_counter ||= {} @trace_func_counter[id] = 0 puts "start tracing #{Expression[id[0]]}" # setup a bg_color_callback on the disassembler if not defined? @trace_func_dasmcolor @trace_func_dasmcolor = true return if not disassembler.gui oldcb = disassembler.gui.bg_color_callback disassembler.gui.bg_color_callback = lambda { |addr| if oldcb and c = oldcb[addr] c elsif di = disassembler.di_at(addr) and di.block.list.first.comment.to_s =~ /functrace/ 'ff0' end } end end
retrieve an instructionblock, disassemble if needed
# File samples/dbg-plugins/trace_func.rb, line 93 def trace_get_block(addr) # TODO trace all blocks from addr for which we know the target, stop on call / jmp [foo] disassembler.disassemble_fast_block(addr) if di = disassembler.di_at(addr) di.block end end
# File samples/dbg-plugins/trace_func.rb, line 164 def trace_subfuncs; @trace_subfuncs ||= false end
# File samples/dbg-plugins/trace_func.rb, line 163 def trace_subfuncs=(v) @trace_subfuncs = v end
# File samples/r0trace.rb, line 256 def unloadmod(mod=$drv) sh = DynLdr.openscmanagera(0, 0, DynLdr::SC_MANAGER_ALL_ACCESS) raise "cannot openscm" if (sh == 0) rh = DynLdr.openservicea(sh, mod, DynLdr::SERVICE_ALL_ACCESS) DynLdr.controlservice(rh, DynLdr::SERVICE_CONTROL_STOP, 0.chr*4*32) DynLdr.deleteservice(rh) DynLdr.CloseServiceHandle(rh) DynLdr.CloseServiceHandle(sh) end