diff options
-rw-r--r-- | gdb-gef | 3 | ||||
-rw-r--r-- | gdb-gef.spec | 86 | ||||
-rw-r--r-- | gef-got-audit.patch | 207 |
3 files changed, 296 insertions, 0 deletions
@@ -0,0 +1,3 @@ +#!/bin/sh + +exec gdb -ex "source @libdir@/gdb/gef.py" "$@" diff --git a/gdb-gef.spec b/gdb-gef.spec new file mode 100644 index 0000000..6ba1358 --- /dev/null +++ b/gdb-gef.spec @@ -0,0 +1,86 @@ +%global forgeurl https://github.com/hugsy/gef + +Name: gdb-gef +Version: 2024.01 + +%forgemeta + +Release: 1%{?dist} +Summary: GEF (GDB Enhanced Features) + +License: MIT +URL: %{forgeurl} +Source0: %{forgesource} +Source1: gdb-gef +# https://github.com/hugsy/gef/pull/1094 +Patch0: gef-got-audit.patch + +Requires: gdb +Requires: file +Requires: binutils +Requires: procps-ng +Requires: python3 + +BuildRequires: gdb +BuildRequires: gdb-gdbserver +BuildRequires: file +BuildRequires: binutils +BuildRequires: procps-ng +BuildRequires: python3 +BuildRequires: python3-pylint +BuildRequires: python3-pytest +BuildRequires: python3-pytest-benchmark +BuildRequires: python3-pytest-cov +BuildRequires: python3-pytest-xdist +BuildRequires: python3-coverage +BuildRequires: python3-rpyc +BuildRequires: python3-requests +BuildRequires: gcc +BuildRequires: gcc-c++ +BuildRequires: make +BuildRequires: sed +BuildRequires: qemu-user +BuildRequires: git + + +%description +GEF (pronounced ʤɛf - "Jeff") is a set of commands for x86/64, ARM, +MIPS, PowerPC and SPARC to assist exploit developers and +reverse-engineers when using old school GDB. It provides additional +features to GDB using the Python API to assist during the process of +dynamic analysis and exploit development. Application developers will +also benefit from it, as GEF lifts a great part of regular GDB +obscurity, avoiding repeating traditional commands, or bringing out +the relevant information from the debugging runtime. + + +%prep +%forgesetup +%patch 0 -p1 + + +%install +mkdir -p %{buildroot}/%{_libdir}/gdb +cp gef.py %{buildroot}/%{_libdir}/gdb/gef.py + +sed -e "s:@libdir@:%{_libdir}:g" < %{SOURCE1} > %{SOURCE1}.sh +mkdir -p %{buildroot}/%{_bindir} +install -m 0755 %{SOURCE1}.sh %{buildroot}/%{_bindir}/gdb-gef + + +%check +make -C tests/binaries +python3 -m pytest -v -m "not benchmark" -m "not online" tests/ + + +%files +%license LICENSE +%doc docs README.md +%dir %{_libdir}/gdb +%{_libdir}/gdb/gef.py +%{_bindir}/gdb-gef + + +%changelog +* Wed Apr 24 2024 Gordon Messmer <gordon.messmer@gmail.com> +- Package gef 2024.01 diff --git a/gef-got-audit.patch b/gef-got-audit.patch new file mode 100644 index 0000000..cc412df --- /dev/null +++ b/gef-got-audit.patch @@ -0,0 +1,207 @@ +diff --git a/docs/commands/got-audit.md b/docs/commands/got-audit.md +new file mode 100644 +index 0000000..e14a834 +--- /dev/null ++++ b/docs/commands/got-audit.md +@@ -0,0 +1,36 @@ ++## Command `got-audit` ++ ++Display the current state of GOT table of the running process. ++ ++The `got-audit` command optionally takes function names and filters the output displaying only the ++matching functions. ++ ++The command output will list symbols in the GOT along with the file providing the mapped memory ++where the symbol's value points. ++ ++If the file providing the mapped memory doesn't export the symbol, `got-audit` will print an ++error. If multiple files export the named symbol, `got-audit` will print an error. ++ ++```text ++gef➤ got-audit ++``` ++ ++ ++ ++The applied filter partially matches the name of the functions, so you can do something like this. ++ ++```text ++gef➤ got-audit str ++gef➤ got-audit print ++gef➤ got-audit read ++``` ++ ++ ++ ++Example of multiple partial filters: ++ ++```text ++gef➤ got-audit str get ++``` ++ ++ +diff --git a/docs/install.md b/docs/install.md +index b2b5f6f..3a29fc3 100644 +--- a/docs/install.md ++++ b/docs/install.md +@@ -7,6 +7,7 @@ Therefore it requires the following binaries to be present: + + * `file` + * `readelf` ++* `nm` + * `ps` + * `python3` + +diff --git a/gef.py b/gef.py +index f9c6f7e..f808e5d 100644 +--- a/gef.py ++++ b/gef.py +@@ -9196,6 +9196,11 @@ class GotCommand(GenericCommand): + "Line color of the got command output for unresolved function") + return + ++ def build_line(self, name: str, color: str, address_val: int, got_address: int): ++ line = f"[{hex(address_val)}] " ++ line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) ++ return line ++ + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + readelf = gef.session.constants["readelf"] +@@ -9222,7 +9227,7 @@ class GotCommand(GenericCommand): + relro_status = "No RelRO" + + # retrieve jump slots using readelf +- lines = gef_execute_external([readelf, "--relocs", elf_file], as_list=True) ++ lines = gef_execute_external([readelf, "--wide", "--relocs", elf_file], as_list=True) + jmpslots = [line for line in lines if "JUMP" in line] + + gef_print(f"\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") +@@ -9250,12 +9255,68 @@ class GotCommand(GenericCommand): + else: + color = self["function_resolved"] + +- line = f"[{hex(address_val)}] " +- line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) ++ line = self.build_line(name, color, address_val, got_address) + gef_print(line) + return + + ++@register ++class GotAuditCommand(GotCommand, GenericCommand): ++ """Display current status of the got inside the process with paths providing functions.""" ++ ++ _cmdline_ = "got-audit" ++ _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " ++ _example_ = "got-audit read printf exit" ++ _symbols_: Dict[str, List[str]] = collections.defaultdict(list) ++ _paths_: Dict[str, List[str]] = collections.defaultdict(list) ++ _expected_dups_ = ['__cxa_finalize'] ++ ++ def get_symbols_from_path(self, elf_file): ++ nm = gef.session.constants["nm"] ++ # retrieve symbols using nm ++ lines = gef_execute_external([nm, "-D", elf_file], as_list=True) ++ for line in lines: ++ words = line.split() ++ # Record the symbol if it is in the text section or ++ # an indirect function or weak symbol ++ if len(words) == 3 and words[-2] in ('T', 'i', 'I', 'v', 'V', 'w', 'W'): ++ sym = words[-1].split('@')[0] ++ if elf_file not in self._symbols_[sym]: ++ self._symbols_[sym].append(elf_file) ++ self._paths_[elf_file].append(sym) ++ ++ @only_if_gdb_running ++ def do_invoke(self, argv: List[str]) -> None: ++ # Build a list of the symbols provided by each path, and ++ # a list of paths that provide each symbol. ++ for section in gef.memory.maps: ++ if (section.path not in self._paths_ ++ and pathlib.Path(section.path).is_file() ++ and section.permission & Permission.EXECUTE): ++ self.get_symbols_from_path(section.path) ++ return super().do_invoke(argv) ++ ++ def build_line(self, name: str, color: str, address_val: int, got_address: int): ++ line = Color.colorify(f"{name}", color) ++ found = 0 ++ for section in gef.memory.maps: ++ if not got_address in range(section.page_start, section.page_end): ++ continue ++ line += f" : {section.path}" ++ found = 1 ++ short_name = name.split('@')[0] ++ if (len(self._symbols_[short_name]) > 1 ++ and short_name not in self._expected_dups_): ++ line += f" :: ERROR {short_name} found in multiple paths ({str(self._symbols_[short_name])})" ++ if (section.path != "[vdso]" ++ and short_name not in self._paths_[section.path]): ++ line += f" :: ERROR {short_name} not exported by {section.path}" ++ break ++ if not found: ++ line += " : no mapping found" ++ return line ++ ++ + @register + class HighlightCommand(GenericCommand): + """Highlight user-defined text matches in GEF output universally.""" +@@ -10979,7 +11040,7 @@ class GefSessionManager(GefManager): + self.aliases: List[GefAlias] = [] + self.modules: List[FileFormat] = [] + self.constants = {} # a dict for runtime constants (like 3rd party file paths) +- for constant in ("python3", "readelf", "file", "ps"): ++ for constant in ("python3", "readelf", "nm", "file", "ps"): + self.constants[constant] = which(constant) + return + +diff --git a/tests/commands/got_audit.py b/tests/commands/got_audit.py +new file mode 100644 +index 0000000..ae2470b +--- /dev/null ++++ b/tests/commands/got_audit.py +@@ -0,0 +1,42 @@ ++""" ++`got-audit` command test module ++""" ++ ++import pytest ++ ++from tests.base import RemoteGefUnitTestGeneric ++ ++from tests.utils import ( ++ ARCH, ++ ERROR_INACTIVE_SESSION_MESSAGE, ++ debug_target, ++) ++ ++ ++@pytest.mark.skipif(ARCH in ("ppc64le",), reason=f"Skipped for {ARCH}") ++class GotAuditCommand(RemoteGefUnitTestGeneric): ++ """`got-audit` command test module""" ++ ++ def setUp(self) -> None: ++ self._target = debug_target("format-string-helper") ++ return super().setUp() ++ ++ ++ def test_cmd_got_audit(self): ++ gdb = self._gdb ++ ++ self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("got-audit", to_string=True)) ++ ++ # Advance the program until after GOT symbols have been resolved ++ gdb.execute("start") ++ gdb.execute("break greetz") ++ gdb.execute("run beep") ++ gdb.execute("step 4") ++ res = gdb.execute("got-audit", to_string=True) ++ self.assertIn("printf", res) ++ self.assertIn("strcpy", res) ++ self.assertIn("/libc.so.6", res) ++ ++ res = gdb.execute("got-audit printf", to_string=True) ++ self.assertIn("printf", res) ++ self.assertNotIn("strcpy", res) |