depthcharge.memory

This module provides memory access functionality through MemoryReader and MemoryWriter abstractions. These abstractions allow one to write re-usable scripts and tools to interact with exposed U-Boot consoles, even when the available commands vary platform to platform.

The underlying implementations are built atop of both U-Boot console commands specifically intended for arbitrary memory access, as well as those often overlooked (when deployed in production systems) as memory-access primitives.

During the initialization of a Depthcharge context, the target platform is inspected to determine which memory operations are available. In general, an API user should not need to manually instantiate any of the classes within this depthcharge.memory subpackage. Instead, one only needs to interact with higher level methods such as Depthcharge.read_memory() and Depthcharge.write_memory(). Familiarity with the underlying implementations, however, allows one to choose an specific implementation (via the impl= keyword argument) or introduce new implementations atop of vendor-specific commands.

Base Classes

class depthcharge.memory.MemoryReader(ctx, **_kwargs)

This base class extends Operation to provide memory read() and read_to_file() methods. The constructor takes a single Depthcharge context object.

_setup(addr, size)

Subclasses should override this method to perform any necessary setup activities (e.g. “configure Companion device”) .

The MemoryReader base class implementation is a no-op.

_teardown()

Subclasses should override this method to perform any necessary clean-up activities (e.g. “exit sub-prompt”).

The MemoryReader base class implementation is a no-op.

_read(addr: int, size: int, handle_data)

Subclasses of MemoryReader must implement this method to perform the actual read operation.

This method does not return a value, but instead should pass data to the provided handle_data() handler. This will take care of either buffering the data in memory or writing it to disk, depending upon which API function was invoked.

read(addr: int, size: int, **kwargs) bytes

Read size bytes from the specified address (addr) and return data as a bytes object.

Specify a show_progress=False keyword argument to disable the progress bar printed during the read operation.

This method will return partial output and present a warning if interrupted by a KeyboardInterrupt exception.

read_to_file(addr: int, size: int, filename: str, **kwargs)

Read size bytes from memory (located at addr) and stream data to a file specified via filename.

Specify a show_progress=False keyword argument to disable the progress bar printed during the read operation.

If interrupted by a KeyboardInterrupt exception, this method will finish writing any received data, cleanly close the file, and present a warning about a partial read.

class depthcharge.memory.MemoryWordReader(ctx, **kwargs)

A MemoryWordReader is a specific type of MemoryReader that can only operate on byte, word, long-word, and potentially quad-word sized data. The constructor takes a single Depthcharge context object.

Subclasses must implement _read_word(). This parent class will take care of invoking this method as needed to perform arbitrary-sized reads.

_read_word(addr: int, size: int, handle_data)

Subclasses of MemoryWordReader must implement this method to perform the actual word-read operation.

This method does not return a value, but instead should pass data to the provided handle_data() handler. This will take care of either buffering the data in memory or writing it to disk, depending upon which API function was invoked.

class depthcharge.memory.DataAbortMemoryReader(ctx, **kwargs)

Available only for ARM targets.

This is a MemoryWordReader that extracts memory contents by triggering Data Aborts and parsing the relevant value from the register dump printed by U-Boot when this occurs.

A da_data_reg keyword argument may be provided to specify the name of the register that is expected to contain memory read contents. It defaults to an architecture-specific value. This value can be overridden by a DEPTHCHARGE_DA_DATA_REG environment variable.

A da_crash_addr keyword argument can be used to specify a memory address to access in order to induce a data abort. It defaults to an architecture-specific value. This value can be overridden by a DEPTHCHARGE_DA_ADDR environment variable.

Given that this reader causes the platform to reset, it may be the case that the data you’re attempting to read is overwritten during the crash-reset-init series of operations. Two workaround for this situation are provided:

  1. A da_pre_fn keyword argument, which will be executed before each word read. This function must accept the following parameters: (address: int, size: int, pre_info)

    Note that the final argument is any value passed as to this constructor as da_pre_info keyword argument. You’ll likely want to use a type that can be instantiated early and populated later, such as a dictionary. This allows items such as a Depthcharge context to be added following the completion of initialization code.

  2. A da_pre_cmds keyword argument may be specified as a semicolon-delimited command string or as a tuple/list of commands. These will be executed before each word-read.

If both are provided, they will be executed in the order shown here.

_trigger_data_abort(address, **kwargs) str

Sub-classes must implement this method and return the data abort text.

class depthcharge.memory.MemoryWriter(ctx, **kwargs)

This base class extends Operation to provide memory write() and write_from_file() methods. The constructor takes a single Depthcharge context object, as well as an optional block_size keyword argument.

The block_size values can be used to override the number of bytes written at a time. The default value is 128, but some subclasses override this with a more appropriate default or do not respect this value. You probably don’t want or need to change this.

_setup(addr, data)

Subclasses should override this method to perform any necessary setup activities (e.g. “configure Companion device”) .

The MemoryWriter base class implementation is a no-op.

_teardown()

Subclasses should override this method to perform any necessary clean-up activities (e.g. “exit sub-prompt”).

The MemoryWriter base class implementation is a no-op.

_write(addr: int, data: bytes, **kwargs)

Subclasses of MemoryWriter must implement this method to perform the actual write operation.

write(addr: int, data: bytes, **kwargs)

Write data to the specified address (addr).

Specify a show_progress=False keyword argument to disable the progress bar printed during the write operation.

write_from_file(addr: int, filename: str, **kwargs)

Open the file specified via filename and write its contents to the address indicated by addr.

Specify a show_progress=False keyword argument to disable the progress bar printed during the write operation.

class depthcharge.memory.MemoryWordWriter(ctx, **kwargs)

A MemoryWordWriter is a specific type of MemoryWriter that can only operate on byte, word, long-word, and potentially quad-word sized data. The constructor takes a single Depthcharge context object.

Subclasses must implement _write_word(). This parent class will take care of invoking this method as needed to perform arbitrary-sized writes.

_write_word(addr: int, data: bytes, **kwargs)

Subclasses of MemoryWordWriter must implement this method to perform the actual word-write operation.

class depthcharge.memory.StratagemMemoryWriter(ctx, **kwargs)

StratagemMemoryWriter is a base class for MemoryWriter implementations that cannot write memory directly, but rather through a side-effect or roundabout approach described by a depthcharge.Stratagem.

write(addr: int, data: Optional[bytes] = None, **kwargs)

Execute the Stratagem specified in a stratagem keyword argument in order to write a desired payload to a target memory location (addr).

Because a StratagemMemoryWriter cannot write data directly, the data argument is unused and should be left as None.

For this type of MemoryWriter, the write_from_file() method is often more intuitive to use.

Example:

my_stratagem_writer.write(0x8400_0000, data=None, strategm=my_stratagem)
write_from_file(addr: int, filename: str, **kwargs)

Load a Stratagem file and use it to write to the target memory locations specified by addr.

Example:

my_stratagem_writer.write_from_file(0x8400_0000, 'my_stratagem.json')

Implementations

MemoryReader / MemoryWordReader

DataAbortMemoryReader

MemoryWriter / MemoryWordWriter

StratagemMemoryWriter

class depthcharge.memory.CRC32MemoryReader(ctx, **kwargs)

Reads memory contents by performing CRC32 operations over 1, 2, or 4-byte values and using lookup-tables entries or inverse calculations to recover the input data from the checksum.

class depthcharge.memory.CRC32MemoryWriter(ctx, **kwargs)

The U-Boot console’s crc32 command allows a computed checksum to be written to a specified memory addres. By computing a CRC32 preimage, this can be exploited as an arbitrary memory write operation.

The CRC32MemoryWriter inherits StratagemMemoryWriter and can be used to perform writes using Stratagem objects produced by ReverseCRC32Hunter.

class depthcharge.memory.CpCrashMemoryReader(ctx, **kwargs)

Available only for ARM targets.

The CpCrashMemoryReader crashes the platform by attempting to copy a word from a target read location to a non-writable location, resulting in a Data Abort. The read data is extracted from a register dump printed by U-Boot when this occurs.

This is very slow, as it involves 1 reset per-word.

Refer to the DataAbortMemoryReader parent class for information about supported keyword arguments.

class depthcharge.memory.CpMemoryWriter(ctx, **kwargs)

This StratagemMemoryWriter uses the cp console command to write a desired payload to memory using a Stratagem built by CpHunter.

class depthcharge.memory.GoMemoryReader(ctx, **kwargs)

The GoMemoryReader leverages a simple binary payload that can be invoked with U-Boot’s “go” command to dump large regions of memory. It writes binary data to the console, allowing data to be retrieved more efficiently than with text-based memory access mechanisms like MdMemoryReader.

However, in order to use this MemoryReader, a memory write primitive is required to deploy its executable payloads, and the “go” command needs to be present. (Hint: Technically, only a write primitive is strictly necessary if one can modify the U-Boot command table “linker list” to direct a command’s function pointer to a makeshift “go” command implementation. ;) )

class depthcharge.memory.I2CMemoryReader(ctx, **kwargs)

The I2CMemoryReader leverages a Depthcharge Companion device to achieve a memory read operation using U-Boot’s i2c write console command.

As shown below, this command writes data from the SoC memory space to a peripheral device on a platform’s I2C bus. By directing the I2C write to a device that we control (and have attached to the bus), these memory contents can be relayed back to the host-side Depthcharge code.

../_images/i2c-read.png
class depthcharge.memory.I2CMemoryWriter(ctx, **kwargs)

The I2CMemoryWriter operates in concert with a Depthcharge Companion device to achieve a memory write operation using U-Boot’s i2c read console command. The following diagram depicts its high-level operation.

The i2c read command copies data retrieved from a peripheral device into a specified SoC memory region. Because we control what the Companion device will respond to read requests with, we can effective deploy arbitrary payloads to selected addresses in the target SoC’s memory space.

../_images/i2c-write.png
class depthcharge.memory.ItestMemoryReader(ctx, **kwargs)

This MemorReader implementation that uses the itest U-Boot command as an byte-wise memory read operation.

By design, this command allows two values to be compared using the operators -eq, -ne, -lt, -gt, -le, -ge, ==, !=, <>, <, >, <=, and >=. It also allows addresses to be dereferenced in these comparisons using a C-like *<address> syntax.

Although the itest command cannot read a value directory, a binary search using the above operators can be used to determine the value at a specified memory location, with a byte-level granularity.

class depthcharge.memory.LoadbMemoryWriter(ctx, **kwargs)

This is a MemoryWriter implemented atop of the ckermit program, which implements the Kermit protocol. It can be used to load data into memory using U-Boot’s loadb command.

The ckermit package appears to have been dropped from some recent Linux distributions. Source code can be obtained from http://www.kermitproject.org. We’ve found that in order to build it with make linux, ckucmd.c needs to have an _IO_file_flags macro defined due to ckermit’s decision to access private data members of libc FILE structures. 😬

class depthcharge.memory.LoadxMemoryWriter(ctx, **kwargs)

This is a MemoryWriter implemented atop of the sx program, which implements XMODEM protocol. It can be used to load data into memory using U-Boot’s loadx command.

class depthcharge.memory.LoadyMemoryWriter(ctx, **kwargs)

This is a MemoryWriter implemented atop of the sb program, which implements YMODEM protocol. It can be used to load data into memory using U-Boot’s loady command.

class depthcharge.memory.MdMemoryReader(ctx, **_kwargs)

Reads memory using the U-Boot console command md (memory display), which outputs a textual hex dump.

class depthcharge.memory.MmMemoryReader(ctx, **kwargs)

Reads memory contents using the U-Boot console’s mm (memory modify) command, which provides an interactive interface for viewing and modifying memory.

This implementation leverages the fact that the current state is displayed, but not modified, if no change is provided for the currently displayed word.

class depthcharge.memory.MmMemoryWriter(ctx, **kwargs)

Writes memory using the U-Boot console command mm, which provides an interactive interface for viewing and modifying memory.

class depthcharge.memory.MwMemoryWriter(ctx, **kwargs)

Write data to memory using the U-Boot console mw (memory fill) command, one word at a time.

class depthcharge.memory.NmMemoryReader(ctx, **kwargs)

Reads memory using U-Boot’s interactive nm (memory modify, constant address) command, one word at a time.

This leverages the fact that no change is made to the currently shown word if no replacement is provided.

class depthcharge.memory.NmMemoryWriter(ctx, **kwargs)

Writes memory using U-Boot’s interactive nm (memory modify, constant address) command, one word at a time.

class depthcharge.memory.SetexprMemoryReader(ctx, **kwargs)

The U-Boot setexpr console command can be used to assign an environment variable based upon the result of an expression. The supported expression syntax includes a memory dereference operation, which this class leverages to provide a MemoryWordReader implementation.

Memory Patching

class depthcharge.memory.MemoryPatch(addr: int, value: bytes, expected: Optional[bytes] = None, desc='')

A MemoryPatch describes a change to apply to a contiguous memory region.

An optional expected parameter can be used to denote the value expected to be present at the specified address befor a change is made. If provided, the size of expected must be equal to that of value.

The desc string is used to describe the change. It should be concise and suitable for printing to a user.

In addition to this constructor, a MemoryPatch can be created using from_tuple() or from_dict(). The values provided at creation-time can be later accessed using the following properties.

property address: int

Address that the patch should be applied to

property value: bytes

Data to write to the selected address

property expected: bytes

Data expected to reside at selected address, prior to performing write

property description: str

Patch description string

classmethod from_tuple(src: tuple)

Create a MemoryPatch object from a tuple with the following elements:

Index

Type

Description

0

int

Address to apply patch to

1

bytes

Data to write to the target address

2

bytes

Value expected to reside at target address. Optional; may be None

3

str

Description of patch. Optional may be None or empty string.

classmethod from_dict(src: dict)

Create a MemoryPatch object from a dictionary with keys address, value, expected and description. Refer to the corresponding parameters of MemoryPatch constructor.

class depthcharge.memory.MemoryPatchList(patch_list=None)

A MemoryPatchList stores a sequence of MemoryPatch objects.

Entries may be accessed by index and iterated over. (e.g. for patch in memory_patch_list: ...)

append(patch)

Append a MemoryPatch to the list. The patch argument may also be specified as a dict or tuple, populated according to MemoryPatch.from_dict() or MemoryPatch.from_tuple().