From 115137cc05d6c90f589f0840009b116c633cb48a Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Wed, 25 Sep 2024 21:27:55 -0700 Subject: [PATCH 1/5] Move GEF to a Python 3.10 baseline (#1133) ## Description Move GEF to a Python 3.10 baseline Fixes #1132 --------- Co-authored-by: Grazfather --- .github/workflows/tests.yml | 2 +- .pylintrc | 2 +- .python-version | 1 + README.md | 2 +- docs/compat.md | 8 +++----- docs/faq.md | 16 ++++++++-------- docs/install.md | 7 +++---- gef.py | 7 ++++++- 8 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 .python-version diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 34286663e..416626e93 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - runner: [ubuntu-24.04, ubuntu-22.04, ubuntu-20.04] + runner: [ubuntu-24.04, ubuntu-22.04] name: "Run Unit tests on ${{ matrix.runner }}" runs-on: ${{ matrix.runner }} diff --git a/.pylintrc b/.pylintrc index 1a9979b4e..4261939ba 100644 --- a/.pylintrc +++ b/.pylintrc @@ -52,7 +52,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.6 +py-version=3.10 # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..1445aee86 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.14 diff --git a/README.md b/README.md index 8f3d3e5c7..4189c63a5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ from the debugging runtime. ## Instant Setup -Simply make sure you have [GDB 8.0 or higher](https://www.gnu.org/s/gdb) compiled with Python3.6+ +Simply make sure you have [GDB 10.0 or higher](https://www.gnu.org/s/gdb) compiled with Python3.10+ bindings, then: ```bash diff --git a/docs/compat.md b/docs/compat.md index 813000d4f..9e94ef718 100644 --- a/docs/compat.md +++ b/docs/compat.md @@ -2,11 +2,9 @@ This matrix indicates the version of Python and/or GDB -| GEF version | GDB Python compatibility* | Python compatibility** | +| GEF version | GDB Python compatibility | Python compatibility | |:--:|:--:|:--:| | [2018.02](https://github.com/hugsy/gef/releases/tag/2018.02) | 7.2 | Python 2.7, Python 3.4+ | | [2020.03](https://github.com/hugsy/gef/releases/tag/2020.03) | 7.4 | Python 2.7, Python 3.4+ | -| [2022.01](https://github.com/hugsy/gef/releases/tag/2021.01) | 7.7 | Python 3.4+ | -| [Current](https://github.com/hugsy/gef/tree/main) | 8.0+ | Python 3.6+ | - - ** Up to (included) +| [2022.01](https://github.com/hugsy/gef/releases/tag/2022.01) | 8.0 | Python 3.6+ | +| [2024.09](https://github.com/hugsy/gef/releases/tag/2024.09) | 10.0 | Python 3.10+ | diff --git a/docs/faq.md b/docs/faq.md index f5fc62622..ef933cfe4 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -13,21 +13,21 @@ new architectures very easily as well! Also, PEDA development has been quite idle for a few years now, and many new interesting features a debugger can provide simply do not exist. -## What if my GDB is < 8.0 ? +## What if my GDB is < 10.0 ? GDB was introduced with its Python support early 2011 with the release of GDB 7. A (very) long way has gone since and the Python API has been massively improved, and GEF is taking advantage of them to provide the coolest features with as little performance impact as possible. -Currently, GEF is optimized for running against GDB version 8.0+, and Python 3.6+. This allows for a -best performance and best use of the GDB Python API. However, GEF can run on older versions too, +Currently, GEF is optimized for running against GDB version 10.0+, and Python 3.10+. This allows for +the best performance and use of the GDB Python API. However, GEF can run on older versions too, check out [the version compatibility matrix](compat.md). For really older versions of GDB, you can use [`gef-legacy`](https://github.com/hugsy/gef-legacy) which supports a lot of older GDB, and a Python 2/3 compatibility layer. Therefore, it is highly recommended to run GEF with the latest version of GDB. However, all -functions should work on a GDB 8.0 and up. If not, send a [bug -report](https://github.com/hugsy/gef/issues) and provide as much details as possible. +functions should work on a GDB 10.0 and up. If not, send a [bug +report](https://github.com/hugsy/gef/issues) and provide as many details as possible. If you are running an obsolete version, GEF will show a error and message and exit. @@ -36,7 +36,7 @@ Some pre-compiled static binaries for both recent GDB and GDBServer can be downl ## I cannot get GEF setup -GEF will work on any GDB 8+ compiled with Python 3.6+ support. You can view that commands that +GEF will work on any GDB 10+ compiled with Python 3.10+ support. You can view that commands that failed to load using `gef missing`, but this will not affect GEF generally. If you experience problems setting it up on your host, first go to the [Discord @@ -45,8 +45,8 @@ channel](https://discord.gg/HCS8Hg7) for that. You will find great people there Note that the GitHub issue section is to be used to **report bugs** and **GEF issues** (like unexpected crash, improper error handling, weird edge case, etc.), not a place to ask for help. -All recent distributions ship packaged GDB that should be ready-to-go, with a GDB >= 8.0 and Python -3.6+. Any version higher or equal will work just fine. So you might actually only need to run `apt +All recent distributions ship packaged GDB that should be ready-to-go, with GDB >= 10.0 and Python +3.10+. Any version higher or equal will work just fine. So you might actually only need to run `apt install gdb` to get the full-force of GEF. ## I get a SegFault when starting GDB with GEF diff --git a/docs/install.md b/docs/install.md index 3a29fc375..08bd2f14a 100644 --- a/docs/install.md +++ b/docs/install.md @@ -16,7 +16,7 @@ your OS package manager to install them. ### GDB -Only [GDB 8 and higher](https://www.gnu.org/s/gdb) is required. It must be compiled with Python 3.6 +Only [GDB 10.0 and higher](https://www.gnu.org/s/gdb) is required. It must be compiled with Python 3.10 or higher support. For most people, simply using your distribution package manager should be enough. As of January 2020, GEF officially doesn't support Python 2 any longer, due to Python 2 becoming @@ -36,8 +36,7 @@ This should display your version of Python compiled with `gdb`. ```bash $ gdb -nx -ex 'pi print(sys.version)' -ex quit -3.6.9 (default, Nov 7 2019, 10:44:02) -[GCC 8.3.0] +3.12.3 (main, Jul 31 2024, 17:43:48) [GCC 13.2.0] ``` ### Python dependencies @@ -56,7 +55,7 @@ easily extended via ### Quick install The quickest way to get started with GEF is through the installation script available. Simply make -sure you have [GDB 8.0 or higher](https://www.gnu.org/s/gdb), compiled with Python 3.6 or higher, +sure you have [GDB 10.0 or higher](https://www.gnu.org/s/gdb), compiled with Python 3.10 or higher, and run ```bash diff --git a/gef.py b/gef.py index af6ee583e..fc512d861 100644 --- a/gef.py +++ b/gef.py @@ -743,7 +743,12 @@ def _search_for_realpath(self) -> Optional[str]: @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 self._search_for_realpath() + if gef.session.remote is None: + return self.path + default = self._search_for_realpath() + if default: + return default + raise FileNotFoundError def __str__(self) -> str: return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, " From d8a3043ed84ba2c74ec78ce0987b926d4ef53542 Mon Sep 17 00:00:00 2001 From: ValekoZ Date: Sun, 29 Sep 2024 19:17:16 +0200 Subject: [PATCH 2/5] [memory] Fix read_cstring trying to read too far (#1112) --- gef.py | 20 ++++++++++++++++++-- tests/api/gef_memory.py | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/gef.py b/gef.py index fc512d861..80c18c676 100644 --- a/gef.py +++ b/gef.py @@ -10742,8 +10742,24 @@ def read_cstring(self, try: res_bytes = self.read(address, length) except gdb.error: - err(f"Can't read memory at '{address}'") - return "" + current_address = address + res_bytes = b"" + while len(res_bytes) < length: + try: + # Calculate how many bytes there are until next page + next_page = current_address + DEFAULT_PAGE_SIZE + page_mask = ~(DEFAULT_PAGE_SIZE - 1) + size = (next_page & page_mask) - current_address + + # Read until the end of the current page + res_bytes += self.read(current_address, size) + + current_address += size + except gdb.error: + if not res_bytes: + err(f"Can't read memory at '{address:#x}'") + return "" + break try: with warnings.catch_warnings(): # ignore DeprecationWarnings (see #735) diff --git a/tests/api/gef_memory.py b/tests/api/gef_memory.py index 8a448a5af..6b0b6760d 100644 --- a/tests/api/gef_memory.py +++ b/tests/api/gef_memory.py @@ -178,3 +178,17 @@ def test_func_parse_maps_realpath(self): "/usr" not in section.realpath): assert pathlib.Path(section.realpath).is_file() break + + def test_func_read_cstring_oob(self): + gef, gdb = self._gef, self._gdb + + gdb.execute("b main") + gdb.execute("start") + + section = gef.memory.maps[0] + oob_val = gef.memory.read_cstring(section.page_start, section.page_end - + section.page_start + 0x100) + val = gef.memory.read_cstring(section.page_start, section.page_end - + section.page_start) + + assert val == oob_val From f9f80450dc17ffd573362b73fdff0a71044de503 Mon Sep 17 00:00:00 2001 From: Grazfather Date: Wed, 2 Oct 2024 00:38:51 -0400 Subject: [PATCH 3/5] Remove ksymaddr docs (now in gef-extras) (#1141) Now in gef-extras. Moved docs in hugsy/gef-extras#118 Fixes #1140 --- docs/commands/ksymaddr.md | 24 ------------------------ mkdocs.yml | 1 - 2 files changed, 25 deletions(-) delete mode 100644 docs/commands/ksymaddr.md diff --git a/docs/commands/ksymaddr.md b/docs/commands/ksymaddr.md deleted file mode 100644 index ba7f0cc3d..000000000 --- a/docs/commands/ksymaddr.md +++ /dev/null @@ -1,24 +0,0 @@ -## Command `ksymaddr` - -`ksymaddr` helps locate a kernel symbol by its name. - -The syntax is straight forward: - -```text -ksymaddr -``` - -For example, - -```text -gef➤ ksymaddr commit_creds -[+] Found matching symbol for 'commit_creds' at 0xffffffff8f495740 (type=T) -[*] Found partial match for 'commit_creds' at 0xffffffff8f495740 (type=T): commit_creds -[*] Found partial match for 'commit_creds' at 0xffffffff8fc71ee0 (type=R): __ksymtab_commit_creds -[*] Found partial match for 'commit_creds' at 0xffffffff8fc8d008 (type=r): __kcrctab_commit_creds -[*] Found partial match for 'commit_creds' at 0xffffffff8fc9bfcd (type=r): __kstrtab_commit_creds -``` - -Note that the debugging process needs to have the correct permissions for this command to show -kernel addresses. For more information see also [this stackoverflow -post](https://stackoverflow.com/a/55592796). diff --git a/mkdocs.yml b/mkdocs.yml index 635847836..597fb5d14 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,7 +47,6 @@ nav: - hexdump: commands/hexdump.md - highlight: commands/highlight.md - hijack-fd: commands/hijack-fd.md - - ksymaddr: commands/ksymaddr.md - memory: commands/memory.md - name-break: commands/name-break.md - nop: commands/nop.md From ba70548a3ee24355c1c6daca0aa302d326dc2335 Mon Sep 17 00:00:00 2001 From: ValekoZ Date: Thu, 24 Oct 2024 10:21:44 +0200 Subject: [PATCH 4/5] [cmd] Add support for flags specifying filters type in vmmap, and allow multiple filters (#1120) Parse args: - `-a` / `--addr`: - filter by address -> parses the next arg as an int or asks gdb the value - `-n` / `--name`: - filter based on section name - If nothing is specified, print a warning and guess the type (previous behavior) --------- Co-authored-by: Grazfather --- docs/commands/vmmap.md | 20 +++++++++++---- gef.py | 55 ++++++++++++++++++++++++++++++++--------- tests/commands/vmmap.py | 28 +++++++++++++++++++-- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/docs/commands/vmmap.md b/docs/commands/vmmap.md index 73f20b36e..597decb8d 100644 --- a/docs/commands/vmmap.md +++ b/docs/commands/vmmap.md @@ -9,13 +9,23 @@ differs from one architecture to another (this is one of the main reasons I star place). For example, you can learn that ELF running on SPARC architectures always have their `.data` and `heap` sections set as Read/Write/Execute. -`vmmap` accepts one argument, either a pattern to match again mapping names, or an address to -determine which section it belongs to. +`vmmap` can accept multiple arguments, either patterns to match again mapping names, or addresses +to determine which section it belongs to: -![vmmap-grep](https://i.imgur.com/ZFF4QVf.png) +1. `-a` / `--addr`: + - filter by address -> parses the next argument as an integer or asks gdb to interpret the value +2. `-n` / `--name`: + - filter based on section name +3. If nothing is specified, it prints a warning and guesses the type -![vmmap-address](https://i.imgur.com/hfcs1jH.png) +![vmmap-grep](https://github.com/hugsy/gef/assets/11377623/a3dbaa3e-88b0-407f-a0dd-07e65c4a3f73) + +![vmmap-address](https://github.com/hugsy/gef/assets/11377623/4dffe491-f927-4f03-b842-4d941140e66c) The address can be also be given in the form of a register or variable. -![vmmap-register](https://i.imgur.com/RlZA6NU.png) +![vmmap-register](https://github.com/hugsy/gef/assets/11377623/aed7ecdc-7ad9-4ba5-ae03-329e66432731) + +And you can do all of them in one command 🙂 + +![vmmap-all-in-one](https://github.com/hugsy/gef/assets/11377623/b043f61b-48b3-4316-9f84-eb83822149ac) diff --git a/gef.py b/gef.py index 80c18c676..ac9389003 100644 --- a/gef.py +++ b/gef.py @@ -8898,12 +8898,35 @@ class VMMapCommand(GenericCommand): _example_ = f"{_cmdline_} libc" @only_if_gdb_running - def do_invoke(self, argv: List[str]) -> None: + @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args : argparse.Namespace = kwargs["arguments"] vmmap = gef.memory.maps if not vmmap: err("No address mapping information found") return + addrs: Dict[str, int] = {x: parse_address(x) for x in args.addr} + names: List[str] = [x for x in args.name] + + for arg in args.unknown_types: + if not arg: + continue + + if self.is_integer(arg): + addr = int(arg, 0) + else: + addr = safe_parse_and_eval(arg) + + if addr is None: + names.append(arg) + warn(f"`{arg}` has no type specified. We guessed it was a name filter.") + else: + addrs[arg] = int(addr) + warn(f"`{arg}` has no type specified. We guessed it was an address filter.") + warn("You can use --name or --addr before the filter value for specifying its type manually.") + gef_print() + if not gef.config["gef.disable_color"]: self.show_legend() @@ -8912,22 +8935,30 @@ def do_invoke(self, argv: List[str]) -> None: headers = ["Start", "End", "Offset", "Perm", "Path"] gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) + last_printed_filter = None + for entry in vmmap: - if not argv: + names_filter = [f"name = '{x}'" for x in names if x in entry.path] + addrs_filter = [f"addr = {self.format_addr_filter(arg, addr)}" for arg, addr in addrs.items() + if entry.page_start <= addr < entry.page_end] + filter_content = f"[{' & '.join([*names_filter, *addrs_filter])}]" + + if not names and not addrs: self.print_entry(entry) - continue - if argv[0] in entry.path: + + elif names_filter or addrs_filter: + if filter_content != last_printed_filter: + gef_print() # skip a line between different filters + gef_print(Color.greenify(filter_content)) + last_printed_filter = filter_content self.print_entry(entry) - elif self.is_integer(argv[0]): - addr = int(argv[0], 0) - if addr >= entry.page_start and addr < entry.page_end: - self.print_entry(entry) - else: - addr = safe_parse_and_eval(argv[0]) - if addr is not None and addr >= entry.page_start and addr < entry.page_end: - self.print_entry(entry) + + gef_print() return + def format_addr_filter(self, arg: str, addr: int): + return f"`{arg}`" if self.is_integer(arg) else f"`{arg}` ({addr:#x})" + def print_entry(self, entry: Section) -> None: line_color = "" if entry.path == "[stack]": diff --git a/tests/commands/vmmap.py b/tests/commands/vmmap.py index 1ac0c5a2e..fbc00a46a 100644 --- a/tests/commands/vmmap.py +++ b/tests/commands/vmmap.py @@ -20,7 +20,31 @@ def test_cmd_vmmap(self): self.assertGreater(len(res.splitlines()), 1) res = gdb.execute("vmmap stack", to_string=True) - self.assertGreater(len(res.splitlines()), 1) + assert "`stack` has no type specified. We guessed it was a name filter." in res + self.assertEqual(len(res.splitlines()), 9) res = gdb.execute("vmmap $pc", to_string=True) - self.assertEqual(len(res.splitlines()), 2) + assert "`$pc` has no type specified. We guessed it was an address filter." in res + self.assertEqual(len(res.splitlines()), 8) + + def test_cmd_vmmap_addr(self): + gef, gdb = self._gef, self._gdb + gdb.execute("start") + + pc = gef.arch.register("pc") + + res = gdb.execute(f"vmmap -a {pc:#x}", to_string=True) + self.assertEqual(len(res.splitlines()), 5) + + res = gdb.execute("vmmap --addr $pc", to_string=True) + self.assertEqual(len(res.splitlines()), 5) + + def test_cmd_vmmap_name(self): + gdb = self._gdb + gdb.execute("start") + + res = gdb.execute("vmmap -n stack", to_string=True) + self.assertEqual(len(res.splitlines()), 5) + + res = gdb.execute("vmmap --name stack", to_string=True) + self.assertEqual(len(res.splitlines()), 5) From 5376d781606b0b9fdce69bff332e8f5610a12b67 Mon Sep 17 00:00:00 2001 From: Michael Krasnitski <42564254+mkrasnitski@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:19:51 -0700 Subject: [PATCH 5/5] Use shutil when looking for executables on the host system (#1146) ## Description The implementation of the `which` function only checks for the executable flag on paths, which means it can return a directory (at least on POSIX), causing weird permission errors down the line. It should instead defer to `shutil.which`; that function contains a platform-agnostic implementation that should hopefully match the behavior of the GNU `which` utility. Co-authored-by: Grazfather Co-authored-by: crazy hugsy --- gef.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gef.py b/gef.py index ac9389003..c31ee9009 100644 --- a/gef.py +++ b/gef.py @@ -1936,13 +1936,10 @@ def gef_pybytes(x: str) -> bytes: @lru_cache() def which(program: str) -> pathlib.Path: """Locate a command on the filesystem.""" - for path in os.environ["PATH"].split(os.pathsep): - dirname = pathlib.Path(path) - fpath = dirname / program - if os.access(fpath, os.X_OK): - return fpath - - raise FileNotFoundError(f"Missing file `{program}`") + res = shutil.which(program) + if not res: + raise FileNotFoundError(f"Missing file `{program}`") + return pathlib.Path(res) def style_byte(b: int, color: bool = True) -> str: