diff --git a/docs/commands/gef.md b/docs/commands/gef.md index a47cdb9d6..903922051 100644 --- a/docs/commands/gef.md +++ b/docs/commands/gef.md @@ -5,11 +5,11 @@ Displays a list of GEF commands and their descriptions. ``` -gef➤ gef +gef➤ gef ─────────────────────────────────── GEF - GDB Enhanced Features ─────────────────────────────────── $ -- SmartEval: Smart eval (vague approach to mimic WinDBG `?`). aslr -- View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not - attached). This command allows to change that setting. + attached). This command allows to change that setting. assemble -- Inline code assemble. Architecture can be set in GEF runtime config (default x86-32). (alias: asm) bincompare -- BincompareCommand: compare an binary file with the memory position looking for badchars. bytearray -- BytearrayCommand: Generate a bytearray to be compared with possible badchars. @@ -57,7 +57,7 @@ context.libc_args (bool) = False ``` -To filter the config settings you can use `gef config [setting]`. +To filter the config settings you can use `gef config [setting]`. ``` gef➤ gef config theme @@ -81,7 +81,7 @@ gef➤ gef config theme.address_stack blue ### GEF Save Command The `gef save` command saves the current settings (set with `gef config`) to -the user's `~/.gef.rc` file (making the changes persistent). +the user's `~/.gef.rc` file (making the changes persistent). ``` gef➤ gef save @@ -92,7 +92,7 @@ gef➤ gef save Using `gef restore` loads and applies settings from the `~/.gef.rc` file to the current session. This is useful if you are modifying your GEF configuration -file and want to see the changes without completely reloading GEF. +file and want to see the changes without completely reloading GEF. ``` gef➤ gef restore @@ -119,3 +119,33 @@ gef➤ gef run ./binary ``` +### GEF Install Command + +`gef install` allows to install one (or more) specific script(s) from `gef-extras`. The new scripts will be downloaded and sourced to be used immediately after by GEF. The syntax is straight forward: + +``` +gef➤ gef install SCRIPTNAME1 [SCRIPTNAME2...] +``` + +Where `SCRIPTNAME1` ... are the names of script from the [`gef-extras` repository](https://github.com/hugsy/gef-extras/master/tree/scripts/). + + +``` +gef➤ gef install remote windbg stack +[+] Searching for 'remote.py' in `gef-extras@master`... +[+] Installed file '/tmp/gef/remote.py', new command(s) available: `rpyc-remote` +[+] Searching for 'windbg.py' in `gef-extras@master`... +[+] Installed file '/tmp/gef/windbg.py', new command(s) available: `pt`, `hh`, `tt`, `ptc`, `sxe`, `u`, `xs`, `tc`, `pc`, `g`, `r` +[+] Searching for 'stack.py' in `gef-extras@master`... +[+] Installed file '/tmp/gef/stack.py', new command(s) available: `current-stack-frame` +gef➤ +``` + +This makes it easier to deploy new functionalities in limited environment. By default, the command looks up for script names in the `master` branch of `gef-extras`. However you can change specify a different branch through the `gef.default_branch` configuration setting: + +``` +gef➤ gef config gef.default_branch dev +``` + +The files will be dowloaded in the path configured in the `gef.extra_plugins_dir` setting, allowing to reload it easily without having to re-download. + diff --git a/gef.py b/gef.py index 03b0a5ca5..dae7ba9ff 100644 --- a/gef.py +++ b/gef.py @@ -10263,6 +10263,7 @@ def setup(self) -> None: GefMissingCommand() GefSetCommand() GefRunCommand() + GefInstallExtraScriptCommand() # load the saved settings gdb.execute("gef restore") @@ -10901,6 +10902,64 @@ def screen_setup(self) -> None: return +class GefInstallExtraScriptCommand(gdb.Command): + """`gef install` command: installs one or more scripts from the `gef-extras` script repo. Note that the command + doesn't check for external dependencies the script(s) might require.""" + _cmdline_ = "gef install" + _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]" + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) + self.branch = gef.config.get("gef.extras_default_branch", "master") + return + + def invoke(self, args: str, from_tty: bool) -> None: + self.dont_repeat() + if not args: + err("No script name provided") + return + + args = args.split() + + if "--list" in args or "-l" in args: + subprocess.run(["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"]) + return + + dir_setting = gef.config["gef.extra_plugins_dir"] or GEF_TEMP_DIR + self.dirpath = pathlib.Path(dir_setting).expanduser().absolute() + if not self.dirpath.is_dir(): + err("'gef.extra_plugins_dir' is not a valid directory") + return + + for script in args: + script = script.lower() + if not self.__install_extras_script(script): + warn(f"Failed to install '{script}', skipping...") + return + + + def __install_extras_script(self, script: str) -> bool: + fpath = self.dirpath / f"{script}.py" + if not fpath.exists(): + url = f"https://raw.githubusercontent.com/hugsy/gef-extras/{self.branch}/scripts/{script}.py" + info(f"Searching for '{script}.py' in `gef-extras@{self.branch}`...") + data = http_get(url) + if not data: + warn("Not found") + return False + + with fpath.open("wb") as fd: + fd.write(data) + fd.flush() + + old_command_set = set(gef.gdb.commands) + gdb.execute(f"source {fpath}") + new_command_set = set(gef.gdb.commands) + new_commands = [f"`{c[0]}`" for c in (new_command_set - old_command_set)] + ok(f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}") + return True + + # # GEF internal classes # diff --git a/tests/commands/gef.py b/tests/commands/gef.py index 329f8f8e9..194ae2bf1 100644 --- a/tests/commands/gef.py +++ b/tests/commands/gef.py @@ -2,8 +2,15 @@ `gef` command test module """ +import pytest +import pathlib -from tests.utils import gdb_run_cmd, GefUnitTestGeneric +from tests.utils import ( + gdb_run_cmd, + GefUnitTestGeneric, + gdb_start_silent_cmd_last_line, + removeuntil, +) class GefCommand(GefUnitTestGeneric): @@ -17,30 +24,61 @@ def test_cmd_gef(self): def test_cmd_gef_config(self): - pass - - - def test_cmd_gef_help(self): - pass - - - def test_cmd_gef_missing(self): - pass - - - def test_cmd_gef_restore(self): - pass - + res = gdb_run_cmd("gef config") + self.assertNoException(res) + self.assertIn("GEF configuration settings", res) + + known_patterns = ( + "gef.autosave_breakpoints_file (str)", + "gef.debug (bool)", + "gef.disable_color (bool)", + "gef.extra_plugins_dir (str)", + "gef.follow_child (bool)", + "gef.readline_compat (bool)", + "gef.show_deprecation_warnings (bool)", + "gef.tempdir (str)", + "got.function_not_resolved (str)", + "got.function_resolved (str)", + ) + for pattern in known_patterns: + self.assertIn(pattern, res) + + + def test_cmd_gef_config_get(self): + res = gdb_run_cmd("gef config gef.debug") + self.assertNoException(res) + self.assertIn("GEF configuration setting: gef.debug", res) + # the `True` is automatically set by `gdb_run_cmd` so we know it's there + self.assertIn("""gef.debug (bool) = True\n\nDescription:\n\tEnable debug mode for gef""", + res) - def test_cmd_gef_run(self): - pass + def test_cmd_gef_config_set(self): + res = gdb_start_silent_cmd_last_line("gef config gef.debug 0", + after=("pi print(is_debug())", )) + self.assertNoException(res) + self.assertEqual("False", res) - def test_cmd_gef_save(self): - pass + def test_cmd_gef_help(self): + res = gdb_run_cmd("help gef") + self.assertNoException(res) - def test_cmd_gef_set(self): + known_patterns = ( + "gef config", + "gef help", + "gef install", + "gef missing", + "gef restore", + "gef run", + "gef save", + "gef set", + ) + for pattern in known_patterns: + self.assertIn(pattern, res) + + + def test_cmd_gef_run_and_run(self): res = gdb_run_cmd("gef set args $_gef0", before=("pattern create -n 4", ), after=("show args")) @@ -51,3 +89,34 @@ def test_cmd_gef_set(self): before=("pattern create -n 4", ), after=("show args")) self.assertException(res) + + + def test_cmd_gef_save(self): + # check + res = gdb_run_cmd("gef save") + self.assertNoException(res) + self.assertIn("Configuration saved to '", res) + + gefrc_file = removeuntil("Configuration saved to '", res.rstrip("'")) + + # set & check + for name in ("AAAABBBBCCCCDDDD", "gef"): + res = gdb_run_cmd("gef save", before=(f"gef config gef.tempdir /tmp/{name}", )) + self.assertNoException(res) + with pathlib.Path(gefrc_file).open() as f: + config = f.read() + self.assertIn(f'tempdir = /tmp/{name}\n', config) + + + @pytest.mark.online + def test_cmd_gef_install(self): + res = gdb_run_cmd("gef install skel windbg stack") + self.assertNoException(res) + # we install 3 plugins, the pattern must be found 3 times + pattern = "Installed file" + for _ in range(3): + idx = res.find(pattern) + self.assertNotEqual(-1, idx) + self.assertIn("new command(s) available", res) + res = res[idx:] +