class Metasm::LinuxHeap
Attributes
main_arena_ptr[RW]
mmaps[RW]
find all chunks in the memory address space
Public Instance Methods
chunkdata(ptr)
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 405 def chunkdata(ptr) @cp.decode_c_ary('uintptr_t', 2, @dbg.memory, ptr).to_array end
del_fastbin(ar)
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 473 def del_fastbin(ar) nfastbins = 10 nfastbins.times { |i| ptr = ar.fastbinsy[i] while ptr @chunks.delete ptr+2*@ptsz ptr = @cp.decode_c_ary('void *', 3, @dbg.memory, ptr)[2] end } end
each_heap() { |top| ... }
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 409 def each_heap if not @cp.toplevel.struct['malloc_state'] @cp.parse <<EOS // TODO autotune these 2 defines.. #define THREAD_STATS 0 //#define PER_THREAD #define NFASTBINS 10 #define NBINS 128 #define BINMAPSIZE (NBINS/32) struct malloc_state { int mutex; int flags; #if THREAD_STATS long stat_lock_direct, stat_lock_loop, stat_lock_wait; #endif void *fastbinsY[NFASTBINS]; void *top; void *last_remainder; void *bins[NBINS * 2 - 2]; // *2: double-linked list unsigned int binmap[BINMAPSIZE]; struct malloc_state *next; #ifdef PER_THREAD struct malloc_state *next_free; #endif uintptr_t system_mem; // XXX int32? uintptr_t max_system_mem; }; struct heap_info { struct malloc_state *ar_ptr; // Arena for this heap. struct _heap_info *prev; // Previous heap. uintptr_t size; // Current size in bytes. XXX int32? uintptr_t mprotect_size; // Size in bytes that has been mprotected }; EOS end ptr = @main_arena_ptr loop do ar = @cp.decode_c_struct('malloc_state', @dbg.memory, ptr) if ptr == @main_arena_ptr # main arena: find start from top.end - system_mem toplen = chunkdata(ar.top)[1] & -8 yield ar.top + toplen - ar.system_mem, ar.system_mem, ar else # non-main arena: find heap_info for top, follow list iptr = ar.top & -0x10_0000 # XXX while iptr hi = @cp.decode_c_struct('heap_info', @dbg.memory, iptr) off = hi.sizeof off += ar.sizeof if iptr+off == hi.ar_ptr yield iptr+off, hi.size-off, ar iptr = hi.prev end end ptr = ar.next break if ptr == @main_arena_ptr end end
scan_chunks()
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 283 def scan_chunks @chunks = {} each_heap { |a, l, ar| scan_heap(a, l, ar) } @mmapchunks = [] @mmaps.each { |a, l| ll = scan_mmap(a, l) || 4096 a += ll l -= ll } end
scan_chunks_xr()
click to toggle source
scan all chunks for cross-references (one chunk contaning a pointer to some other chunk)
# File samples/dbg-plugins/heapscan/heapscan.rb, line 297 def scan_chunks_xr @xrchunksto = {} @xrchunksfrom = {} each_heap { |a, l, ar| scan_heap_xr(a, l) } @mmapchunks.each { |a| scan_mmap_xr(a, @chunks[a]) } end
scan_heap(base, len, ar)
click to toggle source
scan chunks from a heap base addr
# File samples/dbg-plugins/heapscan/heapscan.rb, line 309 def scan_heap(base, len, ar) dw = dwcache(base, len) ptr = 0 psz = dw[ptr] sz = dw[ptr+1] base += 2*@ptsz # user pointer raise "bad heap base %x %x %x %x" % [psz, sz, base, len] if psz != 0 or sz & 1 == 0 loop do clen = sz & -8 # chunk size ptr += clen/@ptsz # start of next chk break if ptr >= dw.length or clen == 0 sz = dw[ptr+1] if sz & 1 > 0 # pv_inuse # user data length up to chucksize-4 (over next psz) #puts "used #{'%x' % base} #{clen-@ptsz}" if $VERBOSE @chunks[base] = clen-@ptsz else #puts "free #{'%x' % base} #{clen-@ptsz}" if $VERBOSE end base += clen end del_fastbin(ar) end
scan_heap_xr(base, len)
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 336 def scan_heap_xr(base, len) dw = dwcache(base, len) @chunks.each_key { |p| i = (p-base) / @ptsz if i >= 0 and i < dw.length lst = dw[i, @chunks[p]/@ptsz].find_all { |pp| @chunks[pp] } @xrchunksto[p] = lst if not lst.empty? lst.each { |pp| (@xrchunksfrom[pp] ||= []) << p } end } end
scan_libc(addr)
click to toggle source
we need to find the main_arena from the libc we do this by analysing 'malloc_trim'
# File samples/dbg-plugins/heapscan/heapscan.rb, line 374 def scan_libc(addr) raise 'no libc' if not addr return if @main_arena_ptr = @dbg.symbols.index('main_arena') unless trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim') @dbg.loadsyms 'libc[.-]' trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim') end raise 'cant find malloc_trim' if not trim d = @dbg.disassembler d.disassemble_fast(trim) if not d.di_at(trim) if d.block_at(trim).list.last.opcode.name == 'call' # x86 getip, need to dasm to have func_binding (cross fingers) d.disassemble d.block_at(trim).to_normal.first end d.each_function_block(trim) { |b| # mutex_lock(&main_arena.mutex) gives us the addr next if not cmpxchg = d.block_at(b).list.find { |di| di.kind_of? DecodedInstruction and di.opcode.name == 'cmpxchg' } @main_arena_ptr = d.backtrace(cmpxchg.instruction.args.first.symbolic.pointer, cmpxchg.address) if @main_arena_ptr.length == 1 @main_arena_ptr = @main_arena_ptr[0].reduce break end } raise "cant find mainarena" if not @main_arena_ptr.kind_of? Integer @dbg.symbols[@main_arena_ptr] = 'main_arena' end
scan_mmap(base, len)
click to toggle source
scan chunks from a mmap base addr big chunks are allocated on anonymous-mmap areas for mmap chunks, pv_sz=0 pv_inuse=0, mmap=1, data starts at 8, mmapsz = userlen+12 [roundup 4096] one entry in /proc/pid/maps may point to multiple consecutive mmap chunks scans for a mmap chunk header, returns the chunk size if pattern match or nil
# File samples/dbg-plugins/heapscan/heapscan.rb, line 353 def scan_mmap(base, len) foo = chunkdata(base) clen = foo[1] & ~0xfff if foo[0] == 0 and foo[1] & 0xfff == 2 and clen > 0 and clen <= len @chunks[base + foo.length] = clen-4*@ptsz @mmapchunks << (base + foo.length) clen end end
scan_mmap_xr(base, len)
click to toggle source
# File samples/dbg-plugins/heapscan/heapscan.rb, line 363 def scan_mmap_xr(base, len) dw = dwcache(base, len) lst = dw[2..-1].find_all { |pp| @chunks[pp] } @xrchunksto[base] = lst if not lst.empty? lst.each { |pp| (@xrchunksfrom[pp] ||= []) << base } end