class Object

searches an object in the attributes of another anyobj.scan_for() => “anyobj.someattr['blabla']”

Constants

TRACE_BUF_SZ

size of the eip buffer (in dwords)

Public Class Methods

diff(o1, o2) click to toggle source

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

Public Instance Methods

_printadv(a) click to toggle source
# File samples/scan_pt_gnu_stack.rb, line 18
def _printadv(a)
        $stderr.print a.to_s.ljust(60)[-60, 60] + "\r"
end
_puts(a) click to toggle source
# File samples/scan_pt_gnu_stack.rb, line 15
def _puts(a)
        puts a.to_s.ljust(60)
end
addrtolabel() click to toggle source

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
asm() click to toggle source

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.asm_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
backup_program_file() click to toggle source

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
bookmark_addrs(list, color) click to toggle source
# 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
bookmark_delete(list) click to toggle source
# File samples/dasm-plugins/bookmark.rb, line 109
def bookmark_delete(list)
        @bookmarklist -= list
        list.each { |a| @bookmarkcolor.delete a }
end
bookmark_delete_color(col) click to toggle source
# 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
bookmark_delete_function(addr) click to toggle source
# 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
bookmark_function(addr, color) click to toggle source

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
bouncepkt(clt, srv, timeout=nil) click to toggle source
# 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
compile_warn(tg) click to toggle source
# 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
create_sig(exe) click to toggle source

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
create_sig_arch(exe) click to toggle source

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
create_sig_coff(coff) { |name, data| ... } click to toggle source

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
create_sig_elf(elf) { |name, data| ... } click to toggle source

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
dasm_all(addrstart, length, method=:disassemble_fast_deep) click to toggle source

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
dasm_all_section(name, method=:disassemble_fast_deep) click to toggle source
# 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
demangle_all_cppnames() click to toggle source

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
epilog() click to toggle source
# 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
findgadget_asm(asm) click to toggle source

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
findgadget_asm_to_regex(asm) click to toggle source

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
findgadget_prompt() click to toggle source
# 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
gets() click to toggle source

shows the preprocessor path to find a specific line usage: ruby chdr-find.rb 'regex pattern' list of files.h

Calls superclass method
# File misc/cheader-findpppath.rb, line 11
def gets
        l = $ungets
        $ungets = nil
        l || super()
end
graph_to_svg() click to toggle source
# 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('<', '&lt;').gsub('>', '&gt;')
                        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
gui_show_array(addr) click to toggle source
# 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
gui_show_list(addr) click to toggle source
# 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
hash_name(sym) click to toggle source
# 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
heap() click to toggle source
# File samples/dbg-plugins/heapscan.rb, line 49
def heap; @heap ; end
heap=(h) click to toggle source
# File samples/dbg-plugins/heapscan.rb, line 50
def heap=(h) ; @heap = h ; end
heapscan_graph() click to toggle source
# File samples/dbg-plugins/heapscan.rb, line 129
def heapscan_graph
        heapscan_time
        @heap.dump_graph
        heapscan_time 'graph.gv'
end
heapscan_kernels() click to toggle source
# File samples/dbg-plugins/heapscan.rb, line 117
def heapscan_kernels
        heapscan_time
        @heap.find_kernels
        heapscan_time "#{@heap.kernels.length} kernels"
end
heapscan_roots() click to toggle source
# File samples/dbg-plugins/heapscan.rb, line 123
def heapscan_roots
        heapscan_time
        @heap.find_roots
        heapscan_time "#{@heap.roots.length} roots"
end
heapscan_scan(xr=true) click to toggle source
# 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/i
                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
heapscan_structs() click to toggle source
# 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
heapscan_time(s='') click to toggle source
# 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
imm_to_const(addr) { |i| ... } click to toggle source

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
imm_to_const_decompose(imm, cstbase) click to toggle source

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}/i 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
lib_name(sym) click to toggle source
# 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
lint(tg) click to toggle source

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
lint_file(tg) click to toggle source
# 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
loadmod(mod=$drv) click to toggle source
# 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
log_caller(cls, meth, singleton=false, histlen=nil) click to toggle source

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
make_alias(newop, newargs, oldop, oldargs) click to toggle source

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]+$/i
                        fld = op[2][i]
                        op2[1] |= Integer(oa) << $field_shift[fld]
                end
        }
        puts "#\talias #{newop} #{newargs.join(', ')}  ->  #{oldop} #{oldargs.join(', ')}".downcase
end
make_instr(bins, bits, text) click to toggle source
# 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
match_libsigs(sigfile) click to toggle source
# 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
parse(root=false) click to toggle source
# 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
parse_page(lines) click to toggle source
# 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
patch_instrs(addr, asmsrc) click to toggle source
# 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
puts_trace(str) click to toggle source
# File samples/emubios.rb, line 46
def puts_trace(str)
        trace str
        puts str
end
read_sector(addr, fileoff, len) click to toggle source

read one sector from the drive to memory, invalidate already disassembled instruction from the address range

# File samples/emubios.rb, line 22
def read_sector(addr, fileoff, len)
        e, o = $dasm.get_section_at(addr)
        $dasm.decoded.keys.grep(addr..(addr+len)).each { |k| $dasm.decoded.delete k }
        raw_chunk = File.open($rawname, 'rb') { |fd| fd.pos = fileoff ; fd.read len } || ''
        raw_chunk << 0.chr until raw_chunk.length >= len
        e[e.ptr, len] = raw_chunk
        $dbg.invalidate if $dbg
end
reopen_rw(addr=nil, edata=nil) click to toggle source

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
scan_for(targets, path='', done={}) click to toggle source

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
scan_iter() { |self, "[#{i}]"| ... } click to toggle source
# 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
scanfuncstart(addr) click to toggle source

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
scanxrefs(target) click to toggle source

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 = []
        csz = cpu.size
        msk = (1 << csz) - 1
        upq = (csz == 64 ? 'q' : 'V')
        sections.sort.each { |s_addr, edata|
                raw = edata.data.to_str
                (0..raw.length-csz/8).each { |off|
                        r = raw[off, csz/8].unpack(upq).first
                        ans << (s_addr + off) if (r + off+csz/8 + s_addr) & msk == target or r == target
                }
        }
        ans
end
show(pfx, str) click to toggle source

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
solve_indirect_call_set_struct(ptr, struct) click to toggle source
# 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
solve_indirect_calls() { |obj| ... } click to toggle source
# 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
stringsxrefs(maxsz = 32) click to toggle source

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
test_all() click to toggle source
# 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
test_layout(lo) click to toggle source
# File tests/graph_layout.rb, line 13
def test_layout(lo)
        $cur ||= 0
        $cur += 1
        if $target.to_i != 0
                return if $cur != $target.to_i
        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
trace() click to toggle source

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
trace_func(addr, oneshot = false) click to toggle source

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
trace_func_add_block(id, blockaddr) click to toggle source

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
trace_func_block(id) click to toggle source

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
trace_func_blockend(id, blockaddr) click to toggle source

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
trace_func_entersubfunc(id, di) click to toggle source

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
trace_func_finish(id) click to toggle source

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
trace_func_istraceend(id, di) click to toggle source

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
trace_func_linkdasm(from_addr, new_addr) click to toggle source

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
trace_func_newtrace(id) click to toggle source

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
trace_get_block(addr) click to toggle source

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(addr)
        if di = disassembler.di_at(addr)
                di.block
        end
end
trace_subfuncs() click to toggle source
# File samples/dbg-plugins/trace_func.rb, line 164
def trace_subfuncs; @trace_subfuncs ||= false end
trace_subfuncs=(v) click to toggle source
# File samples/dbg-plugins/trace_func.rb, line 163
def trace_subfuncs=(v) @trace_subfuncs = v end
unloadmod(mod=$drv) click to toggle source
# 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