diff --git a/pwndbg/abi.py b/pwndbg/abi.py index 1d2cd56cc..4f2a5bb88 100644 --- a/pwndbg/abi.py +++ b/pwndbg/abi.py @@ -10,6 +10,7 @@ import re import gdb import pwndbg.arch +import pwndbg.color.message as M class ABI(object): @@ -136,6 +137,17 @@ def update(): linux = 'Linux' in abi + if not linux: + msg = M.warn( + "The bare metal debugging is enabled since the gdb's osabi is '%s' which is not 'GNU/Linux'.\n" + "Ex. the page resolving and memory de-referencing ONLY works on known pages.\n" + "This option is based ib gdb client compile arguments (by default) and will be corrected if you load an ELF which has the '.note.ABI-tag' section.\n" + "If you are debuging a program that runs on Linux ABI, please select the correct gdb client." + % abi + ) + print(msg) + + def LinuxOnly(default=None): """Create a decorator that the function will be called when ABI is Linux. Otherwise, return `default`. @@ -150,3 +162,7 @@ def LinuxOnly(default=None): return caller return decorator + + +# Update when starting the gdb to show warning message for non-Linux ABI user. +update() diff --git a/pwndbg/chain.py b/pwndbg/chain.py index ad17d9b83..0f485e22d 100755 --- a/pwndbg/chain.py +++ b/pwndbg/chain.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import gdb +import pwndbg.abi import pwndbg.color.chain as C import pwndbg.color.memory as M import pwndbg.color.theme as theme @@ -20,7 +21,7 @@ LIMIT = pwndbg.config.Parameter('dereference-limit', 5, 'max number of pointers def get(address, limit=LIMIT, offset=0, hard_stop=None, hard_end=0): """ - Recursively dereferences an address. + Recursively dereferences an address. For bare metal, it will stop when the address is not in any of vmmap pages to avoid redundant dereference. Arguments: address(int): the first address to begin dereferencing @@ -33,7 +34,7 @@ def get(address, limit=LIMIT, offset=0, hard_stop=None, hard_end=0): A list representing pointers of each ```address``` and reference """ limit = int(limit) - + result = [address] for i in range(limit): # Don't follow cycles, except to stop at the second occurrence. @@ -45,7 +46,14 @@ def get(address, limit=LIMIT, offset=0, hard_stop=None, hard_end=0): break try: - address = int(pwndbg.memory.poi(pwndbg.typeinfo.ppvoid, address + offset)) + address = address + offset + + # Avoid redundant dereferences in bare metal mode by checking + # if address is in any of vmmap pages + if not pwndbg.abi.linux and not pwndbg.vmmap.find(address): + break + + address = int(pwndbg.memory.poi(pwndbg.typeinfo.ppvoid, address)) address &= pwndbg.arch.ptrmask result.append(address) except gdb.MemoryError: diff --git a/pwndbg/commands/vmmap.py b/pwndbg/commands/vmmap.py index a8d4f8b23..bc1e7f219 100644 --- a/pwndbg/commands/vmmap.py +++ b/pwndbg/commands/vmmap.py @@ -12,10 +12,13 @@ import argparse import gdb import six +from elftools.elf.constants import SH_FLAGS +from elftools.elf.elffile import ELFFile import pwndbg.color.memory as M import pwndbg.commands import pwndbg.compat +import pwndbg.elf import pwndbg.vmmap @@ -54,3 +57,83 @@ def vmmap(pages_filter=None): print(M.legend()) for page in pages: print(M.get(page.vaddr, text=str(page))) + + +parser = argparse.ArgumentParser() +parser.description = 'Add Print virtual memory map page.' +parser.add_argument('start', help='Starting virtual address') +parser.add_argument('size', help='Size of the address space, in bytes') +parser.add_argument('flags', nargs='?', type=str, default='', help='Flags set by the ELF file, see PF_X, PF_R, PF_W') +parser.add_argument('offset', nargs='?', default=0, help='Offset into the original ELF file that the data is loaded from') + +@pwndbg.commands.ArgparsedCommand(parser) +def vmmap_add(start, size, flags, offset): + page_flags = { + 'r': pwndbg.elf.PF_R, + 'w': pwndbg.elf.PF_W, + 'x': pwndbg.elf.PF_X, + } + perm = 0 + for flag in flags: + flag_val = page_flags.get(flag, None) + if flag_val is None: + print('Invalid page flag "%s"', flag) + return + perm |= flag_val + + page = pwndbg.memory.Page(start, size, perm, offset) + pwndbg.vmmap.add_custom_page(page) + + print('%r added' % page) + + +@pwndbg.commands.ParsedCommand +def vmmap_clear(): + pwndbg.vmmap.clear_custom_page() + + +parser = argparse.ArgumentParser() +parser.description = 'Load virtual memory map pages from ELF file.' +parser.add_argument('filename', nargs='?', type=str, help='ELF filename, by default uses current loaded filename.') + +@pwndbg.commands.ArgparsedCommand(parser) +def vmmap_load(filename): + if filename is None: + filename = pwndbg.proc.exe + + print('Load "%s" ...' % filename) + + # TODO: Add an argument to let use to choose loading the page information from sections or segments + + # Use section information to recover the segment information. + # The entry point of bare metal enviroment is often at the first segment. + # For example, assume the entry point is at 0x8000. + # In most of case, link will create a segment and starts from 0x0. + # This cause all values less than 0x8000 be considered as a valid pointer. + pages = [] + with open(filename, 'rb') as f: + elffile = ELFFile(f) + + for section in elffile.iter_sections(): + vaddr = section['sh_addr'] + memsz = section['sh_size'] + sh_flags = section['sh_flags'] + offset = section['sh_offset'] + + # Don't add the sections that aren't mapped into memory + if not sh_flags & SH_FLAGS.SHF_ALLOC: + continue + + # Guess the segment flags from section flags + flags = pwndbg.elf.PF_R + if sh_flags & SH_FLAGS.SHF_WRITE: + flags |= pwndbg.elf.PF_W + if sh_flags & SH_FLAGS.SHF_EXECINSTR: + flags |= pwndbg.elf.PF_X + + page = pwndbg.memory.Page(vaddr, memsz, flags, offset, filename) + pages.append(page) + + for page in pages: + pwndbg.vmmap.add_custom_page(page) + print('%r added' % page) diff --git a/pwndbg/elf.py b/pwndbg/elf.py index f9d15c0a5..bc7f76644 100644 --- a/pwndbg/elf.py +++ b/pwndbg/elf.py @@ -173,14 +173,12 @@ def get_ehdr(pointer): # the ELF header. base = pwndbg.memory.page_align(pointer) - # XXX: for non linux ABI, the ELF header may not be found in memory. + # For non linux ABI, the ELF header may not be found in memory. # This will hang the gdb when using the remote gdbserver to scan 1024 pages - if not pwndbg.abi.linux: - return None, None - base = find_elf_magic(pointer, search_down=True) if base is None: - print("ERROR: Could not find ELF base!") + if pwndbg.abi.linux: + print("ERROR: Could not find ELF base!") return None, None # Determine whether it's 32- or 64-bit diff --git a/pwndbg/stack.py b/pwndbg/stack.py index ed0787256..f0cff21af 100644 --- a/pwndbg/stack.py +++ b/pwndbg/stack.py @@ -45,6 +45,11 @@ def find(address): def find_upper_stack_boundary(addr, max_pages=1024): addr = pwndbg.memory.page_align(int(addr)) + # We can't get the stack size from stack layout and page fault on bare metal mode, + # so we return current page as a walkaround. + if not pwndbg.abi.linux: + return addr + pwndbg.memory.PAGE_SIZE + return pwndbg.elf.find_elf_magic(addr, max_pages=max_pages, ret_addr_anyway=True) diff --git a/pwndbg/vmmap.py b/pwndbg/vmmap.py index 09fdfd8f1..0bc17c3e5 100644 --- a/pwndbg/vmmap.py +++ b/pwndbg/vmmap.py @@ -12,6 +12,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import bisect import os import sys @@ -35,6 +36,9 @@ import pwndbg.typeinfo # by analyzing the stack or register context. explored_pages = [] +# List of custom pages that can be managed manually by vmmap_* commands family +custom_pages = [] + @pwndbg.events.new_objfile @pwndbg.memoize.reset_on_stop def get(): @@ -50,6 +54,7 @@ def get(): pages.extend(pwndbg.stack.stacks.values()) pages.extend(explored_pages) + pages.extend(custom_pages) pages.sort() return tuple(pages) @@ -114,6 +119,26 @@ def clear_explored_pages(): while explored_pages: explored_pages.pop() + +def add_custom_page(page): + bisect.insort(custom_pages, page) + + # Reset all the cache + # We can not reset get() only, since the result may be used by others. + # TODO: avoid flush all caches + pwndbg.memoize.reset() + + +def clear_custom_page(): + while custom_pages: + custom_pages.pop() + + # Reset all the cache + # We can not reset get() only, since the result may be used by others. + # TODO: avoid flush all caches + pwndbg.memoize.reset() + + @pwndbg.memoize.reset_on_stop def proc_pid_maps(): """ diff --git a/tests/testLoadsWithoutCrashing.py b/tests/testLoadsWithoutCrashing.py index 8546ecace..c96515818 100644 --- a/tests/testLoadsWithoutCrashing.py +++ b/tests/testLoadsWithoutCrashing.py @@ -17,5 +17,5 @@ def escape_ansi(line): def test_loads_wivout_crashing_bruv(): output = escape_ansi(common.run_gdb_with_script()) - assert ('pwndbg: loaded 156 commands. Type pwndbg [filter] for a list.\n' + assert ('pwndbg: loaded 159 commands. Type pwndbg [filter] for a list.\n' 'pwndbg: created $rebase, $ida gdb functions (can be used with print/break)') in output, output