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 memoryread()
andread_to_file()
methods. The constructor takes a singleDepthcharge
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 singleDepthcharge
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:
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.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 memorywrite()
andwrite_from_file()
methods. The constructor takes a singleDepthcharge
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 singleDepthcharge
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 adepthcharge.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 asNone
.For this type of
MemoryWriter
, thewrite_from_file()
method is often more intuitive to use.Example:
my_stratagem_writer.write(0x8400_0000, data=None, strategm=my_stratagem)
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 usingStratagem
objects produced byReverseCRC32Hunter
.
- 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 thecp
console command to write a desired payload to memory using aStratagem
built byCpHunter
.
- 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.
- 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.
- 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’sloadb
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 libcFILE
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’sloadx
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’sloady
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 aMemoryWordReader
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 usingfrom_tuple()
orfrom_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 ofMemoryPatch
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 adict
ortuple
, populated according toMemoryPatch.from_dict()
orMemoryPatch.from_tuple()
.