def instruction(ins): asm = '%-06s %s' % (ins.mnemonic, ins.op_str) if pwndbg.config.syntax_highlight: asm = syntax_highlight(asm) is_branch = set(ins.groups) & capstone_branch_groups # Highlight the current line if enabled if pwndbg.config.highlight_pc and ins.address == pwndbg.regs.pc: asm = C.highlight(asm) # tl;dr is a branch? if ins.target not in (None, ins.address + ins.size): sym = pwndbg.symbol.get(ins.target) or None target = M.get(ins.target) const = ins.target_const hextarget = hex(ins.target) hexlen = len(hextarget) # If it's a constant expression, color it directly in the asm. if const: asm = '%s <%s>' % (ljust_colored(asm, 36), target) asm = asm.replace(hex(ins.target), sym or target) # It's not a constant expression, but we've calculated the target # address by emulation or other means (for example showing ret instruction target) elif sym: asm = '%s <%s; %s>' % (ljust_colored(asm, 36), target, sym) # We were able to calculate the target, but there is no symbol # name for it. else: asm += '<%s>' % (target) # not a branch elif ins.symbol: if is_branch and not ins.target: asm = '%s <%s>' % (asm, ins.symbol) # XXX: not sure when this ever happens asm += '<-- file a pwndbg bug for this' else: inlined_sym = asm.replace(hex(ins.symbol_addr), ins.symbol) # display symbol as mem text if no inline replacement was made mem_text = ins.symbol if inlined_sym == asm else None asm = '%s <%s>' % (ljust_colored(inlined_sym, 36), M.get(ins.symbol_addr, mem_text)) # Style the instruction mnemonic if it's a branch instruction. if is_branch: asm = asm.replace(ins.mnemonic, branch(ins.mnemonic), 1) # If we know the conditional is taken, mark it as taken. if ins.condition is None: asm = ' ' + asm elif ins.condition: asm = on('✔ ') + asm else: asm = ' ' + asm return asm
def memoize(): pwndbg.memoize.memoize.caching = not pwndbg.memoize.memoize.caching status = message.off( 'OFF (pwndbg will work slower, use only for debugging pwndbg)') if pwndbg.memoize.memoize.caching: status = message.on('ON') print("Caching is now %s" % status)
def aslr(state=None): if state: gdb.execute('set disable-randomization %s' % options[state], from_tty=False, to_string=True) if pwndbg.proc.alive: print("Change will take effect when the process restarts") aslr, method = pwndbg.vmmap.check_aslr() status = message.off('OFF') if aslr: status = message.on('ON') print("ASLR is %s (%s)" % (status, method))
def attachp(target): try: resolved_target = int(target) except ValueError: # GDB supposedly supports device files, so let's try it here...: # <disconnect3d> hey, does anyone know what does `attach <device-file>` do? # <disconnect3d> is this an alias for `target extended /dev/ttyACM0` or similar? # <disconnect3d> I mean, `help attach` suggests that the `attach` command supports a device file target... # <simark> I had no idea # <simark> what you pass to attach is passed directly to target_ops::attach # <simark> so it must be very target-specific # <disconnect3d> how can it be target specific if it should attach you to a target? # <disconnect3d> or do you mean osabi/arch etc? # <simark> in "attach foo", foo is interpreted by the target you are connected to # <simark> But all targets I can find interpret foo as a PID # <simark> So it might be that old targets had some other working mode if _is_device(target): resolved_target = target else: try: pids = check_output(["pidof", target]).decode().rstrip("\n").split(" ") except FileNotFoundError: print(message.error("Error: did not find `pidof` command")) return except CalledProcessError: pids = [] if not pids: print(message.error("Process %s not found" % target)) return if len(pids) > 1: print( message.warn("Found pids: %s (use `attach <pid>`)" % ", ".join(pids))) return resolved_target = int(pids[0]) print(message.on("Attaching to %s" % resolved_target)) try: gdb.execute("attach %s" % resolved_target) except gdb.error as e: print(message.error("Error: %s" % e))
def aslr(state=None): """ Check the current ASLR status, or turn it on/off. Does not take effect until the program is restarted. """ if state: gdb.execute('set disable-randomization %s' % options[state], from_tty=False, to_string=True) if pwndbg.proc.alive: print("Change will take effect when the process restarts") aslr = pwndbg.vmmap.check_aslr() status = message.off('OFF') if aslr: status = message.on('ON') print("ASLR is %s" % status)
def instruction(ins): asm = '%-06s %s' % (ins.mnemonic, ins.op_str) if pwndbg.config.syntax_highlight: asm = syntax_highlight(asm) is_branch = set(ins.groups) & capstone_branch_groups # Highlight the current line if enabled if pwndbg.config.highlight_pc and ins.address == pwndbg.regs.pc: asm = C.highlight(asm) # tl;dr is a branch? if ins.target not in (None, ins.address + ins.size): sym = pwndbg.symbol.get(ins.target) or None target = M.get(ins.target) const = ins.target_const # Use format string instead of hex() to avoid suffix 'l' or 'L' hextarget = '0x%x' % ins.target hexlen = len(hextarget) # If it's a constant expression, color it directly in the asm. if const: # TODO: Also colorize the address starts with '$' and '#' asm = asm.replace(hextarget, sym or target) if sym: asm = '%s <%s>' % (ljust_colored(asm, 36), target) # It's not a constant expression, but we've calculated the target # address by emulation. elif sym: asm = '%s <%s; %s>' % (ljust_colored(asm, 36), target, sym) # We were able to calculate the target, but there is no symbol # name for it. else: asm += '<%s>' % (target) # not a branch elif ins.symbol: if is_branch and not ins.target: asm = '%s <%s>' % (asm, ins.symbol) # XXX: not sure when this ever happens asm += '<-- file a pwndbg bug for this' else: asm = asm.replace(hex(ins.symbol_addr), ins.symbol) asm = '%s <%s>' % (ljust_colored(asm, 36), M.get(ins.symbol_addr)) # Style the instruction mnemonic if it's a branch instruction. if is_branch: asm = asm.replace(ins.mnemonic, branch(ins.mnemonic), 1) # If we know the conditional is taken, mark it as taken. if ins.condition is None: asm = ' ' + asm elif ins.condition: asm = on('✔ ') + asm else: asm = ' ' + asm return asm
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 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