diff --git a/gef.py b/gef.py index 5a7977a51..1c64f8209 100644 --- a/gef.py +++ b/gef.py @@ -693,10 +693,57 @@ def size(self) -> int: raise AttributeError return self.page_end - self.page_start + def _search_for_realpath_without_versions(self, path: pathlib.Path) -> Optional[str]: + """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) -> Optional[str]: + """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) + if candidate1.is_file(): + 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 + # the /usr prefix. + if self.path.startswith("/usr"): + candidate = gef.session.remote.root / remote_path.relative_to("/usr") + if candidate.is_file(): + 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: # 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}, " @@ -9372,20 +9419,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 [] diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index e5cb60e56..8a448a5af 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -151,3 +151,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() + break