diff --git a/archs/arm-blackmagicprobe.py b/archs/arm-blackmagicprobe.py index 8d11287..603f094 100644 --- a/archs/arm-blackmagicprobe.py +++ b/archs/arm-blackmagicprobe.py @@ -10,6 +10,8 @@ import gdb +assert 'gef' in globals(), "This file must be source after gef.py" + class ARMBlackMagicProbe(ARM): arch = "ARMBlackMagicProbe" @@ -31,21 +33,17 @@ def maps(): @register class BMPRemoteCommand(GenericCommand): - """GDB `target remote` command on steroids. This command will use the remote procfs to create - a local copy of the execution environment, including the target binary and its libraries - in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it - will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command - will likely fail. You can however still use the limited command provided by GDB `target remote`.""" + """This command is intended to replace `gef-remote` to connect to a + BlackMagicProbe. It uses a special session manager that knows how to + connect and manage the server running over a tty.""" _cmdline_ = "gef-bmp-remote" _syntax_ = f"{_cmdline_} [OPTIONS] TTY" - _example_ = [ - f"{_cmdline_} --scan /dev/ttyUSB1" - f"{_cmdline_} --scan /dev/ttyUSB1 --power" - f"{_cmdline_} --scan /dev/ttyUSB1 --power --keep-power" + _example_ = [f"{_cmdline_} --scan /dev/ttyUSB1", + f"{_cmdline_} --scan /dev/ttyUSB1 --power", + f"{_cmdline_} --scan /dev/ttyUSB1 --power --keep-power", f"{_cmdline_} --file /path/to/binary.elf --attach 1 /dev/ttyUSB1", - f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1", - ] + f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1"] def __init__(self) -> None: super().__init__(prefix=False) @@ -53,7 +51,7 @@ def __init__(self) -> None: @parse_arguments({"tty": ""}, {"--file": "", "--attach": "", "--power": False, "--keep-power": False, "--scan": False}) - def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def do_invoke(self, _: list[str], **kwargs: Any) -> None: if gef.session.remote is not None: err("You're already in a remote session. Close it first before opening a new one...") return @@ -95,8 +93,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: class GefBMPRemoteSessionManager(GefRemoteSessionManager): - """Class for managing remote sessions with GEF. It will create a temporary environment - designed to clone the remote one.""" + """This subclass of GefRemoteSessionManager specially handles the + intricacies involved with connecting to a BlackMagicProbe.""" def __init__(self, tty: str="", file: str="", attach: int=1, scan: bool=False, power: bool=False, keep_power: bool=False) -> None: self.__tty = tty @@ -106,7 +104,12 @@ def __init__(self, tty: str="", file: str="", attach: int=1, self.__power = power self.__keep_power = keep_power self.__local_root_fd = tempfile.TemporaryDirectory() - self.__local_root_path = pathlib.Path(self.__local_root_fd.name) + self.__local_root_path = Path(self.__local_root_fd.name) + class BMPMode(): + def prompt_string(self) -> str: + return Color.boldify("(BMP) ") + + self._mode = BMPMode() def __str__(self) -> str: return f"BMPRemoteSessionManager(tty='{self.__tty}', file='{self.__file}', attach={self.__attach})" @@ -123,7 +126,7 @@ def close(self) -> None: return @property - def root(self) -> pathlib.Path: + def root(self) -> Path: return self.__local_root_path.absolute() @property @@ -135,9 +138,9 @@ def sync(self, src: str, dst: Optional[str] = None) -> bool: return None @property - def file(self) -> Optional[pathlib.Path]: + def file(self) -> Optional[Path]: if self.__file: - return pathlib.Path(self.__file).expanduser() + return Path(self.__file).expanduser() return None def connect(self) -> bool: diff --git a/archs/arm-openocd.py b/archs/arm-openocd.py new file mode 100644 index 0000000..1a244ec --- /dev/null +++ b/archs/arm-openocd.py @@ -0,0 +1,177 @@ +""" +ARM through OpenOCD support for GEF + +To use, source this file *after* gef + +Author: Grazfather +""" + +from typing import Optional +from pathlib import Path + +import gdb + +assert 'gef' in globals(), "This file must be source after gef.py" + + +class ARMOpenOCD(ARM): + arch = "ARMOpenOCD" + aliases = ("ARMOpenOCD",) + all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", + "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", + "$lr", "$pc", "$xPSR") + flag_register = "$xPSR" + @staticmethod + def supports_gdb_arch(arch: str) -> Optional[bool]: + if "arm" in arch and arch.endswith("-m"): + return True + return None + + @staticmethod + def maps(): + yield from GefMemoryManager.parse_info_mem() + + +@register +class OpenOCDRemoteCommand(GenericCommand): + """This command is intended to replace `gef-remote` to connect to an + OpenOCD-hosted gdbserver. It uses a special session manager that knows how + to connect and manage the server.""" + + _cmdline_ = "gef-openocd-remote" + _syntax_ = f"{_cmdline_} [OPTIONS] HOST PORT" + _example_ = [f"{_cmdline_} --file /path/to/binary.elf localhost 3333", + f"{_cmdline_} localhost 3333"] + + def __init__(self) -> None: + super().__init__(prefix=False) + return + + @parse_arguments({"host": "", "port": 0}, {"--file": ""}) + def do_invoke(self, _: list[str], **kwargs: Any) -> None: + if gef.session.remote is not None: + err("You're already in a remote session. Close it first before opening a new one...") + return + + # argument check + args: argparse.Namespace = kwargs["arguments"] + if not args.host or not args.port: + err("Missing parameters") + return + + # Try to establish the remote session, throw on error + # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which + # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None + # This prevents some spurious errors being thrown during startup + gef.session.remote_initializing = True + session = GefOpenOCDRemoteSessionManager(args.host, args.port, args.file) + + dbg(f"[remote] initializing remote session with {session.target} under {session.root}") + + # Connect can return false if it wants us to disconnect + if not session.connect(): + gef.session.remote = None + gef.session.remote_initializing = False + return + if not session.setup(): + gef.session.remote = None + gef.session.remote_initializing = False + raise EnvironmentError("Failed to setup remote target") + + gef.session.remote_initializing = False + gef.session.remote = session + reset_all_caches() + gdb.execute("context") + return + + +# We CANNOT use the normal session manager because it assumes we have a PID +class GefOpenOCDRemoteSessionManager(GefRemoteSessionManager): + """This subclass of GefRemoteSessionManager specially handles the + intricacies involved with connecting to an OpenOCD-hosted GDB server. + Specifically, it does not have the concept of PIDs which we need to work + around.""" + def __init__(self, host: str, port: str, file: str="") -> None: + self.__host = host + self.__port = port + self.__file = file + self.__local_root_fd = tempfile.TemporaryDirectory() + self.__local_root_path = Path(self.__local_root_fd.name) + class OpenOCDMode(): + def prompt_string(self) -> str: + return Color.boldify("(OpenOCD) ") + + self._mode = OpenOCDMode() + + def __str__(self) -> str: + return f"OpenOCDRemoteSessionManager(='{self.__tty}', file='{self.__file}', attach={self.__attach})" + + def close(self) -> None: + self.__local_root_fd.cleanup() + try: + gef_on_new_unhook(self.remote_objfile_event_handler) + gef_on_new_hook(new_objfile_handler) + except Exception as e: + warn(f"Exception while restoring local context: {str(e)}") + return + + @property + def target(self) -> str: + return f"{self.__host}:{self.__port}" + + @property + def root(self) -> Path: + return self.__local_root_path.absolute() + + def sync(self, src: str, dst: Optional[str] = None) -> bool: + # We cannot sync from this target + return None + + @property + def file(self) -> Optional[Path]: + if self.__file: + return Path(self.__file).expanduser() + return None + + def connect(self) -> bool: + """Connect to remote target. If in extended mode, also attach to the given PID.""" + # before anything, register our new hook to download files from the remote target + dbg(f"[remote] Installing new objfile handlers") + try: + gef_on_new_unhook(new_objfile_handler) + except SystemError: + # the default objfile handler might already have been removed, ignore failure + pass + + gef_on_new_hook(self.remote_objfile_event_handler) + + # Connect + with DisableContextOutputContext(): + self._gdb_execute(f"target extended-remote {self.target}") + + try: + with DisableContextOutputContext(): + if self.file: + self._gdb_execute(f"file '{self.file}'") + except Exception as e: + err(f"Failed to connect to {self.target}: {e}") + # a failure will trigger the cleanup, deleting our hook + return False + + return True + + def setup(self) -> bool: + dbg(f"Setting up as remote session") + + # refresh gef to consider the binary + reset_all_caches() + if self.file: + gef.binary = Elf(self.file) + # We'd like to set this earlier, but we can't because of this bug + # https://sourceware.org/bugzilla/show_bug.cgi?id=31303 + reset_architecture("ARMOpenOCD") + return True + + def _gdb_execute(self, cmd): + dbg(f"[remote] Executing '{cmd}'") + gdb.execute(cmd) diff --git a/docs/archs/arm-openocd.md b/docs/archs/arm-openocd.md new file mode 100644 index 0000000..e560c1d --- /dev/null +++ b/docs/archs/arm-openocd.md @@ -0,0 +1,4 @@ +## ARMOpenOCD + +The ARM OpenOCD architecture is a special arcthtecture used with the `gef-openocd-remote` command. +Please read the [documentation](../commands/gef-openocd-remote.md) for the command. diff --git a/docs/commands/gef-openocd-remote.md b/docs/commands/gef-openocd-remote.md new file mode 100644 index 0000000..1f078ee --- /dev/null +++ b/docs/commands/gef-openocd-remote.md @@ -0,0 +1,14 @@ +## Command gef-openocd-remote + +The `gef-openocd-command` is used with the [`ARMOpenOCD`](../../archs/arm-openocd.py] architecture. + +The [arm-openocd.py](../../archs/arm-openocd.py) script adds an easy way to extend the `gef-remote` +functionality to easily debug ARM targets using a OpenOCD gdbserver. It creates a custom ARM-derived +`Architecture`, as well as the `gef-openocd-remote` command, which lets you easily connect to the +target, optionally loading the accompanying ELF binary. + +### Usage + +```bash +gef-openocd-remote localhost 3333 --file /path/to/elf +```