コード例 #1
0
    def get_tcache_bin_chunks(self, index):
        """Fetches the chunks' addresses in the tcache_perthread_struct.entries[] array
        for the specified index

        :return: the list of addresses in this specified bin
        """

        ptm = self.ptm
        tcache = ptm.cache.tcache
        dbg = self.ptm.dbg

        if tcache.entries[index] == 0:
            return []
        # I've seen uninitialized entries[] still holding old data i.e. non-null
        # even though the counts is 0
        if tcache.counts[index] == 0:
            return []

        addr = tcache.entries[index] - 2 * ptm.SIZE_SZ
        p = mc.malloc_chunk(ptm, addr, inuse=False, debugger=dbg, allow_invalid=True, tcache=True)
        if not p.initOK: # afaict should not happen in a normal scenario but better be safe
            return []

        addresses = []
        while True:
            addresses.append(p.address)
            if p.next == 0x0:
                break
            addr = p.next - 2 * ptm.SIZE_SZ
            p = mc.malloc_chunk(ptm, addr, inuse=False, debugger=dbg, allow_invalid=True, tcache=True)
            if not p.initOK: # same
                return addresses
        
        return addresses
コード例 #2
0
    def get_fast_bin_chunks(self, index):
        """Fetches the chunks' addresses in the malloc_state.fastbinsY[] array
        for the specified index

        :return: the list of addresses in this specified bin
        """

        ptm = self.ptm
        mstate = ptm.cache.mstate
        dbg = self.ptm.dbg

        fb_base = int(mstate.address) + mstate.fastbins_offset

        p = mc.malloc_chunk(
            ptm,
            addr=fb_base - (2 * ptm.SIZE_SZ) + index * ptm.SIZE_SZ,
            fast=True,
            debugger=dbg,
            allow_invalid=True,
        )

        addresses = []
        while p.fd != 0:
            if p.fd is None:
                break
            addresses.append(p.fd)
            p = mc.malloc_chunk(
                ptm, 
                p.fd, 
                fast=True,
                debugger=dbg,
                allow_invalid=True,
            )
        
        return addresses
コード例 #3
0
ファイル: ptlist.py プロジェクト: nccgroup/libptmalloc
    def compact_listing(self):
        """Print all the chunks in a given arena using a compact flat listing
        """

        max_count = self.args.count

        pu.print_title("{:>15} for arena @ {:#x}".format("compact flat heap listing", self.cache.mstate.address), end="\n")

        if self.ptm.SIZE_SZ == 4:
            # Workaround on 32-bit. Empirically it seems the first chunk starts at offset +0x8?
            addr = self.sbrk_base+8
        else:
            addr = self.sbrk_base

        count = 0
        while True:
            p = mc.malloc_chunk(
                self.ptm, 
                addr, 
                read_data=False, 
                debugger=self.dbg,
                use_cache=True
            )

            if p.address == self.ptm.top(self.cache.mstate):
                print("|T", end="")
                count += 1
                break

            if p.type == pt.chunk_type.FREE_FAST:
                print("|f%d" % self.ptm.fast_bin_index(self.ptm.chunksize(p)), end="")
            elif p.type == pt.chunk_type.FREE_TCACHE:
                print("|t%d" % self.ptm.tcache_bin_index(self.ptm.chunksize(p)), end="")
            elif p.type == pt.chunk_type.INUSE:
                print("|M", end="")
            else:
                if (
                    (p.fd == self.cache.mstate.last_remainder)
                    and (p.bk == self.cache.mstate.last_remainder)
                    and (self.cache.mstate.last_remainder != 0)
                ):
                    print("|L", end="")
                else:
                    print("|F%d" % self.ptm.bin_index(self.ptm.chunksize(p)), end="")
            count += 1
            sys.stdout.flush()

            if max_count != None and count == max_count:
                break

            addr = self.ptm.next_chunk(p)

        print("|")
        if max_count == None:
            print(f"Total of {count} chunks")
        else:
            print(f"Total of {count}+ chunks")
コード例 #4
0
    def get_bin_chunks(self, index):
        """Fetches the chunks' addresses in the malloc_state.bins[] array
        for the specified index

        :return: the list of addresses in this specified bin
        """

        log.debug("get_bin_chunks(%d)" % index)
        ptm = self.ptm
        mstate = ptm.cache.mstate
        dbg = self.ptm.dbg

        #ptm.mutex_lock(mstate)

        b = ptm.bin_at(mstate, index+1)
        if b == 0:      # Not initialized yet
            return []

        p = mc.malloc_chunk(
            ptm, 
            b, 
            inuse=False, 
            debugger=dbg,
            tcache=False,
            fast=False,
            allow_invalid=True)

        addresses = []
        while p.fd != int(b):
            addresses.append(p.address)
            p = mc.malloc_chunk(
                ptm, 
                ptm.first(p), 
                inuse=False, 
                debugger=dbg,
                tcache=False,
                fast=False,
                allow_invalid=True)

        #ptm.mutex_unlock(mstate)

        return addresses
コード例 #5
0
    def show_stats(self):
        """Show a summary of the memory statistics
        """

        self.cache.update_arena(show_status=False)
        self.cache.update_param(show_status=False)
        self.cache.update_tcache(show_status=False)
        self.cache.update_tcache_bins(show_status=False)

        main_arena_address = self.cache.main_arena_address
        par = self.cache.par

        in_use_b = par.mmapped_mem
        avail_b = 0
        system_b = in_use_b

        pu.print_title("Malloc Stats", end="\n\n")

        arena = 0
        mstate = ms.malloc_state(
            self.ptm, main_arena_address, debugger=self.dbg, version=self.version
        )
        while 1:

            self.cache.update_arena(address=mstate.address, show_status=False)
            self.cache.update_fast_bins(show_status=False)
            self.cache.update_bins(show_status=False)

            if mstate.address == self.cache.main_arena_address:
                sbrk_base, _ = self.dbg.get_heap_address(par)
            else:
                sbrk_base = (mstate.address + mstate.size + self.ptm.MALLOC_ALIGN_MASK) & ~self.ptm.MALLOC_ALIGN_MASK

            avail = 0
            inuse = 0
            nblocks = 1
            addr = sbrk_base
            while True:
                p = mc.malloc_chunk(
                    self.ptm, 
                    addr, 
                    read_data=False, 
                    debugger=self.dbg,
                    use_cache=True
                )

                if p.address == self.ptm.top(self.cache.mstate):
                    avail += self.ptm.chunksize(p)
                    break

                if p.type == pt.chunk_type.FREE_FAST:
                    avail += self.ptm.chunksize(p)
                elif p.type == pt.chunk_type.FREE_TCACHE:
                    avail += self.ptm.chunksize(p)
                elif p.type == pt.chunk_type.INUSE:
                    inuse += self.ptm.chunksize(p)
                else:
                    avail += self.ptm.chunksize(p)
                nblocks += 1

                addr = self.ptm.next_chunk(p)

            pu.print_header("Arena {} @ {:#x}:".format(arena, mstate.address), end="\n")
            print("{:16} = ".format("system bytes"), end="")
            pu.print_value("{} ({:#x})".format(mstate.max_system_mem, mstate.max_system_mem), end="\n")
            print("{:16} = ".format("free bytes"), end="")
            pu.print_value("{} ({:#x})".format(avail, avail), end="\n")
            print("{:16} = ".format("in use bytes"), end="")
            pu.print_value("{} ({:#x})".format(inuse, inuse), end="\n")

            system_b += mstate.max_system_mem
            avail_b += avail
            in_use_b += inuse

            if mstate.next == main_arena_address:
                break
            else:
                next_addr = self.dbg.format_address(mstate.next)
                mstate = ms.malloc_state(
                    self.ptm, next_addr, debugger=self.dbg, version=self.version
                )
                arena += 1

        pu.print_header("\nTotal (including mmap):", end="\n")
        print("{:16} = ".format("system bytes"), end="")
        pu.print_value("{} ({:#x})".format(system_b, system_b), end="\n")
        print("{:16} = ".format("free bytes"), end="")
        pu.print_value("{} ({:#x})".format(avail_b, avail_b), end="\n")
        print("{:16} = ".format("in use bytes"), end="")
        pu.print_value("{} ({:#x})".format(in_use_b, in_use_b), end="\n")

        if self.version <= 2.23:
            # catch the error before we print anything
            val = par.max_total_mem

            print("{:16} = ".format("max system bytes"), end="")
            pu.print_value("{}".format(val), end="\n")

        print("{:16} = ".format("max mmap regions"), end="")
        pu.print_value("{}".format(par.max_n_mmaps), end="\n")
        print("{:16} = ".format("max mmap bytes"), end="")
        pu.print_value("{}".format(par.max_mmapped_mem), end="\n")
コード例 #6
0
    def parse_many(address, ptm, dbg=None, count=1, count_handle=None, search_depth=0, 
        skip_header=False, hexdump_unit=1, search_value=None,
        search_type=None, match_only=False, print_offset=0, verbose=0, no_newline=False,
        debug=False, hexdump=False, maxbytes=0, metadata=None,
        highlight_types=[], highlight_addresses=[], highlight_metadata=[], highlight_only=False, 
        inuse=None, tcache=None, fast=None, allow_invalid=False,
        header_once=None, commands=None,
        use_cache=False, address_offset=False
    ):
        """Parse many chunks starting from a given address and show them based
        passed arguments

        :param address: chunk's address to start parsing from
        :param ptm: ptmalloc object (libptmalloc constants and helpers)
        :param dbg: pydbg object (debugger interface)
        :param count: see ptchunk's ArgumentParser definition
                      maximum number of chunks to print, or None if unlimited
        :param count_handle: maximum number of chunks to handle per address, even if not printed, or None if unlimited
        :param search_depth: see ptchunk's ArgumentParser definition
        :param skip_header: see ptchunk's ArgumentParser definition
        :param hexdump_unit: see ptchunk's ArgumentParser definition
        :param search_value: see ptchunk's ArgumentParser definition
        :param search_type: see ptchunk's ArgumentParser definition
        :param match_only: see ptchunk's ArgumentParser definition
        :param print_offset: see ptchunk's ArgumentParser definition
        :param verbose: see ptchunk's ArgumentParser definition
        :param no_newline: see ptchunk's ArgumentParser definition
        :param debug: see ptchunk's ArgumentParser definition
        :param hexdump: see ptchunk's ArgumentParser definition
        :param maxbytes: see ptchunk's ArgumentParser definition
        :param metadata: see ptchunk's ArgumentParser definition
        :param highlight_types: list of types. highlight chunks with matching type with a '*' e.g. to be used by 'ptlist'
        :param highlight_addresses: list of addresses. highlight chunks with matching address with a '*' e.g. to be used by 'ptlist'
        :param highlight_metadata: list of metadata. highlight chunks with matching metadata with a '*' e.g. to be used by 'ptlist'
        :param highlight_only: see ptchunk's ArgumentParser definition
        :param inuse: True if we know all the chunks are inuse (i.e. not in any bin)
                      False if we know they are NOT in inuse.
                      None otherwise.
                      Useful to specify when parsing a regular bin
        :param tcache: True if we know all the chunks are in the tcache bins,
                        False if we know they are NOT in the tcache bins. 
                        None otherwise.
                        Useful to specify when parsing a tcache bin
        :param fast: Same as "tcache" but for fast bins
        :param allow_invalid: sometimes these structures will be used for
                              that isn't actually a complete chunk, like a freebin, in these cases we
                              still wanted to be able to parse so that we can access the forward and
                              backward pointers, so shouldn't complain about their being invalid size
        :param header_once: string to print before printing the first chunk, or None if not needed
        :param commands: see ptchunk's ArgumentParser definition
        :param use_cache: see ptchunk's ArgumentParser definition
        :param address_offset: see ptchunk's ArgumentParser definition

        :return: the list of malloc_chunk being parsed and already shown
        """
        chunks = []

        highlight_types2 = []
        highlight_types = set(highlight_types)
        for t in highlight_types:
            if t == "M":
                highlight_types2.append(pt.chunk_type.INUSE)
            elif t == "F":
                highlight_types2.append(pt.chunk_type.FREE_SMALL)
                highlight_types2.append(pt.chunk_type.FREE_LARGE)
            elif t == "f":
                highlight_types2.append(pt.chunk_type.FREE_FAST)
            elif t == "t":
                highlight_types2.append(pt.chunk_type.FREE_TCACHE)
            else:
                print("ERROR: invalid chunk type provided, should not happen")
                return []
        highlight_addresses = set(highlight_addresses)
        highlight_metadata = set(highlight_metadata)
        highlight_metadata_found = set([])


        p = mc.malloc_chunk(
            ptm, 
            addr=address, 
            debugger=dbg, 
            use_cache=use_cache,
            tcache=tcache,
            fast=fast,
            allow_invalid=allow_invalid
        )
        if not p.initOK:
            return
        first_address = p.address
        dump_offset = 0
        while True:
            prefix = "" # used for one-line output
            suffix = "" # used for one-line output
            epilog = "" # used for verbose output

            colorize_func = str # do not colorize by default
            if metadata is not None:
                opened = False
                list_metadata = [e.strip() for e in metadata.split(",")]
                L, s, e, colorize_func = ptmeta.get_metadata(p.address, list_metadata=list_metadata)
                suffix += s
                epilog += e
                p.metadata = L # save so we can easily export to json later

            if search_value is not None:
                if not dbg.search_chunk(
                    ptm, p, search_value, search_type=search_type,
                    depth=search_depth, skip=skip_header
                ):
                    found_match = False
                    suffix += " [NO MATCH]"
                else:
                    suffix += pu.light_green(" [MATCH]")
                    found_match = True

            # XXX - the current representation is not really generic as we print the first short
            # as an ID and the second 2 bytes as 2 characters. We may want to support passing the
            # format string as an argument but this is already useful
            if print_offset != 0:
                mem = dbg.read_memory(
                    p.data_address + print_offset, 4
                )
                (id_, desc) = struct.unpack_from("<H2s", mem, 0x0)
                if h.is_ascii(desc):
                    suffix += " 0x%04x %s" % (id_, str(desc, encoding="utf-8"))
                else:
                    suffix += " 0x%04x hex(%s)" % (
                        id_,
                        str(binascii.hexlify(desc), encoding="utf-8"),
                    )

            # Only print the chunk type for non verbose
            if p.address == ptm.cache.par.sbrk_base:
                suffix += " (sbrk_base)"
            elif p.address == ptm.top(ptm.cache.mstate):
                suffix += " (top)"

            printed = False
            if verbose == 0:
                found_highlight = False
                # Only highlight chunks for non verbose
                if p.address in highlight_addresses:
                    found_highlight = True
                    highlight_addresses.remove(p.address)
                if p.type in highlight_types2:
                    found_highlight = True
                if len(highlight_metadata) > 0:
                    # We retrieve all metadata since we want to highlight chunks containing any of the
                    # metadata, even if we don't show some of the metadata
                    _, s, _, _ = ptmeta.get_metadata(p.address, list_metadata="all")
                    for m in highlight_metadata:
                        # we check in the one-line output as it should have less non-useful information
                        if m in s:
                            found_highlight = True
                            highlight_metadata_found.add(m)
                if found_highlight:
                    prefix += "* "
                if (not highlight_only or found_highlight) \
                    and (not match_only or found_match):
                    if header_once != None:
                        print(header_once)
                        header_once = None
                    if no_newline:
                        print(prefix + ptm.chunk_info(p, colorize_func=colorize_func, first_address=first_address, address_offset=address_offset) + suffix, end="")
                    else:
                        print(prefix + ptm.chunk_info(p, colorize_func=colorize_func, first_address=first_address, address_offset=address_offset) + suffix)
                    printed = True
            elif verbose >= 1 and (not match_only or found_match):
                if header_once != None:
                    print(header_once)
                    header_once = None
                print(p)
                printed = True
                # XXX - this is old code used in Cisco ASA. Need removal or merge?
                if ptm.ptchunk_callback is not None:
                    size = ptm.chunksize(p) - p.hdr_size
                    if p.data_address is not None:
                        # We can provide an excess of information and the
                        # callback can choose what to use
                        cbinfo = {}
                        cbinfo["caller"] = "ptchunk"
                        cbinfo["allocator"] = "ptmalloc"
                        cbinfo["addr"] = p.data_address
                        cbinfo["hdr_sz"] = p.hdr_size
                        cbinfo["chunksz"] = ptm.chunksize(p)
                        cbinfo["min_hdr_sz"] = ptm.INUSE_HDR_SZ
                        cbinfo["data_size"] = size
                        cbinfo["inuse"] = p.inuse
                        cbinfo["size_sz"] = ptm.SIZE_SZ
                        if debug:
                            cbinfo["debug"] = True
                            print(cbinfo)
                        # We expect callback to tell us how much data it
                        # 'consumed' in printing out info
                        dump_offset = ptm.ptchunk_callback(cbinfo)
                    # mem-based callbacks not yet supported
            if printed:
                if hexdump:
                    dbg.print_hexdump_chunk(ptm, p, maxlen=maxbytes, off=dump_offset, unit=hexdump_unit, verbose=verbose)
                if verbose >= 1 and epilog:
                    print(epilog, end="")
                if commands:
                    for command in commands.split(";"):
                        formatted_command = command.replace("@", f"{p.address:#x}")
                        print(dbg.execute(formatted_command))
                chunks.append(p)
                if count != None:
                    count -= 1
            if count_handle != None:
                count_handle -= 1
            if count != 0 and count_handle != 0:
                if printed and (verbose >= 1 or hexdump):
                    print("--")
                if p.is_top:
                    # Only print the chunk type for non verbose
                    if verbose == 0:
                        if ptm.cache.mstate.address == ptm.cache.main_arena_address:
                            start = ptm.cache.par.sbrk_base
                        else:
                            # XXX - seems mstate is at offset 0x20 so there is 0x10 unknown bytes and 0x10 bytes for the chunk
                            # header holding the mstate. So aligning to page works?
                            start = ((ptm.cache.mstate.address & ~0xfff) + ptm.MALLOC_ALIGN_MASK) & ~ptm.MALLOC_ALIGN_MASK
                        end = int(start + ptm.cache.mstate.max_system_mem)
                        if address_offset is True:
                            end -= first_address
                        if ptm.cache.mstate.address == ptm.cache.main_arena_address:
                            print("{:#x}".format(end), end="")
                            print(" (sbrk_end)")
                        else:
                            print("{:#x}".format(end))
                    else:
                        print("Stopping due to end of heap")
                    break
                p = mc.malloc_chunk(
                    ptm,
                    addr=(p.address + ptm.chunksize(p)),
                    debugger=dbg,
                    use_cache=use_cache,
                    tcache=tcache,
                    fast=fast,
                    allow_invalid=allow_invalid
                )
                if not p.initOK:
                    break
            else:
                break

        if len(highlight_addresses) != 0:
            pu.print_error("WARNING: Could not find these chunk addresses: %s" % (", ".join(["0x%x" % x for x in highlight_addresses])))
        if len(highlight_metadata-highlight_metadata_found) != 0:
            pu.print_error("WARNING: Could not find these metadata: %s" % (", ".join(list(highlight_metadata-highlight_metadata_found))))

        return chunks
コード例 #7
0
    def prepare_args_if_negative_count(self):
        """This is a little bit of a hack. The idea is to handle cases
        where the user wants to print N chunks going backwards.
        We are going to list all the chunks in the arena until we find all 
        the addresses requested and then craft new arguments as if the user
        requested to print from new addresses N chunks before the requested
        addresses before calling parse_many2()
        """

        self.args.reverse = False
        # Nothing to do if the count is positive or unlimited
        if self.args.count == None or self.args.count >= 0:
            return
        # We are making the count positive
        self.args.count = self.args.count*-1
        # And we print N chunks before the requested chunk + the actual chunk
        self.args.count += 1
        
        addresses = self.dbg.parse_address(self.args.addresses)
        if len(addresses) == 0:
            pu.print_error("WARNING: No valid address supplied")
            self.parser.print_help()
            return []
        # We will fill it with new addresses later below
        self.args.addresses = []

        # Let's get all the chunks' addresses in the arena

        mstate = self.cache.mstate
        par = self.cache.par

        if mstate.address == self.cache.main_arena_address:
            addr, _ = self.dbg.get_heap_address(par)
        else:
            print("Using manual arena calculation for heap start")
            addr = (mstate.address + mstate.size + self.ptm.MALLOC_ALIGN_MASK) & ~self.ptm.MALLOC_ALIGN_MASK

        chunks_addresses = []
        chunks_addresses.append(addr)
        while True:
            p = mc.malloc_chunk(
                self.ptm, 
                addr, 
                read_data=False, 
                debugger=self.dbg,
                use_cache=True
            )
            if not p.initOK:
                pu.print_error("WARNING: Stopping due to invalid chunk parsed in arena")
                break
            chunks_addresses.append(addr)

            if p.address == self.ptm.top(self.cache.mstate):
                break

            addr = self.ptm.next_chunk(p)

        # Prepare arguments for "ptchunk" format
        # i.e. for every address, get the new address N chunks before
        for addr in addresses:
            try:
                index = chunks_addresses.index(addr)
            except ValueError:
                pu.print_error(f"WARNING: Could not find {addr:#x} in arena, skipping")
                continue
            index -= self.args.count
            if index < 0:
                pu.print_error(f"WARNING: Reaching beginning of arena with {addr:#x}")
                index = 0
            self.args.addresses.append(f"{chunks_addresses[index]:#x}")