Skip to content

Commit

Permalink
[target-remote] Basic support for the target remote command (#1020)
Browse files Browse the repository at this point in the history
As mentioned in Gallopsled/pwntools#2264, gef does not work properly
with many tools that rely on the `target remote` command.
In this PR, I propose a fix that uses a remote posthook in order to
instantiate and setup the GefRemoteSessionManager after the connection
being established.

Note that this isn't a perfect solution since we do not have all the
information needed for a proper instantiation of the
GefRemoteSessionManager, but it seems to be a good workaround in order
to make tools like `pwntools` work correctly with gef.
  • Loading branch information
ValekoZ authored Dec 16, 2023
1 parent 15b09cf commit f7a2105
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 13 deletions.
6 changes: 4 additions & 2 deletions docs/commands/gef-remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ does a limited job (80's bandwith FTW) to collect more information about the tar
process of debugging more cumbersome. GEF greatly improves that state with the `gef-remote` command.

📝 **Note**: If using GEF, `gef-remote` **must** be your way or debugging remote processes, never
`target remote`. Maintainers will not provide support or help if you decide to use the traditional
`target remote` command. For many reasons, you **cannot** use `target remote` alone with GEF.
`target remote`. Maintainers will provide minimal support or help if you decide to use the
traditional `target remote` command. For many reasons, you **should not** use `target remote` alone
with GEF. It is still important to note that the default `target remote` command has been
overwritten by a minimal copy `gef-remote`, in order to make most tools relying on this command work.

`gef-remote` can function in 2 ways:

Expand Down
53 changes: 42 additions & 11 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -6006,6 +6006,13 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None:
# This prevents some spurious errors being thrown during startup
gef.session.remote_initializing = True
gef.session.remote = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary)

dbg(f"[remote] initializing remote session with {gef.session.remote.target} under {gef.session.remote.root}")
if not gef.session.remote.connect(args.pid):
raise EnvironmentError(f"Cannot connect to remote target {gef.session.remote.target}")
if not gef.session.remote.setup():
raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote.target}")

gef.session.remote_initializing = False
reset_all_caches()
gdb.execute("context")
Expand Down Expand Up @@ -9503,6 +9510,7 @@ def __init__(self) -> None:
gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)")
gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef")
gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints")
gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`")
plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": GefSetting.no_spaces})
plugins_dir.add_hook("on_write", lambda _: self.load_extra_plugins())
gef.config["gef.extra_plugins_dir"] = plugins_dir
Expand Down Expand Up @@ -10861,12 +10869,6 @@ def __init__(self, host: str, port: int, pid: int =-1, qemu: Optional[pathlib.Pa
self.__local_root_fd = tempfile.TemporaryDirectory()
self.__local_root_path = pathlib.Path(self.__local_root_fd.name)
self.__qemu = qemu
dbg(f"[remote] initializing remote session with {self.target} under {self.root}")
if not self.connect(pid):
raise EnvironmentError(f"Cannot connect to remote target {self.target}")
if not self.setup():
raise EnvironmentError(f"Failed to create a proper environment for {self.target}")
return

def close(self) -> None:
self.__local_root_fd.cleanup()
Expand Down Expand Up @@ -11136,6 +11138,14 @@ def reset_caches(self) -> None:
return


def target_remote_posthook():
if gef.session.remote_initializing:
return

gef.session.remote = GefRemoteSessionManager("", 0)
if not gef.session.remote.setup():
raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}")

if __name__ == "__main__":
if sys.version_info[0] == 2:
err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.")
Expand Down Expand Up @@ -11214,11 +11224,32 @@ def reset_caches(self) -> None:

GefTmuxSetup()

# `target remote` commands cannot be disabled, so print a warning message instead
errmsg = "Using `target remote` with GEF does not work, use `gef-remote` instead. You've been warned."
hook = f"""pi if calling_function() != "connect": err("{errmsg}")"""
gdb.execute(f"define target hook-remote\n{hook}\nend")
gdb.execute(f"define target hook-extended-remote\n{hook}\nend")

disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite"

if not gef.config[disable_tr_overwrite_setting]:
warnmsg = ("Using `target remote` with GEF should work in most cases, "
"but use `gef-remote` if you can. You can disable the "
"overwrite of the `target remote` command by toggling "
f"`{disable_tr_overwrite_setting}` in the config.")
hook = f"""
define target hookpost-{{}}
pi target_remote_posthook()
context
pi if calling_function() != "connect": warn("{warnmsg}")
end
"""

# Register a post-hook for `target remote` that initialize the remote session
gdb.execute(hook.format("remote"))
gdb.execute(hook.format("extended-remote"))
else:
errmsg = ("Using `target remote` does not work, use `gef-remote` "
f"instead. You can toggle `{disable_tr_overwrite_setting}` "
"if this is not desired.")
hook = f"""pi if calling_function() != "connect": err("{errmsg}")"""
gdb.execute(f"define target hook-remote\n{hook}\nend")
gdb.execute(f"define target hook-extended-remote\n{hook}\nend")

# restore saved breakpoints (if any)
bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute()
Expand Down
12 changes: 12 additions & 0 deletions tests/commands/gef_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ def test_cmd_gef_remote_qemu_user(self):
self.assertIn(
f"RemoteSession(target='{GDBSERVER_PREFERED_HOST}:{port}', local='/tmp/", res)
self.assertIn(", qemu_user=True)", res)


def test_cmd_target_remote(self):
port = GDBSERVER_PREFERED_PORT + 3
before = [f"target remote {GDBSERVER_PREFERED_HOST}:{port}"]
with gdbserver_session(port=port) as _:
res = gdb_run_cmd(
"pi print(gef.session.remote)", before=before)
self.assertNoException(res)
self.assertIn(
f"RemoteSession(target=':0', local='/tmp/", res)
self.assertIn(", qemu_user=False)", res)

0 comments on commit f7a2105

Please sign in to comment.