def canary(): global_canary, at_random = canary_value() if global_canary is None or at_random is None: print(message.error("Couldn't find AT_RANDOM - can't display canary.")) return print( message.notice( "AT_RANDOM = %#x # points to (not masked) global canary value" % at_random)) print( message.notice("Canary = 0x%x (may be incorrect on != glibc)" % global_canary)) stack_canaries = list( pwndbg.search.search(pwndbg.arch.pack(global_canary), mappings=pwndbg.stack.stacks.values())) if not stack_canaries: print(message.warn('No valid canaries found on the stacks.')) return print(message.success('Found valid canaries on the stacks:')) for stack_canary in stack_canaries: pwndbg.commands.telescope.telescope(address=stack_canary, count=1)
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. """ # 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')) # Break into the interactive debugger if debug: with pwndbg.stdio.stdio: pdb.post_mortem()
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 heap_freebins(addr=0x0602558): print(message.notice('Linked List')) # addr = 0x0602558 # addr = 0x060E360 print(' ' + hex(addr)) addr = pwndbg.memory.u64(addr) free = [] while addr and pwndbg.memory.peek(addr): free.append(addr) size = pwndbg.memory.u64(addr) in_use = size & 1 size &= ~3 linkedlist = (addr + 8 + size - 0x10) & pwndbg.arch.ptrmask try: bk = pwndbg.memory.u64(linkedlist) except: bk = None try: fd = pwndbg.memory.u64(linkedlist + 8) except: fd = None print(' %#x %#x %s' % (addr, size, '*' if in_use else '')) addr = bk print() return free
def sysroot(): cmd = 'set sysroot remote:/' if is_android(): if gdb.parameter('sysroot') == 'target:': gdb.execute(cmd) else: print(message.notice("sysroot is already set, skipping %r" % cmd))
def heap_allocations(addr, free): while addr and pwndbg.memory.peek(addr): size = pwndbg.memory.u64(addr) in_use = size & 1 flags = size & 3 done = not (size & 2) size &= ~3 if size > 0x1000: print(message.error("FOUND CORRUPTION OR END OF DATA")) data = '' if not in_use or addr in free: print( message.hint("%#016x - usersize=%#x - [FREE %i]" % (addr, size, flags))) linkedlist = (addr + 8 + size - 0x10) & pwndbg.arch.ptrmask if not pwndbg.memory.peek(linkedlist): print('Corrupted? (%#x)' % linkedlist) bk = pwndbg.memory.u64(linkedlist) fd = pwndbg.memory.u64(linkedlist + 8) print(" @ %#x" % linkedlist) print(" bk: %#x" % bk) print(" fd: %#x" % fd) else: print(message.notice("%#016x - usersize=%#x" % (addr, size))) pwndbg.commands.hexdump.hexdump(addr + 8, size) addr += size + 8 print()
def sysroot(): cmd = "set sysroot remote:/" if is_android(): if gdb.parameter("sysroot") == "target:": gdb.execute(cmd) else: print(message.notice("sysroot is already set, skipping %r" % cmd))
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 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" "PS: Pull requests are welcome"))
def canary(): global_canary, at_random = canary_value() if global_canary is None or at_random is None: print(message.error("Couldn't find AT_RANDOM - can't display canary.")) return print(message.notice("AT_RANDOM = %#x # points to (not masked) global canary value" % at_random)) print(message.notice("Canary = 0x%x" % global_canary)) stack_canaries = list( pwndbg.search.search(pwndbg.arch.pack(global_canary), mappings=pwndbg.stack.stacks.values()) ) if not stack_canaries: print(message.warn('No valid canaries found on the stacks.')) return print(message.success('Found valid canaries on the stacks:')) for stack_canary in stack_canaries: pwndbg.commands.telescope.telescope(address=stack_canary, count=1)
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 initial_hook(*a): if show_tip and not pwndbg.decorators.first_prompt: colored_tip = re.sub( "`(.*?)`", lambda s: message.warn(s.group()[1:-1]), get_tip_of_the_day() ) print( message.prompt("------- tip of the day") + message.system(" (disable with %s)" % message.notice("set show-tips off")) + message.prompt(" -------") ) print((colored_tip)) pwndbg.decorators.first_prompt = True prompt_hook(*a) gdb.prompt_hook = prompt_hook
import re import gdb import pwndbg.decorators import pwndbg.events import pwndbg.gdbutils import pwndbg.memoize from pwndbg.color import disable_colors from pwndbg.color import message from pwndbg.tips import get_tip_of_the_day funcs_list_str = ", ".join( message.notice("$" + f.name) for f in pwndbg.gdbutils.functions.functions ) hint_lines = ( "loaded %i commands. Type %s for a list." % (len(pwndbg.commands.commands), message.notice("pwndbg [filter]")), "created %s gdb functions (can be used with print/break)" % funcs_list_str, ) for line in hint_lines: print(message.prompt("pwndbg: ") + message.system(line)) # noinspection PyPackageRequirements show_tip = pwndbg.config.Parameter( "show-tips", True, "whether to display the tip of the day on startup" ) cur = None
def try_free(addr): addr = int(addr) # check hook free_hook = pwndbg.symbol.address('__free_hook') if free_hook is not None: if pwndbg.memory.pvoid(free_hook) != 0: message.success('__libc_free: will execute __free_hook') # free(0) has no effect if addr == 0: message.success('__libc_free: addr is 0, nothing to do') return # constants allocator = pwndbg.heap.current arena = allocator.get_arena() aligned_lsb = allocator.malloc_align_mask.bit_length() size_sz = allocator.size_sz malloc_alignment = allocator.malloc_alignment malloc_align_mask = allocator.malloc_align_mask chunk_minsize = allocator.minsize ptr_size = pwndbg.arch.ptrsize def unsigned_size(size): # read_chunk()['size'] is signed in pwndbg ;/ # there may be better way to handle that if ptr_size < 8: return ctypes.c_uint32(size).value x = ctypes.c_uint64(size).value return x def chunksize(chunk_size): # maybe move this to ptmalloc.py return chunk_size & (~7) def finalize(errors_found, returned_before_error): print('-' * 10) if returned_before_error: print(message.success('Free should succeed!')) elif errors_found > 0: print(message.error('Errors found!')) else: print(message.success('All checks passed!')) # mem2chunk addr -= 2 * size_sz # try to get the chunk try: chunk = read_chunk(addr) except gdb.MemoryError as e: print( message.error( 'Can\'t read chunk at address 0x{:x}, memory error'.format( addr))) return chunk_size = unsigned_size(chunk['size']) chunk_size_unmasked = chunksize(chunk_size) _, is_mmapped, _ = allocator.chunk_flags(chunk_size) if is_mmapped: print(message.notice('__libc_free: Doing munmap_chunk')) return errors_found = False returned_before_error = False # chunk doesn't overlap memory print(message.notice('General checks')) max_mem = (1 << (ptr_size * 8)) - 1 if addr + chunk_size >= max_mem: err = 'free(): invalid pointer -> &chunk + chunk->size > max memory\n' err += ' 0x{:x} + 0x{:x} > 0x{:x}' err = err.format(addr, chunk_size, max_mem) print(message.error(err)) errors_found += 1 # chunk address is aligned addr_tmp = addr if malloc_alignment != 2 * size_sz: addr_tmp = addr + 2 * size_sz if addr_tmp & malloc_align_mask != 0: err = 'free(): invalid pointer -> misaligned chunk\n' err += ' LSB of 0x{:x} are 0b{}, should be 0b{}' if addr_tmp != addr: err += ' (0x{:x} was added to the address)'.format(2 * size_sz) err = err.format(addr_tmp, bin(addr_tmp)[-aligned_lsb:], '0' * aligned_lsb) print(message.error(err)) errors_found += 1 # chunk's size is big enough if chunk_size_unmasked < chunk_minsize: err = 'free(): invalid size -> chunk\'s size smaller than MINSIZE\n' err += ' size is 0x{:x}, MINSIZE is 0x{:x}' err = err.format(chunk_size_unmasked, chunk_minsize) print(message.error(err)) errors_found += 1 # chunk's size is aligned if chunk_size_unmasked & malloc_align_mask != 0: err = 'free(): invalid size -> chunk\'s size is not aligned\n' err += ' LSB of size 0x{:x} are 0b{}, should be 0b{}' err = err.format(chunk_size_unmasked, bin(chunk_size_unmasked)[-aligned_lsb:], '0' * aligned_lsb) print(message.error(err)) errors_found += 1 # tcache if allocator.has_tcache() and 'key' in allocator.tcache_entry.keys(): tc_idx = (chunk_size_unmasked - chunk_minsize + malloc_alignment - 1) // malloc_alignment if tc_idx < allocator.mp['tcache_bins']: print(message.notice('Tcache checks')) e = addr + 2 * size_sz e += allocator.tcache_entry.keys().index('key') * ptr_size e = pwndbg.memory.pvoid(e) tcache_addr = int(allocator.thread_cache.address) if e == tcache_addr: # todo, actually do checks print( message.error( 'Will do checks for tcache double-free (memory_tcache_double_free)' )) errors_found += 1 if int(allocator.get_tcache()['counts'][tc_idx]) < int( allocator.mp['tcache_count']): print(message.success('Using tcache_put')) if errors_found == 0: returned_before_error = True if errors_found > 0: finalize(errors_found, returned_before_error) return # is fastbin if chunk_size_unmasked <= allocator.global_max_fast: print(message.notice('Fastbin checks')) chunk_fastbin_idx = allocator.fastbin_index(chunk_size_unmasked) fastbin_list = allocator.fastbins(int( arena.address))[(chunk_fastbin_idx + 2) * (ptr_size * 2)] try: next_chunk = read_chunk(addr + chunk_size_unmasked) except gdb.MemoryError as e: print( message.error( 'Can\'t read next chunk at address 0x{:x}, memory error'. format(chunk + chunk_size_unmasked))) finalize(errors_found, returned_before_error) return # next chunk's size is big enough and small enough next_chunk_size = unsigned_size(next_chunk['size']) if next_chunk_size <= 2 * size_sz or chunksize(next_chunk_size) >= int( arena['system_mem']): err = 'free(): invalid next size (fast) -> next chunk\'s size not in [2*size_sz; av->system_mem]\n' err += ' next chunk\'s size is 0x{:x}, 2*size_sz is 0x{:x}, system_mem is 0x{:x}' err = err.format(next_chunk_size, 2 * size_sz, int(arena['system_mem'])) print(message.error(err)) errors_found += 1 # chunk is not the same as the one on top of fastbin[idx] if int(fastbin_list[0]) == addr: err = 'double free or corruption (fasttop) -> chunk already is on top of fastbin list\n' err += ' fastbin idx == {}' err = err.format(chunk_fastbin_idx) print(message.error(err)) errors_found += 1 # chunk's size is ~same as top chunk's size fastbin_top_chunk = int(fastbin_list[0]) if fastbin_top_chunk != 0: try: fastbin_top_chunk = read_chunk(fastbin_top_chunk) except gdb.MemoryError as e: print( message.error( 'Can\'t read top fastbin chunk at address 0x{:x}, memory error' .format(fastbin_top_chunk))) finalize(errors_found, returned_before_error) return fastbin_top_chunk_size = chunksize( unsigned_size(fastbin_top_chunk['size'])) if chunk_fastbin_idx != allocator.fastbin_index( fastbin_top_chunk_size): err = 'invalid fastbin entry (free) -> chunk\'s size is not near top chunk\'s size\n' err += ' chunk\'s size == {}, idx == {}\n' err += ' top chunk\'s size == {}, idx == {}' err += ' if `have_lock` is false then the error is invalid' err = err.format( chunk['size'], chunk_fastbin_idx, fastbin_top_chunk_size, allocator.fastbin_index(fastbin_top_chunk_size)) print(message.error(err)) errors_found += 1 # is not mapped elif is_mmapped == 0: print(message.notice('Not mapped checks')) # chunks is not top chunk if addr == int(arena['top']): err = 'double free or corruption (top) -> chunk is top chunk' print(message.error(err)) errors_found += 1 # next chunk is not beyond the boundaries of the arena NONCONTIGUOUS_BIT = 2 top_chunk_addr = (int(arena['top'])) top_chunk = read_chunk(top_chunk_addr) next_chunk_addr = addr + chunk_size_unmasked # todo: in libc, addition may overflow if (arena['flags'] & NONCONTIGUOUS_BIT == 0) and next_chunk_addr >= top_chunk_addr + chunksize( top_chunk['size']): err = 'double free or corruption (out) -> next chunk is beyond arena and arena is contiguous\n' err += 'next chunk at 0x{:x}, end of arena at 0x{:x}' err = err.format( next_chunk_addr, top_chunk_addr + chunksize(unsigned_size(top_chunk['size']))) print(message.error(err)) errors_found += 1 # now we need to dereference chunk try: next_chunk = read_chunk(next_chunk_addr) next_chunk_size = chunksize(unsigned_size(next_chunk['size'])) except (OverflowError, gdb.MemoryError) as e: print( message.error( 'Can\'t read next chunk at address 0x{:x}'.format( next_chunk_addr))) finalize(errors_found, returned_before_error) return # next chunk's P bit is set prev_inuse, _, _ = allocator.chunk_flags(next_chunk['size']) if prev_inuse == 0: err = 'double free or corruption (!prev) -> next chunk\'s previous-in-use bit is 0\n' print(message.error(err)) errors_found += 1 # next chunk's size is big enough and small enough if next_chunk_size <= 2 * size_sz or next_chunk_size >= int( arena['system_mem']): err = 'free(): invalid next size (normal) -> next chunk\'s size not in [2*size_sz; system_mem]\n' err += 'next chunk\'s size is 0x{:x}, 2*size_sz is 0x{:x}, system_mem is 0x{:x}' err = err.format(next_chunk_size, 2 * size_sz, int(arena['system_mem'])) print(message.error(err)) errors_found += 1 # consolidate backward prev_inuse, _, _ = allocator.chunk_flags(chunk['size']) if prev_inuse == 0: print(message.notice('Backward consolidation')) prev_size = chunksize(unsigned_size(chunk['prev_size'])) prev_chunk_addr = addr - prev_size try: prev_chunk = read_chunk(prev_chunk_addr) prev_chunk_size = chunksize(unsigned_size(prev_chunk['size'])) except (OverflowError, gdb.MemoryError) as e: print( message.error( 'Can\'t read next chunk at address 0x{:x}'.format( prev_chunk_addr))) finalize(errors_found, returned_before_error) return if unsigned_size(prev_chunk['size']) != prev_size: err = 'corrupted size vs. prev_size while consolidating\n' err += 'prev_size field is 0x{:x}, prev chunk at 0x{:x}, prev chunk size is 0x{:x}' err = err.format(prev_size, prev_chunk_addr, unsigned_size(prev_chunk['size'])) print(message.error(err)) errors_found += 1 else: addr = prev_chunk_addr chunk_size += prev_size chunk_size_unmasked += prev_size try_unlink(addr) # consolidate forward if next_chunk_addr != top_chunk_addr: print(message.notice('Next chunk is not top chunk')) try: next_next_chunk_addr = next_chunk_addr + next_chunk_size next_next_chunk = read_chunk(next_next_chunk_addr) except (OverflowError, gdb.MemoryError) as e: print( message.error( 'Can\'t read next chunk at address 0x{:x}'.format( next_next_chunk_addr))) finalize(errors_found, returned_before_error) return prev_inuse, _, _ = allocator.chunk_flags(next_next_chunk['size']) if prev_inuse == 0: print(message.notice('Forward consolidation')) try_unlink(next_chunk_addr) chunk_size += next_chunk_size chunk_size_unmasked += next_chunk_size else: print(message.notice('Clearing next chunk\'s P bit')) # unsorted bin fd->bk should be unsorted bean unsorted_addr = int(arena['bins'][0]) try: unsorted = read_chunk(unsorted_addr) try: if read_chunk(unsorted['fd'])['bk'] != unsorted_addr: err = 'free(): corrupted unsorted chunks -> unsorted_chunk->fd->bk != unsorted_chunk\n' err += 'unsorted at 0x{:x}, unsorted->fd == 0x{:x}, unsorted->fd->bk == 0x{:x}' err = err.format(unsorted_addr, unsorted['fd'], read_chunk(unsorted['fd'])['bk']) print(message.error(err)) errors_found += 1 except (OverflowError, gdb.MemoryError) as e: print( message.error( 'Can\'t read chunk at 0x{:x}, it is unsorted bin fd' .format(unsorted['fd']))) errors_found += 1 except (OverflowError, gdb.MemoryError) as e: print( message.error( 'Can\'t read unsorted bin chunk at 0x{:x}'.format( unsorted_addr))) errors_found += 1 else: print(message.notice('Next chunk is top chunk')) chunk_size += next_chunk_size chunk_size_unmasked += next_chunk_size # todo: this may vary strongly FASTBIN_CONSOLIDATION_THRESHOLD = 65536 if chunk_size_unmasked >= FASTBIN_CONSOLIDATION_THRESHOLD: print( message.notice( 'Doing malloc_consolidate and systrim/heap_trim')) #is mapped else: message.notice('Doing munmap_chunk') finalize(errors_found, returned_before_error)
import gdb import pwndbg.decorators import pwndbg.events import pwndbg.gdbutils import pwndbg.memoize from pwndbg.color import disable_colors from pwndbg.color import message funcs_list_str = ', '.join( message.notice('$' + f.name) for f in pwndbg.gdbutils.functions.functions) hint_lines = ( 'loaded %i commands. Type %s for a list.' % (len(pwndbg.commands.commands), message.notice('pwndbg [filter]')), 'created %s gdb functions (can be used with print/break)' % funcs_list_str) for line in hint_lines: print(message.prompt('pwndbg: ') + message.system(line)) cur = None def prompt_hook(*a): global cur pwndbg.decorators.first_prompt = True new = (gdb.selected_inferior(), gdb.selected_thread()) if cur != new: pwndbg.events.after_reload(start=cur is None)
#!/usr/bin/env python # -*- coding: utf-8 -*- import gdb import pwndbg.decorators import pwndbg.events import pwndbg.gdbutils import pwndbg.memoize from pwndbg.color import disable_colors from pwndbg.color import message funcs_list_str = ', '.join(message.notice('$' + f.name) for f in pwndbg.gdbutils.functions.functions) hint_lines = ( 'loaded %i commands. Type %s for a list.' % (len(pwndbg.commands.commands), message.notice('pwndbg [filter]')), 'created %s gdb functions (can be used with print/break)' % funcs_list_str ) for line in hint_lines: print(message.prompt('pwndbg: ') + message.system(line)) cur = (gdb.selected_inferior(), gdb.selected_thread()) def prompt_hook(*a): global cur pwndbg.decorators.first_prompt = True new = (gdb.selected_inferior(), gdb.selected_thread())