def malloc_chunk(addr,fake=False): """ Prints out the malloc_chunk at the specified address. """ main_heap = pwndbg.heap.current if not isinstance(addr, six.integer_types): addr = int(addr) chunk = read_chunk(addr) size = int(chunk['size']) actual_size = size & ~7 prev_inuse, is_mmapped, non_main_arena = main_heap.chunk_flags(size) arena = None if not fake and non_main_arena: arena = main_heap.get_heap(addr)['ar_ptr'] fastbins = [] if fake else main_heap.fastbins(arena) header = M.get(addr) if fake: header += message.prompt(' FAKE') if prev_inuse: if actual_size in fastbins: header += message.hint(' FASTBIN') else: header += message.hint(' PREV_INUSE') if is_mmapped: header += message.hint(' IS_MMAPED') if non_main_arena: header += message.hint(' NON_MAIN_ARENA') print(header, chunk["value"]) return chunk
def configfile_print_scope(scope, show_all=False): params = pwndbg.config.get_params(scope) if not show_all: params = list(filter(lambda p: p.is_changed, params)) if params: if not show_all: print(hint('Showing only changed values:')) for p in params: print('# %s: %s' % (p.optname, p.docstring)) print('# default: %s' % p.native_default) print('set %s %s' % (p.optname, p.native_value)) print() else: print(hint('No changed values. To see current values use `%s`.' % scope))
def config(filter_pattern): values = get_config_parameters('config', filter_pattern) if not values: print(hint('No config parameter found with filter "{}"'.format(filter_pattern))) return longest_optname = max(map(len, [v.optname for v in values])) longest_value = max(map(len, [extend_value_with_default(repr(v.value), repr(v.default)) for v in values])) header = print_row('Name', 'Value', 'Def', 'Documentation', longest_optname, longest_value) print('-' * (len(header))) for v in sorted(values): print_row(v.optname, repr(v.value), repr(v.default), v.docstring, longest_optname, longest_value) print(hint('You can set config variable with `set <config-var> <value>`')) print(hint('You can generate configuration file using `configfile` ' '- then put it in your .gdbinit after initializing pwndbg'))
def __str__(self): res = [] prefix = '[%%%ds] ' % (pwndbg.arch.ptrsize * 2) prefix_len = len(prefix % ('')) arena_name = hex(self.addr) if self.addr != pwndbg.heap.current.main_arena.address else 'main' res.append(message.hint(prefix % (arena_name)) + str(self.heaps[0])) for h in self.heaps[1:]: res.append(' ' * prefix_len + str(h)) return '\n'.join(res)
def probeleak(address=None, count=0x40, max_distance=0x0): address = int(address) address &= pwndbg.arch.ptrmask ptrsize = pwndbg.arch.ptrsize count = max(int(count), ptrsize) off_zeros = int(math.ceil(math.log(count,2)/4)) if count > address > 0x10000: # in case someone puts in an end address and not a count (smh) print(message.warn("Warning: you gave an end address, not a count. Substracting 0x%x from the count." % (address))) count -= address try: data = pwndbg.memory.read(address, count, partial=True) except gdb.error as e: print(message.error(str(e))) return if not data: print(message.error("Couldn't read memory at 0x%x. See 'probeleak -h' for the usage." % (address,))) return found = False for i in range(0, len(data) - ptrsize + 1): p = pwndbg.arch.unpack(data[i:i+ptrsize]) page = find_module(p, max_distance) if page: if not found: print(M.legend()) found = True mod_name = page.objfile if not mod_name: mod_name = '[anon]' if p >= page.end: right_text = '(%s) %s + 0x%x + 0x%x (outside of the page)' % (page.permstr, mod_name, page.memsz, p - page.end) elif p < page.start: right_text = '(%s) %s - 0x%x (outside of the page)' % (page.permstr, mod_name, page.start - p) else: right_text = '(%s) %s + 0x%x' % (page.permstr, mod_name, p - page.start) offset_text = '0x%0*x' % (off_zeros, i) p_text = '0x%0*x' % (int(ptrsize*2), p) text = '%s: %s = %s' % (offset_text, M.get(p, text=p_text), M.get(p, text=right_text)) symbol = pwndbg.symbol.get(p) if symbol: text += ' (%s)' % symbol print(text) if not found: print(message.hint('No leaks found at 0x%x-0x%x :(' % (address, address+count)))
def __str__(self): res = [] prefix = '[%%%ds] ' % (pwndbg.arch.ptrsize * 2) prefix_len = len(prefix % ('')) arena_name = hex( self.addr ) if self.addr != pwndbg.heap.current.main_arena.address else 'main' res.append(message.hint(prefix % (arena_name)) + str(self.heaps[0])) for h in self.heaps[1:]: res.append(' ' * prefix_len + str(h)) return '\n'.join(res)
def inform_report_issue(exception_msg): """ Informs user that he can report an issue. The use of `memoize` makes it reporting only once for a given exception message. """ print( message.notice( "If that is an issue, you can report it on https://github.com/pwndbg/pwndbg/issues\n" "(Please don't forget to search if it hasn't been reported before)\n" "To generate the report and open a browser, you may run ") + message.hint("`bugreport --run-browser`") + message.notice("\nPS: Pull requests are welcome"))
def inform_report_issue(exception_msg): """ Informs user that he can report an issue. The use of `memoize` makes it reporting only once for a given exception message. """ print(message.notice( "If that is an issue, you can report it on https://github.com/pwndbg/pwndbg/issues\n" "(Please don't forget to search if it hasn't been reported before)\n" "To generate the report and open a browser, you may run ") + message.hint("`bugreport --run-browser`") + message.notice("\nPS: Pull requests are welcome") )
def malloc_chunk(addr,fake=False): """ Prints out the malloc_chunk at the specified address. """ main_heap = pwndbg.heap.current if not isinstance(addr, six.integer_types): addr = int(addr) chunk = read_chunk(addr) size = int(chunk['size']) actual_size = size & ~7 prev_inuse, is_mmapped, non_main_arena = main_heap.chunk_flags(size) arena = None if not fake and non_main_arena: arena = main_heap.get_heap(addr)['ar_ptr'] fastbins = [] if fake else main_heap.fastbins(arena) header = M.get(addr) if fake: header += message.prompt(' FAKE') if prev_inuse: if actual_size in fastbins: header += message.hint(' FASTBIN') else: header += message.hint(' PREV_INUSE') if is_mmapped: header += message.hint(' IS_MMAPED') if non_main_arena: header += message.hint(' NON_MAIN_ARENA') chunk_str='{\n' for key in chunk["value"].type.keys(): chunk_str+=' %s = %s,\n'%(str(key),hex(int(chunk["value"][key]))) chunk_str+='}' print(header, chunk_str) #print(header, chunk["value"]) return chunk
def format_bin(bins, verbose=False): main_heap = pwndbg.heap.current fd_offset = main_heap.chunk_key_offset('fd') result = [] for size in bins: chain = bins[size] if not verbose and chain == [0]: continue formatted_chain = pwndbg.chain.format(chain, offset=fd_offset) if isinstance(size, int): size = hex(size) result.append((message.hint(size) + ': ').ljust(13) + formatted_chain) if not result: result.append(message.hint('empty')) return result
def format_bin(bins, verbose=False, offset=None): main_heap = pwndbg.heap.current if offset is None: offset = main_heap.chunk_key_offset('fd') result = [] bins_type = bins.pop('type') for size in bins: b = bins[size] count, is_chain_corrupted = None, False # fastbins consists of only single linked list if bins_type == 'fastbins': chain_fd = b # tcachebins consists of single linked list and entries count elif bins_type == 'tcachebins': chain_fd, count = b # normal bins consists of double linked list and may be corrupted (we can detect corruption) else: # normal bin chain_fd, chain_bk, is_chain_corrupted = b if not verbose and (chain_fd == [0] and not count) and not is_chain_corrupted: continue if bins_type == 'tcachebins': limit = 8 if count <= 7: limit = count + 1 formatted_chain = pwndbg.chain.format(chain_fd[0], offset=offset, limit=limit) else: formatted_chain = pwndbg.chain.format(chain_fd[0], offset=offset) if isinstance(size, int): size = hex(size) if is_chain_corrupted: line = message.hint(size) + message.error(' [corrupted]') + '\n' line += message.hint('FD: ') + formatted_chain + '\n' line += message.hint('BK: ') + pwndbg.chain.format(chain_bk[0], offset=main_heap.chunk_key_offset('bk')) else: if count is not None: line = (message.hint(size) + message.hint(' [%3d]' % count) + ': ').ljust(13) else: line = (message.hint(size) + ': ').ljust(13) line += formatted_chain result.append(line) if not result: result.append(message.hint('empty')) return result
def handle(name='Error'): """Displays an exception to the user, optionally displaying a full traceback and spawning an interactive post-moretem debugger. Notes: - ``set exception-verbose on`` enables stack traces. - ``set exception-debugger on`` enables the post-mortem debugger. """ # This is for unit tests so they fail on exceptions instead of displaying them. if getattr(sys, '_pwndbg_unittest_run', False) is True: E, V, T = sys.exc_info() e = E(V) e.__traceback__ = T raise e # Display the error if debug or verbose: exception_msg = traceback.format_exc() print(exception_msg) inform_report_issue(exception_msg) else: exc_type, exc_value, exc_traceback = sys.exc_info() print( message.error('Exception occured: {}: {} ({})'.format( name, exc_value, exc_type))) print( message.notice('For more info invoke `') + message.hint('set exception-verbose on') + message.notice( '` and rerun the command\nor debug it by yourself with `') + message.hint('set exception-debugger on') + message.notice('`')) # Break into the interactive debugger if debug: with pwndbg.stdio.stdio: pdb.post_mortem()
def handle(name='Error'): """Displays an exception to the user, optionally displaying a full traceback and spawning an interactive post-moretem debugger. Notes: - ``set exception-verbose on`` enables stack traces. - ``set exception-debugger on`` enables the post-mortem debugger. """ # This is for unit tests so they fail on exceptions instead of displaying them. if getattr(sys, '_pwndbg_unittest_run', False) is True: E, V, T = sys.exc_info() e = E(V) e.__traceback__ = T raise e # Display the error if debug or verbose: exception_msg = traceback.format_exc() print(exception_msg) inform_report_issue(exception_msg) else: exc_type, exc_value, exc_traceback = sys.exc_info() print(message.error('Exception occured: {}: {} ({})'.format(name, exc_value, exc_type))) print(message.notice('For more info invoke `') + message.hint('set exception-verbose on') + message.notice('` and rerun the command\nor debug it by yourself with `') + message.hint('set exception-debugger on') + message.notice('`')) # Break into the interactive debugger if debug: with pwndbg.stdio.stdio: pdb.post_mortem()
def heap(addr=None): """ Prints out all chunks in the main_arena, or the arena specified by `addr`. """ main_heap = pwndbg.heap.current main_arena = main_heap.get_arena(addr) if main_arena is None: return heap_region = main_heap.get_region(addr) if heap_region is None: print(message.error('Could not find the heap')) return top = main_arena['top'] last_remainder = main_arena['last_remainder'] print(message.hint('Top Chunk: ') + M.get(top)) print(message.hint('Last Remainder: ') + M.get(last_remainder)) print() # Print out all chunks on the heap # TODO: Add an option to print out only free or allocated chunks addr = heap_region.vaddr while addr <= top: chunk = malloc_chunk(addr) size = int(chunk['size']) # Clear the bottom 3 bits size &= ~7 if size == 0: break addr += size
def theme(filter_pattern): values = get_config_parameters('theme', filter_pattern) if not values: print( hint('No theme parameter found with filter "{}"'.format( filter_pattern))) return longest_optname = max(map(len, [v.optname for v in values])) longest_value = max( map(len, [ extend_value_with_default(str(v.value), str(v.default)) for v in values ])) header = print_row('Name', 'Value', 'Def', 'Documentation', longest_optname, longest_value) print('-' * (len(header))) for v in sorted(values): if isinstance(v, pwndbg.color.theme.ColoredParameter): value = generateColorFunction(v.value)(v.value) default = generateColorFunction(v.default)(v.default) elif isinstance(v.value, str): value = "'%s'" % str(v.value) default = str(v.default) else: value = repr(v.value) default = repr(v.default) print_row(v.optname, value, default, v.docstring, longest_optname, longest_value) print(hint('You can set theme variable with `set <theme-var> <value>`')) print( hint('You can generate theme config file using `themefile` ' '- then put it in your .gdbinit after initializing pwndbg'))
def format_bin(bins, verbose=False, offset=None): main_heap = pwndbg.heap.current if offset is None: offset = main_heap.chunk_key_offset('fd') result = [] bins_type = bins.pop('type') for size in bins: b = bins[size] count, is_chain_corrupted = None, False # fastbins consists of only single linked list if bins_type == 'fastbins': chain_fd = b # tcachebins consists of single linked list and entries count elif bins_type == 'tcachebins': chain_fd, count = b # normal bins consists of double linked list and may be corrupted (we can detect corruption) else: # normal bin chain_fd, chain_bk, is_chain_corrupted = b if not verbose and (chain_fd == [0] and not count) and not is_chain_corrupted: continue formatted_chain = pwndbg.chain.format(chain_fd[0], offset=offset) if isinstance(size, int): size = hex(size) if is_chain_corrupted: line = message.hint(size) + message.error(' [corrupted]') + '\n' line += message.hint('FD: ') + formatted_chain + '\n' line += message.hint('BK: ') + pwndbg.chain.format(chain_bk[0], offset=main_heap.chunk_key_offset('bk')) else: if count is not None: line = (message.hint(size) + message.hint(' [%3d]' % count) + ': ').ljust(13) else: line = (message.hint(size) + ': ').ljust(13) line += formatted_chain result.append(line) if not result: result.append(message.hint('empty')) return result
def probeleak(address=None, count=0x40): address = int(address) address &= pwndbg.arch.ptrmask count = max(int(count), 0) ptrsize = pwndbg.arch.ptrsize off_zeros = int(math.ceil(math.log(count,2)/4)) if count > address > 0x10000: # in case someone puts in an end address and not a count (smh) count -= address if count % ptrsize > 0: newcount = count - (count % ptrsize) print(message.warning("Warning: count 0x%x is not a multiple of 0x%x; truncating to 0x%x." % (count, ptrsize, newcount))) count = newcount try: data = pwndbg.memory.read(address, count, partial=True) except gdb.error as e: print(message.error(str(e))) return if not data: print(message.error("Couldn't read memory at 0x%x" % (address,))) return found = False for i in range(0, count, ptrsize): p = pwndbg.arch.unpack(data[i:i+ptrsize]) page = find_module(p) if page: if not found: print(M.legend()) found = True mod_name = page.objfile if not mod_name: mod_name = '[anon]' fmt = '+0x{offset:0{n1}x}: 0x{ptr:0{n2}x} = {page}' right_text = ('(%s) %s + 0x%x') % (page.permstr, mod_name, p - page.vaddr + page.offset) print(fmt.format(n1=off_zeros, n2=ptrsize*2, offset=i, ptr=p, page=M.get(p, text=right_text))) if not found: print(message.hint('No leaks found at 0x{:x}-0x{:x} :('.format(address, address+count)))
def arenas(): """ Prints out allocated arenas """ heap = pwndbg.heap.current addr = None arena = heap.get_arena(addr) main_arena_addr = int(arena.address) fmt = '[%%%ds]' % (pwndbg.arch.ptrsize *2) while addr != main_arena_addr: h = heap.get_region(addr) if not h: print(message.error('Could not find the heap')) return hdr = message.hint(fmt % (hex(addr) if addr else 'main')) print(hdr, M.heap(str(h))) addr = int(arena['next']) arena = heap.get_arena(addr)
def got(name_filter=""): relro_status = pwndbg.wrappers.checksec.relro_status() pie_status = pwndbg.wrappers.checksec.pie_status() jmpslots = list(pwndbg.wrappers.readelf.get_jmpslots()) if not len(jmpslots): print(message.error("NO JUMP_SLOT entries available in the GOT")) return if "PIE enabled" in pie_status: bin_base = pwndbg.elf.exe().address relro_color = message.off if "Partial" in relro_status: relro_color = message.warn elif "Full" in relro_status: relro_color = message.on print( "\nGOT protection: %s | GOT functions: %d\n " % (relro_color(relro_status), len(jmpslots)) ) for line in jmpslots: address, info, rtype, value, name = line.split()[:5] if name_filter not in name: continue address_val = int(address, 16) if ( "PIE enabled" in pie_status ): # if PIE, address is only the offset from the binary base address address_val = bin_base + address_val got_address = pwndbg.memory.pvoid(address_val) print( "[0x%x] %s -> %s" % (address_val, message.hint(name), pwndbg.chain.format(got_address)) )
def format_bin(bins, verbose=False, offset=None): allocator = pwndbg.heap.current if offset is None: offset = allocator.chunk_key_offset("fd") result = [] bins_type = bins.pop("type") for size in bins: b = bins[size] count, is_chain_corrupted = None, False safe_lnk = False # fastbins consists of only single linked list if bins_type == "fastbins": chain_fd = b safe_lnk = pwndbg.glibc.check_safe_linking() # tcachebins consists of single linked list and entries count elif bins_type == "tcachebins": chain_fd, count = b safe_lnk = pwndbg.glibc.check_safe_linking() # normal bins consists of double linked list and may be corrupted (we can detect corruption) else: # normal bin chain_fd, chain_bk, is_chain_corrupted = b if not verbose and (chain_fd == [0] and not count) and not is_chain_corrupted: continue if bins_type == "tcachebins": limit = 8 if count <= 7: limit = count + 1 formatted_chain = pwndbg.chain.format(chain_fd[0], offset=offset, limit=limit, safe_linking=safe_lnk) else: formatted_chain = pwndbg.chain.format(chain_fd[0], offset=offset, safe_linking=safe_lnk) if isinstance(size, int): size = hex(size) if is_chain_corrupted: line = message.hint(size) + message.error(" [corrupted]") + "\n" line += message.hint("FD: ") + formatted_chain + "\n" line += message.hint("BK: ") + pwndbg.chain.format( chain_bk[0], offset=allocator.chunk_key_offset("bk")) else: if count is not None: line = (message.hint(size) + message.hint(" [%3d]" % count) + ": ").ljust(13) else: line = (message.hint(size) + ": ").ljust(13) line += formatted_chain result.append(line) if not result: result.append(message.hint("empty")) return result
def __str__(self): fmt = '[%%%ds]' % (pwndbg.arch.ptrsize * 2) return message.hint(fmt % (hex(self.first_chunk))) + M.heap(str(pwndbg.vmmap.find(self.addr)))
def malloc_chunk(addr, fake=False, verbose=False, simple=False): """Print a malloc_chunk struct's contents.""" # points to the real start of the chunk cursor = int(addr) allocator = pwndbg.heap.current ptr_size = allocator.size_sz size_field = pwndbg.memory.u(cursor + allocator.chunk_key_offset('size')) real_size = size_field & ~allocator.malloc_align_mask headers_to_print = [] # both state (free/allocated) and flags fields_to_print = set() # in addition to addr and size out_fields = "Addr: {}\n".format(M.get(cursor)) if fake: headers_to_print.append(message.on("Fake chunk")) verbose = True # print all fields for fake chunks if simple: chunk = read_chunk(cursor) if not headers_to_print: headers_to_print.append(message.hint(M.get(cursor))) prev_inuse, is_mmapped, non_main_arena = allocator.chunk_flags( int(chunk['size'])) if prev_inuse: headers_to_print.append(message.hint('PREV_INUSE')) if is_mmapped: headers_to_print.append(message.hint('IS_MMAPED')) if non_main_arena: headers_to_print.append(message.hint('NON_MAIN_ARENA')) print(' | '.join(headers_to_print)) for key, val in chunk.items(): print(message.system(key) + ": 0x{:02x}".format(int(val))) print('') return arena = allocator.get_arena_for_chunk(cursor) arena_address = None is_top = False if not fake and arena: arena_address = arena.address top_chunk = arena['top'] if cursor == top_chunk: headers_to_print.append(message.off("Top chunk")) is_top = True if not is_top: fastbins = allocator.fastbins(arena_address) or {} smallbins = allocator.smallbins(arena_address) or {} largebins = allocator.largebins(arena_address) or {} unsortedbin = allocator.unsortedbin(arena_address) or {} if allocator.has_tcache(): tcachebins = allocator.tcachebins(None) if real_size in fastbins.keys() and cursor in fastbins[real_size]: headers_to_print.append(message.on("Free chunk (fastbins)")) if not verbose: fields_to_print.add('fd') elif real_size in smallbins.keys() and cursor in bin_addrs( smallbins[real_size], "smallbins"): headers_to_print.append(message.on("Free chunk (smallbins)")) if not verbose: fields_to_print.update(['fd', 'bk']) elif real_size >= list( largebins.items())[0][0] and cursor in bin_addrs( largebins[(list( largebins.items())[allocator.largebin_index(real_size) - 64][0])], "largebins"): headers_to_print.append(message.on("Free chunk (largebins)")) if not verbose: fields_to_print.update( ['fd', 'bk', 'fd_nextsize', 'bk_nextsize']) elif cursor in bin_addrs(unsortedbin['all'], "unsortedbin"): headers_to_print.append(message.on("Free chunk (unsortedbin)")) if not verbose: fields_to_print.update(['fd', 'bk']) elif allocator.has_tcache() and real_size in tcachebins.keys( ) and cursor + ptr_size * 2 in bin_addrs(tcachebins[real_size], "tcachebins"): headers_to_print.append(message.on("Free chunk (tcache)")) if not verbose: fields_to_print.add('fd') else: headers_to_print.append(message.hint("Allocated chunk")) if verbose: fields_to_print.update( ['prev_size', 'size', 'fd', 'bk', 'fd_nextsize', 'bk_nextsize']) else: out_fields += "Size: 0x{:02x}\n".format(size_field) prev_inuse, is_mmapped, non_main_arena = allocator.chunk_flags(size_field) if prev_inuse: headers_to_print.append(message.hint('PREV_INUSE')) if is_mmapped: headers_to_print.append(message.hint('IS_MMAPED')) if non_main_arena: headers_to_print.append(message.hint('NON_MAIN_ARENA')) fields_ordered = [ 'prev_size', 'size', 'fd', 'bk', 'fd_nextsize', 'bk_nextsize' ] for field_to_print in fields_ordered: if field_to_print in fields_to_print: out_fields += message.system( field_to_print) + ": 0x{:02x}\n".format( pwndbg.memory.u( cursor + allocator.chunk_key_offset(field_to_print))) print(' | '.join(headers_to_print) + "\n" + out_fields)
def init_ida_rpc_client(): global _ida, _ida_last_exception, _ida_last_connection_check if not ida_enabled: return now = time.time() if _ida is None and (now - _ida_last_connection_check) < int(ida_timeout) + 5: return addr = 'http://{host}:{port}'.format(host=ida_rpc_host, port=ida_rpc_port) _ida = xmlrpclib.ServerProxy(addr) socket.setdefaulttimeout(int(ida_timeout)) exception = None # (type, value, traceback) try: _ida.here() print( message.success( "Pwndbg successfully connected to Ida Pro xmlrpc: %s" % addr)) except socket.error as e: if e.errno != errno.ECONNREFUSED: exception = sys.exc_info() _ida = None except socket.timeout: exception = sys.exc_info() _ida = None except xmlrpclib.ProtocolError: exception = sys.exc_info() _ida = None if exception: if not isinstance( _ida_last_exception, exception[0]) or _ida_last_exception.args != exception[1].args: if hasattr( pwndbg.config, "exception_verbose") and pwndbg.config.exception_verbose: print(message.error("[!] Ida Pro xmlrpc error")) traceback.print_exception(*exception) else: exc_type, exc_value, _ = exception print( message.error( 'Failed to connect to IDA Pro ({}: {})'.format( exc_type.__qualname__, exc_value))) if exc_type is socket.timeout: print( message.notice( 'To increase the time to wait for IDA Pro use `') + message.hint( 'set ida-timeout <new-timeout-in-seconds>') + message.notice('`')) else: print( message.notice('For more info invoke `') + message.hint('set exception-verbose on') + message.notice('`')) print( message.notice('To disable IDA Pro integration invoke `') + message.hint('set ida-enabled off') + message.notice('`')) _ida_last_exception = exception and exception[1] _ida_last_connection_check = now
def probeleak(address=None, count=0x40, max_distance=0x0, point_to=None, max_ptrs=0, flags=None): address = int(address) address &= pwndbg.arch.ptrmask ptrsize = pwndbg.arch.ptrsize count = max(int(count), ptrsize) off_zeros = int(math.ceil(math.log(count, 2) / 4)) if flags != None: require_flags = flags_str2int(flags) if count > address > 0x10000: # in case someone puts in an end address and not a count (smh) print( message.warn( "Warning: you gave an end address, not a count. Substracting 0x%x from the count." % (address))) count -= address try: data = pwndbg.memory.read(address, count, partial=True) except gdb.error as e: print(message.error(str(e))) return if not data: print( message.error( "Couldn't read memory at 0x%x. See 'probeleak -h' for the usage." % (address, ))) return found = False find_cnt = 0 for i in range(0, len(data) - ptrsize + 1): p = pwndbg.arch.unpack(data[i:i + ptrsize]) page = find_module(p, max_distance) if page: if point_to != None and point_to not in page.objfile: continue if flags != None and not satisfied_flags(require_flags, page.flags): continue if not found: print(M.legend()) found = True mod_name = page.objfile if not mod_name: mod_name = '[anon]' if p >= page.end: right_text = '(%s) %s + 0x%x + 0x%x (outside of the page)' % ( page.permstr, mod_name, page.memsz, p - page.end) elif p < page.start: right_text = '(%s) %s - 0x%x (outside of the page)' % ( page.permstr, mod_name, page.start - p) else: right_text = '(%s) %s + 0x%x' % (page.permstr, mod_name, p - page.start) offset_text = '0x%0*x' % (off_zeros, i) p_text = '0x%0*x' % (int(ptrsize * 2), p) text = '%s: %s = %s' % (offset_text, M.get( p, text=p_text), M.get(p, text=right_text)) symbol = pwndbg.symbol.get(p) if symbol: text += ' (%s)' % symbol print(text) find_cnt += 1 if max_ptrs != 0 and find_cnt >= max_ptrs: break if not found: print( message.hint('No leaks found at 0x%x-0x%x :(' % (address, address + count)))
def new_malloc_chunk(addr, fake=False, verbose=False, simple=False): idx, start, end, rest = (lambda idx, start, end, **rest: (idx, start, end, rest))(**scope) scope['idx'] += 1 if start is not None and end is not None and idx not in range( start, end + 1): return ## original begin cursor = int(addr) allocator = pwndbg.heap.current ptr_size = allocator.size_sz size_field = pwndbg.memory.u(cursor + allocator.chunk_key_offset('size')) real_size = size_field & ~allocator.malloc_align_mask headers_to_print = [] # both state (free/allocated) and flags fields_to_print = set() # in addition to addr and size out_fields = "Addr: {}\n".format(M.get(cursor)) if fake: headers_to_print.append(message.on("Fake chunk")) verbose = True # print all fields for fake chunks if simple: chunk = read_chunk(cursor) if not headers_to_print: headers_to_print.append(message.hint(M.get(cursor))) prev_inuse, is_mmapped, non_main_arena = allocator.chunk_flags( int(chunk['size'])) if prev_inuse: headers_to_print.append(message.hint('PREV_INUSE')) if is_mmapped: headers_to_print.append(message.hint('IS_MMAPED')) if non_main_arena: headers_to_print.append(message.hint('NON_MAIN_ARENA')) print(' | '.join(headers_to_print)) for key, val in chunk.items(): print(message.system(key) + ": 0x{:02x}".format(int(val))) print('') return arena = allocator.get_arena_for_chunk(cursor) arena_address = None is_top = False if not fake and arena: arena_address = arena.address top_chunk = arena['top'] if cursor == top_chunk: headers_to_print.append(message.off("Top".center(9, " "))) is_top = True if not is_top: fastbins = allocator.fastbins(arena_address) or {} smallbins = allocator.smallbins(arena_address) or {} largebins = allocator.largebins(arena_address) or {} unsortedbin = allocator.unsortedbin(arena_address) or {} if allocator.has_tcache(): tcachebins = allocator.tcachebins(None) if real_size in fastbins.keys() and cursor in fastbins[real_size]: headers_to_print.append(message.on("F(fast)".center(9, " "))) if not verbose: fields_to_print.add('fd') elif real_size in smallbins.keys() and cursor in bin_addrs( smallbins[real_size], "smallbins"): headers_to_print.append(message.on("F(small)".center(9, " "))) if not verbose: fields_to_print.update(['fd', 'bk']) elif real_size >= list( largebins.items())[0][0] and cursor in bin_addrs( largebins[(list(largebins.items())[ allocator.largebin_index(real_size) - 64][0])], "largebins"): headers_to_print.append(message.on("F(large)".center(9, " "))) if not verbose: fields_to_print.update( ['fd', 'bk', 'fd_nextsize', 'bk_nextsize']) elif cursor in bin_addrs(unsortedbin['all'], "unsortedbin"): headers_to_print.append(message.on("F(unsort)".center(9, " "))) if not verbose: fields_to_print.update(['fd', 'bk']) elif allocator.has_tcache() and real_size in tcachebins.keys( ) and cursor + ptr_size * 2 in bin_addrs(tcachebins[real_size], "tcachebins"): headers_to_print.append(message.on("F(tcache)".center(9, " "))) if not verbose: fields_to_print.add('fd') else: headers_to_print.append( message.hint("Allocated".center(9, " "))) if verbose: fields_to_print.update([ 'prev_size', 'size', 'fd', 'bk', 'fd_nextsize', 'bk_nextsize' ]) else: out_fields += "Size: 0x{:02x}\n".format(size_field) prev_inuse, is_mmapped, non_main_arena = allocator.chunk_flags( size_field) if prev_inuse: headers_to_print.append(message.hint('PREV_INUSE')) if is_mmapped: headers_to_print.append(message.hint('IS_MMAPED')) if non_main_arena: headers_to_print.append(message.hint('NON_MAIN_ARENA')) ## original end if not verbose: def ascii_char(byte): if byte >= 0x20 and byte < 0x7e: return chr(byte) # Ensure we return a str else: return "." content = "" content += "[{:03d}] ".format(idx) content += M.get(cursor) + f" SIZE=0x{size_field:08x}" data = addr + pwndbg.arch.ptrsize * 2 content += " DATA[" + hex(data) + "]" if size_field >= 0x20: bytes_read = pwndbg.memory.read(data, 0x20, partial=True) else: bytes_read = pwndbg.memory.read(data, size, partial=True) content += " |" + "".join([ascii_char(c) for c in bytes_read]) + "| " print(content + " | ".join(headers_to_print)) else: print_chunk_detail(addr, size_field)
def heap(addr=None, verbose=False): """Iteratively print chunks on a heap, default to the current thread's active heap. """ allocator = pwndbg.heap.current heap_region = allocator.get_heap_boundaries(addr) arena = allocator.get_arena_for_chunk( addr) if addr else allocator.get_arena() top_chunk = arena['top'] ptr_size = allocator.size_sz # Calculate where to start printing; if an address was supplied, use that, # if this heap belongs to the main arena, start at the beginning of the # heap's mapping, otherwise, compensate for the presence of a heap_info # struct and possibly an arena. if addr: cursor = int(addr) elif arena == allocator.main_arena: cursor = heap_region.start else: cursor = heap_region.start + allocator.heap_info.sizeof if pwndbg.vmmap.find(allocator.get_heap( heap_region.start)['ar_ptr']) == heap_region: # Round up to a 2-machine-word alignment after an arena to # compensate for the presence of the have_fastchunks variable # in GLIBC versions >= 2.27. cursor += (allocator.malloc_state.sizeof + ptr_size) & ~allocator.malloc_align_mask # i686 alignment heuristic first_chunk_size = pwndbg.arch.unpack( pwndbg.memory.read(cursor + ptr_size, ptr_size)) if first_chunk_size == 0: cursor += ptr_size * 2 while cursor in heap_region: old_cursor = cursor size_field = pwndbg.memory.u(cursor + allocator.chunk_key_offset('size')) real_size = size_field & ~allocator.malloc_align_mask if cursor == top_chunk: out = message.off("Top chunk\n") out += "Addr: {}\nSize: 0x{:02x}".format(M.get(cursor), size_field) print(out) break fastbins = allocator.fastbins(arena.address) smallbins = allocator.smallbins(arena.address) largebins = allocator.largebins(arena.address) unsortedbin = allocator.unsortedbin(arena.address) if allocator.has_tcache(): tcachebins = allocator.tcachebins(None) out = "Addr: {}\nSize: 0x{:02x}\n".format(M.get(cursor), size_field) if real_size in fastbins.keys() and cursor in fastbins[real_size]: out = message.on("Free chunk (fastbins)\n") + out if not verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) elif real_size in smallbins.keys() and cursor in bin_addrs( smallbins[real_size], "smallbins"): out = message.on("Free chunk (smallbins)\n") + out if not verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) out += message.system("bk: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk'))) elif real_size >= list( largebins.items())[0][0] and cursor in bin_addrs( largebins[(list( largebins.items())[allocator.largebin_index(real_size) - 64][0])], "largebins"): out = message.on("Free chunk (largebins)\n") + out if not verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) out += message.system("bk: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk'))) out += message.system("fd_nextsize: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd_nextsize'))) out += message.system("bk_nextsize: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk_nextsize'))) elif cursor in bin_addrs(unsortedbin['all'], "unsortedbin"): out = message.on("Free chunk (unsortedbin)\n") + out if not verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) out += message.system("bk: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk'))) elif allocator.has_tcache() and real_size in tcachebins.keys( ) and cursor + ptr_size * 2 in bin_addrs(tcachebins[real_size], "tcachebins"): out = message.on("Free chunk (tcache)\n") + out if not verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) else: out = message.hint("Allocated chunk\n") + out if verbose: out += message.system("fd: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd'))) out += message.system("bk: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk'))) out += message.system("fd_nextsize: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('fd_nextsize'))) out += message.system("bk_nextsize: ") + "0x{:02x}\n".format( pwndbg.memory.u(cursor + allocator.chunk_key_offset('bk_nextsize'))) print(out) cursor += real_size # Avoid an infinite loop when a chunk's size is 0. if cursor == old_cursor: break