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