Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for BlackMagicProbe #99

Merged
merged 6 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions archs/README.md

This file was deleted.

203 changes: 203 additions & 0 deletions archs/arm-blackmagicprobe.py
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)
4 changes: 4 additions & 0 deletions docs/archs/arm-blackmagicprobe.md
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.
38 changes: 38 additions & 0 deletions docs/commands/gef-bmp-remote.md
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",
```
Loading