Make de-reference only works on known pages in bare metal mode and add commands to manually add pages (#385)

* Make chain.get() to check vmmap first in bare metal mode

Make chain.get() limit to de-reference within the known page in
bare metal mode.
Since the address are all valid when mmu is not enable and all
the value are valid physical address. It will be de-referenced
even these addresses are not used and actually, it is data in
the most of case. Ex. 0x1 often means the value 1, not the
address 0x1.

Also, for issue #371, some addresses may be the MMIO registers.
The read operation on these address will break the state.
It is better to limit the de-reference address range. This patch
will also fix it, hopefully.

* Add custom vmmap add/del API in vmmap.py

In some cases, ex. bare metal, the pages information can not be
detected automatically. Also, the most of pwndbg feature rely on
page information such as highlighting.
User may want to create page information manually and maintain it
by himself.

This commit add python APIs to manually add/del page information
and they are isolated.

* Fix stack page detection in bare metal mode

We can not detect the stack page size in bare metal mode by
1. finding the ELF location after the stack page
2. page fault

A simple workaround is returning the current $sp page
and assume it is the stack page.

* Add vmmap control command to add/del customized vmmap

In some cases, ex. bare metal, the pages information can not be
detected automatically. Also, the most of pwndbg feature rely on
page information such as highlighting.
User may want to create page information manually and maintain it
by himself.

I add few commands to make user can add/del pages and load page
information from ELF sections.

* Fix the command amount for auto test to pass CI

* Add warning message

* Fix descriptions

* Fix cache issue and use bisect in insert API

* Keep LinuxOnly in find_elf_magic

* remove XXX
pull/399/head
Meng-Huan Yu 8 years ago committed by Disconnect3d
parent 103b1def61
commit e0c02a4484

@ -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()

@ -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:

@ -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)

@ -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

@ -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)

@ -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():
"""

@ -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

Loading…
Cancel
Save