From 9ee31c45623db4169fa13be17013e76f8ffe0682 Mon Sep 17 00:00:00 2001 From: Gordon Messmer Date: Sun, 26 May 2024 22:53:27 -0700 Subject: [PATCH 1/2] Fix the Section.realpath property in remote debugging sessions. --- gef.py | 71 +++++++++++++++++++++++++++++++++-------- tests/api/gef_memory.py | 27 ++++++++++++++++ 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/gef.py b/gef.py index 11a466fd4..d0d46563d 100644 --- a/gef.py +++ b/gef.py @@ -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: + """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: + """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}, " @@ -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 [] diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index 19d2d350b..c83cef02c 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -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 + break From 6e5a96dfef5f41ebba7d09005c825c9e0f370fab Mon Sep 17 00:00:00 2001 From: Gordon Messmer Date: Sat, 27 Jul 2024 14:44:31 -0700 Subject: [PATCH 2/2] Review feedback. --- gef.py | 4 ++-- tests/api/gef_memory.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gef.py b/gef.py index d0d46563d..da3f624ab 100644 --- a/gef.py +++ b/gef.py @@ -689,7 +689,7 @@ def size(self) -> int: raise AttributeError return self.page_end - self.page_start - def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str: + 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 @@ -704,7 +704,7 @@ def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str: candidate = re.match(r"^(.*)\.(\d*)$", candidate) return None - def _search_for_realpath(self) -> str: + 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 diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index c83cef02c..08e2e6913 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -174,5 +174,5 @@ def test_func_parse_maps_realpath(self): 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 + assert pathlib.Path(section.realpath).is_file() break