-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for BlackMagicProbe (#99)
This adds three things: `gef-bmp-remote` command, to connect to a black magic probe. This is its own command because it connects to a tty instead of a host/port. `GefBMPRemoteSessionManager`, which handles connecting and such. `ARMBlackMagicProbe`, a new arch, which handles its own memory management and has different register names that e.g. openocd.
- Loading branch information
1 parent
3a63c22
commit b6b8ad1
Showing
4 changed files
with
245 additions
and
3 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
""" | ||
ARM through the Black Magic Probe support for GEF | ||
To use, source this file *after* gef | ||
Author: Grazfather | ||
""" | ||
|
||
from typing import Optional | ||
|
||
import gdb | ||
|
||
|
||
class ARMBlackMagicProbe(ARM): | ||
arch = "ARMBlackMagicProbe" | ||
aliases = ("ARMBlackMagicProbe",) | ||
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 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`.""" | ||
|
||
_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" | ||
f"{_cmdline_} --file /path/to/binary.elf --attach 1 /dev/ttyUSB1", | ||
f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1", | ||
] | ||
|
||
def __init__(self) -> None: | ||
super().__init__(prefix=False) | ||
return | ||
|
||
@parse_arguments({"tty": ""}, {"--file": "", "--attach": "", "--power": False, | ||
"--keep-power": False, "--scan": False}) | ||
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.tty: | ||
err("Missing parameters") | ||
return | ||
|
||
if not args.scan and not args.attach: | ||
err("Must provide target to attach to if not scanning") | ||
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 = GefBMPRemoteSessionManager(args.tty, args.file, args.attach, args.scan, args.power) | ||
|
||
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 | ||
|
||
|
||
class GefBMPRemoteSessionManager(GefRemoteSessionManager): | ||
"""Class for managing remote sessions with GEF. It will create a temporary environment | ||
designed to clone the remote one.""" | ||
def __init__(self, tty: str="", file: str="", attach: int=1, | ||
scan: bool=False, power: bool=False, keep_power: bool=False) -> None: | ||
self.__tty = tty | ||
self.__file = file | ||
self.__attach = attach | ||
self.__scan = scan | ||
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) | ||
|
||
def __str__(self) -> str: | ||
return f"BMPRemoteSessionManager(tty='{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) | ||
if self.__power and not self.__keep_power: | ||
self._power_off() | ||
except Exception as e: | ||
warn(f"Exception while restoring local context: {str(e)}") | ||
return | ||
|
||
@property | ||
def root(self) -> pathlib.Path: | ||
return self.__local_root_path.absolute() | ||
|
||
@property | ||
def target(self) -> str: | ||
return f"{self.__tty} (attach {self.__attach})" | ||
|
||
def sync(self, src: str, dst: Optional[str] = None) -> bool: | ||
# We cannot sync from this target | ||
return None | ||
|
||
@property | ||
def file(self) -> Optional[pathlib.Path]: | ||
if self.__file: | ||
return pathlib.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.__tty}") | ||
|
||
# Optionally enable target-powering | ||
if self.__power: | ||
self._power_on() | ||
|
||
# We must always scan, but with --scan we are done here | ||
self._gdb_execute("monitor swdp_scan") | ||
if self.__scan: | ||
self._gdb_execute("disconnect") | ||
|
||
# Returning false cleans up the session | ||
return False | ||
|
||
try: | ||
with DisableContextOutputContext(): | ||
if self.file: | ||
self._gdb_execute(f"file {self.file}") | ||
self._gdb_execute(f"attach {self.__attach or 1}") | ||
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("ARMBlackMagicProbe") | ||
return True | ||
|
||
def _power_off(self): | ||
self._gdb_execute("monitor tpwr disable") | ||
|
||
def _power_on(self): | ||
self._gdb_execute("monitor tpwr enable") | ||
|
||
def _gdb_execute(self, cmd): | ||
dbg(f"[remote] Executing '{cmd}'") | ||
gdb.execute(cmd) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
## ARMBlackMagicProbe | ||
|
||
The ARM BlackMagicProbe architecture is a special arcthtecture used with the `gef-bmp-remote` | ||
command. Please read the [documentation](../commands/gef-bmp-remote.md) for the command. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
## Command gef-bmp-remote | ||
|
||
The `gef-bmp-command` is used with the [`ARMBlackMagicProbe`](../../archs/arm-blackmagicprobe.py] | ||
architecture. | ||
|
||
The [Black Magic Probe](https://black-magic.org/) is a JTAG/SWD debugger that handles communicating | ||
with your device and exposes a _gdbserver_ for GDB to connect to. This allows you to connect to it | ||
via GDB with the `target extended-remote` command. However, because this is exposed via a tty, GEF | ||
cannot handle it with its `gef-remote` command (which assumes a _host:port_ connection). The | ||
[arm-blackmagicprobe.py](../../archs/arm-blackmagicprobe.py) script offers a way around this. It | ||
creates a custom ARM-derived `Architecture`, as well as the `gef-bmp-remote` command, which lets you | ||
scan for devices, power the target, and ultimately connect to the target device. | ||
|
||
### Scan for devices | ||
|
||
```bash | ||
gef➤ gef-bmp-remote --scan /dev/ttyUSB1" | ||
[=] [remote] Executing 'monitor swdp_scan' | ||
Target voltage: 3.3V | ||
Available Targets: | ||
No. Att Driver | ||
1 Raspberry RP2040 M0+ | ||
2 Raspberry RP2040 M0+ | ||
3 Raspberry RP2040 Rescue (Attach to reset!) | ||
``` | ||
This will connect to the BMP and use its scan feature to find valid targets connected. They will be | ||
numbered. Use the appropriate number to later `--attach`. | ||
If you are powering the device through the BMP, then make sure to add the `--power` arguments, | ||
otherwise the target may not be powered up when you attempt the scan. | ||
If you want to keep power between scanning and attaching, then use `--keep-power`. | ||
```bash | ||
gef➤ gef-bmp-remote --file /path/to/binary.elf --attach 1 /dev/ttyUSB1", | ||
gef➤ gef-bmp-remote --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1", | ||
``` |