# This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # # Licence is LGPL, see LICENCE in the top-level directory # metasm debugger plugin # adds some heap_* functions to interract with the target heap chunks # functions: # heap_scan, scan for malloc chunks in the heaps and xrefs between them # heap_scanstruct, scan for arrays/linkedlists in the chunk graph # heap_chunk [addr], display a chunk # heap_array [addr], display an array of chunks from their root # heap_list [addr], display a linkedlist # heap_strscan [str], scan the memory for a raw string, display chunks xrefs # heap_snap, make a snapshot of the currently displayed structure, hilight fields change # use precompiled native version when available $heapscan_dir = File.join(File.dirname(plugin_filename).gsub('\\', '/'), 'heapscan') require File.join($heapscan_dir, 'heapscan') fname = case OS.current.shortname when 'linos' 'compiled_heapscan_lin' when 'winos' case OS.current.version[0] when 5; 'compiled_heapscan_win' when 6; 'compiled_heapscan_win7' end end fname = File.join($heapscan_dir, fname) if not File.exist?(fname + '.so') and File.exist?(fname + '.c') puts "compiling native scanner..." exe = DynLdr.host_exe.compile_c_file(DynLdr.host_cpu, fname + '.c') DynLdr.compile_binary_module_hack(exe) exe.encode_file(fname + '.so', :lib) end require fname if File.exist?(fname + '.so') 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 def heap; @heap ; end def heap=(h) ; @heap = h ; end 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 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 def heapscan_kernels heapscan_time @heap.find_kernels heapscan_time "#{@heap.kernels.length} kernels" end def heapscan_roots heapscan_time @heap.find_roots heapscan_time "#{@heap.roots.length} roots" end def heapscan_graph heapscan_time @heap.dump_graph heapscan_time 'graph.gv' end 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 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 if gui require File.join($heapscan_dir, 'graphheap') $ghw = Metasm::Gui::GraphHeapWidget.new(@disassembler, gui.parent_widget.mem) gui.parent_widget.mem.addview :graphheap, $ghw $ghw.show if $ghw.respond_to?(:show) gui.new_command('heap_scan', 'scan the heap(s)') { |*a| heapscan_scan ; $ghw.heap = @heap } gui.new_command('heap_scan_noxr', 'scan the heap(s), no xrefs') { |*a| heapscan_scan(false) ; $ghw.heap = @heap } gui.new_command('heap_scan_xronly', 'scan the heap(s) for xrefs') { |*a| $ghw.heap.scan_chunks_xr } gui.new_command('heap_scanstructs', 'scan the heap for arrays/lists') { |*a| heapscan_structs } gui.new_command('heap_list', 'show a linked list') { |a| if a.to_s != '' gui_show_list(a) else l = [['addr', 'len']] @heap.alllists.each { |al| l << [Expression[al.first], al.length] } gui.listwindow('lists', l) { |*aa| gui_show_list(aa[0][0]) } end } gui.new_command('heap_array', 'show an array') { |a| if a.to_s != '' gui_show_array(a) else l = [['addr', 'len']] @heap.allarrays.each { |al| l << [Expression[al.first], al.length] } gui.listwindow('arrays', l) { |*aa| gui_show_array(aa[0][0]) } end } gui.new_command('heap_chunk', 'show a chunk') { |a| a = resolve(a) gui.parent_widget.mem.focus_addr(a, :graphheap) $ghw.do_focus_addr(a) } gui.new_command('heap_strscan', 'scan a string') { |a| sa = pattern_scan(a) log "found #{sa.length} strings : #{sa.map { |aa| Expression[aa] }.join(' ')}" sa.each { |aa| next if not ck = @heap.find_chunk(aa) log "ptr #{Expression[aa]} in chunk #{Expression[ck]} (#{Expression[@heap.chunks[ck]]}) in list #{@heap.linkedlists && @heap.linkedlists[ck] && true} in array #{@heap.arrays[ck].map { |k, v| "#{Expression[k]} (#{v.length})" }.join(', ') if @heap.arrays and @heap.arrays[ck]}" } } gui.new_command('heap_ptrscan', 'scan a pointer') { |a| a = resolve(a) if @heap.chunks[a] pa = @heap.xrchunksfrom[a].to_a else pa = pattern_scan(Expression.encode_imm(a, @cpu.size/8, @cpu.endianness)) end log "found #{pa.length} pointers : #{pa.map { |aa| Expression[aa] }.join(' ')}" pa.each { |aa| next if not ck = @heap.find_chunk(aa) log "ptr @#{Expression[aa]} in chunk #{Expression[ck]} (#{Expression[@heap.chunks[ck]]}) in list #{@heap.linkedlists && @heap.linkedlists[ck] && true} in array #{@heap.arrays[ck].map { |k, v| "#{Expression[k]} (#{v.length})" }.join(', ') if @heap.arrays and @heap.arrays[ck]}" } } gui.new_command('heap_snap', 'snapshot the current heap struct') { |a| $ghw.snap } gui.new_command('heap_snap_add', 'snapshot, ignore fields changed between now and last snap') { |a| $ghw.snap_add } end