Introduction
What is Depthcharge?
Depthcharge is a toolkit designed to support security research of embedded platforms using the Das U-Boot bootloader, herein referred to as “U-Boot”.
This toolkit consists of:
An extensible Python 3 module
Python Scripts built atop of the Depthcharge Python module
“Depthcharge Companion Firmware”, which is used to perform attacks requiring a malicious peripheral device.
Some example “helper” payload binaries and build scripts to get you started with U-Boot “standalone” program-esque payloads.
The Depthcharge source code and releases can be found at:
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 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 necessary 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 (AN4581 - requires login).
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:
Scripts: depthcharge-read-mem and depthcharge-write-mem
API calls:
Depthcharge.read_memory()
andDepthcharge.write_memory()
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](_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](_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…
a built-in default
a stored environment
or a stored redundant environment (See CONFIG_SYS_REDUNDAND_ENVIRONMENT)
The environment’s CRC32 checksum
The corresponding
CONFIG_ENV_SIZE
- the total (padded) size that CRC32 checksum is computed overThe “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 handler tables
If a device does not appear to readily expose a command console, it can be very useful to determine if any command handler tables (including command 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 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 is 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 to 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/monitor.gif](_images/monitor.gif)
How do I get started?
If you’re reading this documentation, then you’re in the right place!
Below are two ways to install Depthcharge in a virtual environment (venv).
Install via PyPi
The most recent release can be obtained from the Python Package Index (PyPi) as follows:
$ python3 -m venv depthcharge-venv
$ source ./depthcharge-venv/bin/activate
$ python3 -m pip install depthcharge
Installing latest changes from GitHub
The latest fixes and changes can be obtained the next branch of the GitHub repository.
$ git clone -b next https://github.com/tetrelsec/depthcharge
$ cd depthcharge/python
$ python3 -m venv ./venv
$ source ./venv/bin/activate
$ python3 -m pip install .
If you plan to make changes to the code or documentation, replace the last command with:
$ python3 -m pip install -e .[docs]
Next Steps
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 the Blog Posts and Talks for some additional examples and inspiration!