Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix remote use of gef.memory.maps #1109

Merged
merged 2 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,10 +689,57 @@ def size(self) -> int:
raise AttributeError
return self.page_end - self.page_start

def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str:
hugsy marked this conversation as resolved.
Show resolved Hide resolved
"""Given a path, search for a file that exists without numeric suffixes."""

# Match the path string against a regex that will remove a suffix
# consisting of a dot followed by numbers.
candidate = re.match(r"^(.*)\.(\d*)$", str(path))
while candidate:
candidate = candidate.group(1)
# If the prefix from the regex match is a file, return that path.
if pathlib.Path(candidate).is_file():
return candidate
# Otherwise, try to match again.
candidate = re.match(r"^(.*)\.(\d*)$", candidate)
return None

def _search_for_realpath(self) -> str:
hugsy marked this conversation as resolved.
Show resolved Hide resolved
"""This function is a workaround for gdb bug #23764

path might be wrong for remote sessions, so try a simple search for files
that aren't found at the path indicated, which should be canonical.
"""

assert gef.session.remote
remote_path = pathlib.Path(self.path)
# First, try the canonical path in the remote session root.
candidate1 = gef.session.remote.root / remote_path.relative_to(remote_path.anchor)
hugsy marked this conversation as resolved.
Show resolved Hide resolved
if candidate1.is_file():
hugsy marked this conversation as resolved.
Show resolved Hide resolved
return str(candidate1)
# Also try that path without version suffixes.
candidate = self._search_for_realpath_without_versions(candidate1)
if candidate:
return candidate

# On some systems, /lib(64) might be a symlink to /usr/lib(64), so try removing
gordonmessmer marked this conversation as resolved.
Show resolved Hide resolved
# the /usr prefix.
if self.path.startswith("/usr"):
candidate = gef.session.remote.root / remote_path.relative_to("/usr")
if candidate.is_file():
gordonmessmer marked this conversation as resolved.
Show resolved Hide resolved
return str(candidate)
# Also try that path without version suffixes.
candidate = self._search_for_realpath_without_versions(candidate)
if candidate:
return candidate

# Base case, return the original realpath
return str(candidate1)

@property
def realpath(self) -> str:
gordonmessmer marked this conversation as resolved.
Show resolved Hide resolved
# when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
return self.path if gef.session.remote is None else f"/tmp/gef/{gef.session.remote:d}/{self.path}"
return self.path if gef.session.remote is None else self._search_for_realpath()

def __str__(self) -> str:
return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, "
Expand Down Expand Up @@ -9293,20 +9340,18 @@ def build_line(self, name: str, color: str, address_val: int, got_address: int)
@parse_arguments({"symbols": [""]}, {"--all": False})
def do_invoke(self, _: List[str], **kwargs: Any) -> None:
args : argparse.Namespace = kwargs["arguments"]
if args.all:
vmmap = gef.memory.maps
mapfiles = set(mapfile.path for mapfile in vmmap if
pathlib.Path(mapfile.realpath).is_file() and
mapfile.permission & Permission.EXECUTE)
for mapfile in mapfiles:
self.print_got_for(mapfile, args.symbols)
else:
self.print_got_for(str(gef.session.file), args.symbols)

def print_got_for(self, file: str, argv: List[str]) -> None:
vmmap = gef.memory.maps
mapfiles = [mapfile for mapfile in vmmap if
(args.all or mapfile.path == str(gef.session.file)) and
pathlib.Path(mapfile.realpath).is_file() and
mapfile.permission & Permission.EXECUTE]
for mapfile in mapfiles:
self.print_got_for(mapfile.path, mapfile.realpath, args.symbols)

def print_got_for(self, file: str, realpath: str, argv: List[str]) -> None:
readelf = gef.session.constants["readelf"]

elf_file = file
elf_file = realpath
elf_virtual_path = file

func_names_filter = argv if argv else []
Expand Down
27 changes: 27 additions & 0 deletions tests/api/gef_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,30 @@ def test_func_parse_maps_remote_qemu(self):
gdb.execute(cmd)
sections = gef.memory.maps
assert len(sections) > 0

def test_func_parse_maps_realpath(self):
gef, gdb = self._gef, self._gdb
# When in a gef-remote session `parse_gdb_info_proc_maps` should work to
# query the memory maps
while True:
port = random.randint(1025, 65535)
if port != self._port:
break

with gdbserver_session(port=port) as _:
gdb.execute(f"gef-remote {GDBSERVER_DEFAULT_HOST} {port}")
gdb.execute("b main")
gdb.execute("continue")
sections = gef.memory.maps
assert len(sections) > 0
#
# Iterate over each of the maps, checking each path. If the path from
# the /proc/pid/maps file indicates a path that starts with /usr, but
# realpath does not include "/usr", then _search_for_realpath has
# probably corrected the path to account for gdb bug #23764
#
for section in sections:
if(section.is_executable() and section.path.startswith("/usr") and
"/usr" not in section.realpath):
assert pathlib.Path(section.realpath).is_file() is True
hugsy marked this conversation as resolved.
Show resolved Hide resolved
break
Loading