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/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/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/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 97b6d02c2..0dfcbc0ac 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: @@ -8897,12 +8894,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() @@ -8911,22 +8931,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]": @@ -10742,8 +10770,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/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 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 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)