Introduction

What is Depthcharge?

Depthcharge is a toolkit designed to support security research and “jailbreaking” of embedded platforms using the Das U-Boot bootloader, herein referred to only as “U-Boot”.

This toolkit consists of:

The Depthcharge source code and releases can be found at:

https://github.com/nccgroup/depthcharge

Why did we create this?

The first steps in hunting for remotely exploitable, high impact vulnerabilities in an embedded system-based target is often to first extract non-volatile storage contents for analysis, and then achieve privileged access (e.g. a root shell) on a device in order to perform further testing and analysis. U-Boot’s attack surface and breadth of functionality frequently make both readily available through local and physical attacks. While exciting to us hardware hackers, these types of attacks attacks generally pose less risk to “regular” users of consumer devices that are not left unattended or carried around in one’s pocket. (Your system’s threat model may vary, of course!) In this sense, attacking a system’s (vendor-modified) U-Boot bootloader is often just a neccessary first step towards getting a closer look at a system’ custom application software operating along attack surface boundaries.

Security professionals operate under limited time frames and budgets. Tools that allow us to standardize our methodologies and work more efficiently ultimately allow us to do a more thorough job in identifying and reporting vulnerabilities that could harm users – specifically those that be both high-impact and remotely exploitable. Depthcharge was born out of necessity, in situations where approaches like simple fault injection during NV storage access or device-specific environment modifications weren’t possible or fruitful. Specifically, we’ve encountered a number of devices in which a product vendor or OEM attempted to restrict access to the U-Boot console, as well as prevent modification of boot-time code and data through the use of “secure boot” (or “authenticated boot”) implementations, such as NXP’s HABv4.

In many of these situations, a reduced-functionality console was still present and contained a handful of seemingly harmless standard or vendor-added console commands. However, some commands, such as i2c and crc32 can be (ab)used as memory write-what-where primitives to achieve arbitrary code execution within U-Boot, compromising the chain of trust well before the OS has had a chance to boot. This provides us with a powerful vantage point, from which we can leverage to more fully explore a platform. The abuse of commands as arbitrary memory read operations (e.g. setexpr, itest), while not as immediately useful as their write counterparts, allow a platform’s (vendor-modified) U-Boot code to be retrieved for further inspection.

After repeatedly creating one-off proof-of-concept scripts leveraging the same bags of tricks, we finally decided that we needed something that would allow us to iterate more quickly, and with a consistent methodology. Thus, we sought to create a framework of reusable building blocks and common operations.

Finally, given that U-Boot is licensed under GPL v2.0, consumers have the right to request and review modified U-Boot source code from their product vendors. These tools are also intended to support those wishing to exercise their right to repair, tinker with, and re-use the products that they own. Just because a product vendor is no longer running web services, shipping fixes, or distributing security patches for a product, does a perfectly good piece of hardware need to become e-waste. Go forth, upcycle, and breathe new life info those old gadgets!

Will this be useful for my situation?

If you have access to a restricted U-Boot console, can tamper with a stored environment, or modify a loaded script, Depthcharge may be the right tool for the job!

Otherwise, you may still find that the abstractions included in the Depthcharge API (e.g. Operation, Stratagem, Hunter) provide a suitable framework and foundation for building your own U-Boot attack automation and image analysis tools. We very much have intended this API to be a tool for quickly iterating on new ideas and one-off scripts. Take a gander at the underlying implementation of depthcharge-find-cmd, depthcharge-find-env, and depthcharge-find-fdt for examples of U-Boot image analysis use cases.

Many vendors ship production firmware containing highly permissive U-Boot configurations that are directly inherited from upstream defaults intended for development kits and reference design platforms. In this case, you can likely achieve what you want without Depthcharge, but it can definitely be useful if you’re looking to automate interactions with the U-Boot console. Read on!

What are some of its key features?

Below are some highlights of Depthcharge’s current functionality.

Python API

The Depthcharge Python API is the primary focus of this project. This API strives to be a “tool for quickly scripting U-Boot hacks” rather than an automagic exploitation framework for U-Boot. While one can certainly seek to build this atop of Depthcharge, this is not the primary goal of the project. Given that OEMs and product vendors all introduce their own modifications to U-Boot, this API favors common security testing “building blocks” over collecting “one-off” device-unique exploits.

Convenience Scripts

A collection of Python Scripts allow one to leverage key API functionality through simple command-line interfaces. In many cases, users may need only these scripts and otherwise never need to write a line of Python code. That being said, familiarity with the API allows one to leverage the maximum benefit from these scripts, as well as build custom tooling atop of Depthcharge.

Memory Access Abstractions

When platform vendors attempt to prune “dangerous” general-purpose memory access operations from U-Boot console support (rather than remove this functionality entirely), less obvious security-impacting memory access mechanisms (e.g., i2c, crc32, itest, setexpr) may be overlooked, leaving opportunities to read or modify running code. This can be especially perilous in situations where investments have been made in an attempt to put a SoC’s “secure boot” functionality to use, as memory-access mechanisms can be exploited to undermine the chain of trust.

Depthcharge identifies a variety of memory access operations and provides an abstraction atop of them. This makes it easier to automate boot-time tasks and proof-of-concept exploitation examples, regardless of which specific operations you’re (ab)using. Based upon the available functionality and the size of a requested data read/write, it will attempt to select the “best” available operation. (You still have control to specify which implementation is used and how, of course.)

This abstraction is exposed via:

Note that the built-in memory access operations are only the tip of the iceberg. If you search for the U_BOOT_CMD macro in both the upstream U-Boot source repository, as well as the forks maintained by various silicon vendors and OEMs, you’ll find that there many more potential candidates that can be added. (We are of course, happy to accept pull requests for functionality we can reproduce on specified platforms or development kits!)

If you encounter a memory access command that’s not the in the Depthcharge codebase, note that you can register your own MemoryReader or MemoryWriter implementation at runtime using the API via the static Operation.register() class method.

Improved Memory Dumping

Given access to a permissive U-Boot console, a common approach for dumping (storage contents copied to) memory is to use an md-based approach.

However, this tends to be slow, considering that the data is formatted as a hex dump, and may take hours when leveraging this approach to extract flash contents. When the go command is available, a simple binary memory read payload can be deployed and used instead, which is generally much faster.

Although there’s overhead in deploying an executable payload, it only needs to be done once per power-on, and becomes negligible for larger memory dumps (i.e., on the order of MiB). The speed difference between the md and the go with a custom payload approach is apparent in the below examples. Note that the second time the go-based read is performed, the -D option is used to skip re-deployment of the payload, further reducing the run time.

_images/read-mem-demo.gif

And yes, we too know the tragic pain of losing hours due to an accidentally interrupted, long running memory dump. Memory read operations are neighborly and will return data read so far, when interrupted. This is shown below. (Here the -f, --file option is omitted so that the partial data is more evident when displayed as a hex dump.)

_images/read-mem-intr.gif

Data Structure Identification

Depthcharge can identify the following data structures, provided with a memory or flash dump.

Built-in or stored environments

The ability to identify and tamper with (unauthenticated) environment variables (e.g. via offline modification of flash memory) can allow arbitrary commands to be executed within the pre-boot environment, even in situations where an interactive console is inaccessible.

The depthcharge-find-env script can be used to identify and extract environment data from a memory dump, including the following metadata:

  • Whether the environment is…

  • The environment’s CRC32 checksum

  • The corresponding CONFIG_ENV_SIZE - the total (padded) size that CRC32 checksum is computed over

  • The “flags” word used to denote which environment is active, in the case of redundant environments

When viewing the environment contents in their text form, Depthcharge can optionally expand variable definitions. This can make life a little bit easier in those cases where bootcmd and friends are defined as a function of a dozen other variables.

For more information, see EnvironmentHunter.

Command tables

If a device does not appear readily expose a command console, it can be very useful to determine if any tables command handlers (including their name, function pointers, and help text) are present in the binary. If so, this may indicate that access is gated based upon some input, whether it be a standard AUTOBOOT-based “stop string”, a simple IO pin state, or a cryptographic challenge-response mechanism. (Just knowing what a vendor has included in their build is half the battle!)

Furthermore, the presence of multiple unique command tables can suggest that a platform vendor has implemented different operating modes or authorization levels. This is the case demonstrated in our blog post, where we show how this type of table can be patched to expose “hidden” commands.

Depthcharge’s depthcharge-find-cmd script (built atop of CommandTableHunter) can be used to locate these command tables. Below is an abridged example excerpt, when run with the --detail argument.

Command table @ 0x8ff684bc (file offset 0x000684bc) - 308 bytes, 11 entries
   CONFIG_SYS_LONGHELP=True, CONFIG_AUTO_COMPLETE=True

   ...

  [7] @ 0x8ff68580
        name: nboot
     maxargs: 4
     cmd_rep: 0x00000001
         cmd: 0x8ff6502c
    complete: 0x00000000
       usage: boot from NAND device
        help: nboot [partition] | [[[loadAddr] dev] offset]

  [8] @ 0x8ff6859c
        name: nm
     maxargs: 2
     cmd_rep: 0x00000001
         cmd: 0x8ff641d4
    complete: 0x00000000
       usage: memory modify (constant address)
        help: nm [.b, .w, .l] address

    ...

Flattened Device Tree Blobs

U-Boot and the Linux kernel use binary Device Tree files (also called Flattened Device Tree Blobs) to describe the current hardware configuration and necessary driver configuration. These provide a reverse engineer with useful information including, but not limited to:

  • What SoC subsystems are used by the platform. (The use, or lack thereof, of security-relevant subsystems better define the scope of analyses.)

  • What peripheral devices are present (and through which interface)

  • Which memory-mapped regions correspond to which subsystems or devices

  • Which functions are assigned to multiplexed I/O pins or pads

Beyond this, there are some interesting “nodes” in the tree that can more readily lead to compromised, such as the chosen node, which is can be used to pass parameters to the kernel such as a KASLR seed, or boot arguments.

The depthcharge-find-fdt script, which uses FDTHunter, can be used to carve device trees binaries from a memory dump. If the device tree compiler installed, they will also be returned in their “source code” representation.

U-Boot’s Exported Jump Table

Finally, in order to better facilitate writing custom executable payloads, Depthcharge attempts to inspect U-Boot’s “global data structure” in order to find its exported “jump table” - a collection of function pointers to handy functions, intended for use by “standalone programs.”

The locations of identified functions are saved, along with other information collected for a device, in a JSON “device configuration” file, which can be “pretty-printed” with depthcharge-print. Below is an excerpt of this output:

Global Data Structure information
================================================================================
Address: 0x8ef55ee8
Jump Table Pointer: 0x8ef81710
Jump Table Entries:
  0x8ff73350  unsigned long get_version()
  0x8ff79330  int getc()
  0x8ff79378  int tstc()
  0x8ff792d8  void putc(const char)
  0x8ff792a4  void puts(const char *)
  0x8ff9ce50  int printf(const char *, va_list)
  0x8ff7334c  void irq_install_handler(int, void*, void *)
  0x8ff7334c  void irq_free_handler(int)
  0x8ff79b84  void * malloc(size_t)
  0x8ff7993c  void free(void *)
  0x8ff9c158  void udelay(unsigned long)
  0x8ff9c0a4  unsigned long get_timer(unsigned long)
  0x8ff9ce94  int vprintf(const char *, va_list)
  0x8ff68970  int do_reset(void *)
  0x8ff7311c  char  * env_get(const char *)
  0x8ff72ce0  int env_set(const char *, const char *)
  0x8ff9cff4  unsigned long simple_strtoul(const char *, const char **, unsigned int)
  0x8ff9d0ac  int strict_strtoul(const char *, const char **, unsigned int, unsigned long *)
  0x8ff9d124  long simple_strtol(const char *, const char **, unsigned int)
  0x8ff9bc7c  int strcmp(const char *, const char *)
  0x8ff7334c  int i2c_write(unsigned char, unsigned int, int, unsigned char *, int)
  0x8ff7334c  int i2c_read(unsigned char, unsigned int, int, unsigned char *, int)
  0x8ff7334c  void * spi_setup_slave(uint, uint, uint, uint)
  0x8ff7334c  void spi_free_slave(void *)
  0x8ff7334c  int spi_claim_bus(void *)
  0x8ff7334c  void spi_release_bus(void *)
  0x8ff7334c  int spi_xfer(void *)
  0x8ff7334c  unsigned long ustrtoul(const char *, char **, unsigned int)
  0x8ff9d14c  unsigned long long ustrtoull(const char *, char **, unsigned int)
  0x8ff9d298  char * strcpy(char *, const char *)
  0x8ff9bbbc  void mdelay(unsigned long)
  0x8ff9c188  void * memset(void *, int, size_t)

Colorized Serial Monitor

Depthcharge’s Monitor implementations allow you to keep an eye on exactly what is being sent a target device’s console and what the device responds with. As shown below, a colorized monitor can be used to keep tabs on long running operations, or simply to better understand how the Deptcharge code works. The following animation shows this monitor (lower window) logging inspection and memory read operations.

_images/monitor1.gif

How do I get started?

If you’re reading this documentation, then you’re in the right place!

The repository can be cloned as follows.

$ git clone https://github.com/nccgroup/depthcharge
$ cd depthcharge

Once in the Depthcharge directory, install Python module can be installed in a virtual environment (venv):

$ cd python
$ python3 -m venv ./venv
$ source ./venv/bin/activate
$ pip install .

If you plan to make changes to the code or documention, replace the last command with:

$ pip install -e .[docs]

We recommend you kick the tires on Depthcharge using the Python Scripts and a device with a permissive U-Boot configuration, just to get a baseline sense of the toolkit. From there, one can leverage these scripts and other examples in the codebase to learn how to use the API for your own custom tooling.

If you’re new to U-Boot and would like to first get your bearings on a Raspberry Pi, check out the Ready, Set, Yocto! tutorial, which describes how to build a custom SD card image containing both U-Boot and a barebones Linux environment. This will result in a permissive default U-Boot configuration, allowing you to explore a greater breadth of Depthcharge’s functionality.

Finally, refer to this NCC Group blog post for some additional examples and inspiration!