2014-02-19 19:13:08 +00:00
# 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'
2014-02-19 20:01:20 +00:00
'compiled_heapscan_lin'
2014-02-19 19:13:08 +00:00
when 'winos'
2014-02-19 20:01:20 +00:00
case OS . current . version [ 0 ]
when 5 ; 'compiled_heapscan_win'
when 6 ; 'compiled_heapscan_win7'
end
2014-02-19 19:13:08 +00:00
end
fname = File . join ( $heapscan_dir , fname )
if not File . exist? ( fname + '.so' ) and File . exist? ( fname + '.c' )
2014-02-19 20:01:20 +00:00
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 )
2014-02-19 19:13:08 +00:00
end
require fname if File . exist? ( fname + '.so' )
def heapscan_time ( s = '' )
2014-02-19 20:01:20 +00:00
@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
2014-02-19 19:13:08 +00:00
end
def heap ; @heap ; end
def heap = ( h ) ; @heap = h ; end
def heapscan_scan ( xr = true )
2014-02-19 20:01:20 +00:00
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
}
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
hsz = 0
( heaps + mmaps ) . each { | a , l |
hsz += l
@heap . range . update a = > l
}
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
log " #{ hsz / 1024 / 1024 } M heap "
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
@heap . scan_chunks
heapscan_time " #{ @heap . chunks . length } chunks "
return if not xr
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
@heap . scan_chunks_xr
heapscan_time " #{ @heap . xrchunksto . length } src, #{ @heap . xrchunksfrom . length } dst "
2014-02-19 19:13:08 +00:00
end
def heapscan_structs
2014-02-19 20:01:20 +00:00
heapscan_time
@heap . bucketize
heapscan_time " #{ @heap . buckets . length } buckets "
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
@heap . find_arrays
heapscan_time " #{ @heap . allarrays . length } arrays ( #{ @heap . allarrays . flatten . length } elems) "
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
@heap . find_linkedlists
heapscan_time " #{ @heap . alllists . length } lists ( #{ @heap . alllists . flatten . length } elems) "
2014-02-19 19:13:08 +00:00
end
def heapscan_kernels
2014-02-19 20:01:20 +00:00
heapscan_time
@heap . find_kernels
heapscan_time " #{ @heap . kernels . length } kernels "
2014-02-19 19:13:08 +00:00
end
def heapscan_roots
2014-02-19 20:01:20 +00:00
heapscan_time
@heap . find_roots
heapscan_time " #{ @heap . roots . length } roots "
2014-02-19 19:13:08 +00:00
end
def heapscan_graph
2014-02-19 20:01:20 +00:00
heapscan_time
@heap . dump_graph
heapscan_time 'graph.gv'
2014-02-19 19:13:08 +00:00
end
def gui_show_list ( addr )
2014-02-19 20:01:20 +00:00
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 ]
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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 }
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
$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 )
2014-02-19 19:13:08 +00:00
end
def gui_show_array ( addr )
2014-02-19 20:01:20 +00:00
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 ]
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
$ghw . addr_struct = { head = > @heap . cp . decode_c_struct ( st . name , @memory , head ) }
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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 }
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
lst . each { | aa |
$ghw . addr_struct [ aa ] = @heap . cp . decode_c_struct ( st . name , @memory , aa )
}
gui . parent_widget . mem . focus_addr ( head , :graphheap )
2014-02-19 19:13:08 +00:00
end
if gui
2014-02-19 20:01:20 +00:00
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 )
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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 ] } "
}
}
2014-02-19 19:13:08 +00:00
2014-02-19 20:01:20 +00:00
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
}
2014-02-19 19:13:08 +00:00
end