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/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) diff --git a/gef.py b/gef.py index 804dbae..2093b9c 100644 --- a/gef.py +++ b/gef.py @@ -9382,6 +9382,63 @@ class GotCommand(GenericCommand): 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."""