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 +``` + +![gef-got-audit](https://i.imgur.com/KWStygQ.png) + +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 +``` + +![gef-got-audit-one-filter](https://i.imgur.com/YucJboD.png) + +Example of multiple partial filters: + +```text +gef➤ got-audit str get +``` + +![gef-got-audit-multi-filter](https://i.imgur.com/VhMvXYZ.png) 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)