Skip to content

openwrt.py

ofrak_components.openwrt

OpenWrtIdentifier (Identifier)

identify(self, resource, config=None) async

Perform identification on the given resource.

Users should not call this method directly; rather, they should run Resource.identify.

Parameters:

Name Type Description Default
resource Resource required
config

Optional config for identifying. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_components/openwrt.py
async def identify(self, resource: Resource, config=None) -> None:
    trx_magic = await resource.get_data(range=Range(0, 4))
    if trx_magic == OPENWRT_TRX_MAGIC_BYTES:
        resource.add_tag(OpenWrtTrx)

OpenWrtTrx (GenericBinary) dataclass

OpenWrtTrx is a TRX binary update for OpenWrt firmware.

TRX binaries are used for upgrading the firmware of devices already running OpenWrt firmware

The OpenWrtTrx consists of 1 OpenWrtTrxHeader and 3 or 4 partition(s): - OpenWrtTrxLzmaLoader - OpenWrtTrxKernel - OpenWrtTrxRootfs - OpenWrtTrxBinheader (in TRX v2)

OpenWrtTrxHeader (ResourceView) dataclass

OpenWrt trx header Information from https://openwrt.org/docs/techref/headers

TRX v1
 0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------------------------------------------------------+
 |                     magic number ('HDR0')                     |
 +---------------------------------------------------------------+
 |                  length (header size + data)                  |
 +---------------+---------------+-------------------------------+
 |                       32-bit CRC value                        |
 +---------------+---------------+-------------------------------+
 |           TRX flags           |          TRX version          |
 +-------------------------------+-------------------------------+
 |                      Partition offset[0]                      |
 +---------------------------------------------------------------+
 |                      Partition offset[1]                      |
 +---------------------------------------------------------------+
 |                      Partition offset[2]                      |
 +---------------------------------------------------------------+
offset[0] = lzma-loader
offset[1] = Linux-Kernel
offset[2] = rootfs

TRX v2
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------------------------------------------------------+
 |                     magic number ('HDR0')                     |
 +---------------------------------------------------------------+
 |                  length (header size + data)                  |
 +---------------+---------------+-------------------------------+
 |                       32-bit CRC value                        |
 +---------------+---------------+-------------------------------+
 |           TRX flags           |          TRX version          |
 +-------------------------------+-------------------------------+
 |                      Partition offset[0]                      |
 +---------------------------------------------------------------+
 |                      Partition offset[1]                      |
 +---------------------------------------------------------------+
 |                      Partition offset[2]                      |
 +---------------------------------------------------------------+
 |                      Partition offset[3]                      |
 +---------------------------------------------------------------+
offset[0] = lzma-loader
offset[1] = Linux-Kernel
offset[2] = rootfs
offset[3] = bin-Header

OpenWrtTrxHeaderAttributesAnalyzer (Analyzer)

Analyze the OpenWrtTrxHeader of a OpenWrtTrx firmware file.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
OpenWrtTrxHeader

The analysis results

Source code in ofrak_components/openwrt.py
async def analyze(self, resource: Resource, config=None) -> OpenWrtTrxHeader:
    tmp = await resource.get_data()
    deserializer = BinaryDeserializer(
        io.BytesIO(tmp),
        endianness=Endianness.LITTLE_ENDIAN,
        word_size=4,
    )
    deserialized = deserializer.unpack_multiple("IIIHH")
    (
        trx_magic,
        trx_length,
        trx_crc,
        trx_flags,
        trx_version,
    ) = deserialized
    assert trx_magic == OPENWRT_TRX_MAGIC_START

    if trx_version == OpenWrtTrxVersion.VERSION1.value:
        deserialized = deserializer.unpack_multiple("III")
        (
            trx_loader_offset,
            trx_kernel_offset,
            trx_rootfs_offset,
        ) = deserialized
        header = OpenWrtTrxHeader(
            trx_magic,
            trx_length,
            trx_crc,
            trx_flags,
            trx_version,
            trx_loader_offset,
            trx_kernel_offset,
            trx_rootfs_offset,
            None,
        )
    elif trx_version == OpenWrtTrxVersion.VERSION2.value:
        deserialized = deserializer.unpack_multiple("IIII")
        (
            trx_loader_offset,
            trx_kernel_offset,
            trx_rootfs_offset,
            trx_binheader_offset,
        ) = deserialized
        header = OpenWrtTrxHeader(
            trx_magic,
            trx_length,
            trx_crc,
            trx_flags,
            trx_version,
            trx_loader_offset,
            trx_kernel_offset,
            trx_rootfs_offset,
            trx_binheader_offset,
        )
    else:
        raise ValueError(f"Unknown OpenWrt TRX version: {trx_version}")

    return header

OpenWrtTrxHeaderModifier (Modifier)

Modify a OpenWrtTrxHeader according to a given modifier config.

modify(self, resource, config) async

Modify the given resource.

Users should not call this method directly; rather, they should run Resource.run.

Parameters:

Name Type Description Default
resource Resource required
config OpenWrtTrxHeaderModifierConfig

Optional config for modification. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

required
Source code in ofrak_components/openwrt.py
async def modify(self, resource: Resource, config: OpenWrtTrxHeaderModifierConfig) -> None:
    original_attributes = await resource.analyze(OpenWrtTrxHeader.attributes_type)
    new_attributes = ResourceAttributes.replace_updated(original_attributes, config)
    serialized_header = await OpenWrtTrxHeaderModifier.serialize(new_attributes)
    header_v = await resource.view_as(OpenWrtTrxHeader)
    resource.queue_patch(
        Range.from_size(0, header_v.get_header_length()),
        serialized_header,
    )
    resource.add_attributes(new_attributes)

serialize(updated_attributes) async staticmethod

Serialize updated_attributes into bytes. This method doesn't perform any check or compute any CRC.

Source code in ofrak_components/openwrt.py
@staticmethod
async def serialize(
    updated_attributes: OpenWrtTrxHeader.attributes_type,  # type: ignore
) -> bytes:
    """
    Serialize `updated_attributes` into bytes. This method doesn't perform any check or compute
    any CRC.
    """
    if updated_attributes.trx_version == OpenWrtTrxVersion.VERSION1.value:
        return struct.pack(
            "<IIIHHIII",
            OPENWRT_TRX_MAGIC_START,
            updated_attributes.trx_length,
            updated_attributes.trx_crc,
            updated_attributes.trx_flags,
            updated_attributes.trx_version,
            updated_attributes.trx_loader_offset,
            updated_attributes.trx_kernel_offset,
            updated_attributes.trx_rootfs_offset,
        )
    elif updated_attributes.trx_version == OpenWrtTrxVersion.VERSION2.value:
        return struct.pack(
            "<IIIHHIIII",
            OPENWRT_TRX_MAGIC_START,
            updated_attributes.trx_length,
            updated_attributes.trx_crc,
            updated_attributes.trx_flags,
            updated_attributes.trx_version,
            updated_attributes.trx_loader_offset,
            updated_attributes.trx_kernel_offset,
            updated_attributes.trx_rootfs_offset,
            updated_attributes.trx_binheader_offset,
        )
    else:
        raise ValueError(f"Unknown OpenWrt TRX version: {updated_attributes.trx_version}")

OpenWrtTrxHeaderModifierConfig (ComponentConfig) dataclass

Modifier config for a OpenWrtTrxHeader.

The following field is not modifiable: - Image Header Magic Number (a constant): trx_magic

The following fields are modifiable, but will be overwritten by the packer: - Image Data Size: trx_length - Image CRC Checksum: trx_crc - LZMA Loader offset: trx_loader_offset - Kernel offset: trx_kernel_offset - Filesystem offset: trx_rootfs_offset - BinHeader offset, applicable for TRX Version 2: trx_binheader_offset

OpenWrtTrxPacker (Packer)

Pack an OpenWrtTrx firmware file.

It consolidates the OpenWrtTrxHeader and all partition instances into a single binary, updating the CRC checksum, data size, and partition offsets in the header.

pack(self, resource, config=None) async

Pack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.pack.

Parameters:

Name Type Description Default
resource Resource required
config

Optional config for packing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_components/openwrt.py
async def pack(self, resource: Resource, config=None):
    openwrt_v = await resource.view_as(OpenWrtTrx)
    header = await openwrt_v.get_header()
    lzma_loader = await resource.get_only_child_as_view(
        OpenWrtTrxLzmaLoader, ResourceFilter.with_tags(OpenWrtTrxLzmaLoader)
    )
    kernel = await resource.get_only_child_as_view(
        OpenWrtTrxKernel, ResourceFilter.with_tags(OpenWrtTrxKernel)
    )
    rootfs = await resource.get_only_child_as_view(
        OpenWrtTrxRootfs, ResourceFilter.with_tags(OpenWrtTrxRootfs)
    )
    if header.get_version() == OpenWrtTrxVersion.VERSION2:
        binheader = await resource.get_only_child_as_view(
            OpenWrtTrxBinheader, ResourceFilter.with_tags(OpenWrtTrxBinheader)
        )

    repacked_data_l = []
    trx_loader_offset = header.get_header_length()
    repacked_data_l.append(await lzma_loader.resource.get_data())

    trx_kernel_offset = trx_loader_offset + len(repacked_data_l[-1])
    repacked_data_l.append(await kernel.resource.get_data())

    trx_rootfs_offset = trx_kernel_offset + len(repacked_data_l[-1])
    repacked_data_l.append(await rootfs.resource.get_data())

    if header.get_version() == OpenWrtTrxVersion.VERSION2:
        trx_binheader_offset = trx_rootfs_offset + len(repacked_data_l[-1])
        repacked_data_l.append(await binheader.resource.get_data())

    repacked_data_b = b"".join(repacked_data_l)
    trx_length = header.get_header_length() + len(repacked_data_b)

    data_to_crc = struct.pack("<H", header.trx_flags)
    data_to_crc += struct.pack("<H", header.trx_version)
    data_to_crc += struct.pack("<I", trx_loader_offset)
    data_to_crc += struct.pack("<I", trx_kernel_offset)
    data_to_crc += struct.pack("<I", trx_rootfs_offset)
    if header.get_version() == OpenWrtTrxVersion.VERSION2:
        data_to_crc += struct.pack("<I", trx_binheader_offset)
    data_to_crc += repacked_data_b

    if header.get_version() == OpenWrtTrxVersion.VERSION1:
        header_config = OpenWrtTrxHeaderModifierConfig(
            trx_length=trx_length,
            trx_crc=openwrt_crc32(data_to_crc),
            trx_loader_offset=trx_loader_offset,
            trx_kernel_offset=trx_kernel_offset,
            trx_rootfs_offset=trx_rootfs_offset,
        )
    else:
        header_config = OpenWrtTrxHeaderModifierConfig(
            trx_length=trx_length,
            trx_crc=openwrt_crc32(data_to_crc),
            trx_loader_offset=trx_loader_offset,
            trx_kernel_offset=trx_kernel_offset,
            trx_rootfs_offset=trx_rootfs_offset,
            trx_binheader_offset=trx_binheader_offset,
        )
    await header.resource.run(OpenWrtTrxHeaderModifier, header_config)

    original_size = await resource.get_data_length()
    resource.queue_patch(Range(header.get_header_length(), original_size), repacked_data_b)

OpenWrtTrxUnpacker (Unpacker)

Unpack an OpenWrtTrx firmware file into: - OpenWrtTrxHeader - OpenWrtTrxLzmaLoader - OpenWrtTrxKernel - OpenWrtTrxRootfs - OpenWrtTrxBinheader (if TRX v2)

The header has a mapped data range, whereas all partitions are unmapped data.

unpack(self, resource, config=None) async

Unpack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.unpack.

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak_components/openwrt.py
async def unpack(self, resource: Resource, config=None):
    data = await resource.get_data()
    # Peek into TRX version to know how big the header is
    trx_version = OpenWrtTrxVersion(struct.unpack("<H", data[14:16])[0])
    if trx_version == OpenWrtTrxVersion.VERSION1:
        trx_header_r = await resource.create_child(
            tags=(OpenWrtTrxHeader,), data_range=Range(0, OPENWRT_TRXV1_HEADER_LEN)
        )
    elif trx_version == OpenWrtTrxVersion.VERSION2:
        trx_header_r = await resource.create_child(
            tags=(OpenWrtTrxHeader,), data_range=Range(0, OPENWRT_TRXV2_HEADER_LEN)
        )
    else:
        raise UnpackerError(f"Unknown OpenWrt TRX version: {trx_version}")

    trx_header = await trx_header_r.view_as(OpenWrtTrxHeader)
    # Create lzma loader child
    await resource.create_child(
        tags=(OpenWrtTrxLzmaLoader,),
        data=data[trx_header.trx_loader_offset : trx_header.trx_kernel_offset],
    )
    # Create kernel child
    await resource.create_child(
        tags=(OpenWrtTrxKernel,),
        data=data[trx_header.trx_kernel_offset : trx_header.trx_rootfs_offset],
    )
    if trx_version == OpenWrtTrxVersion.VERSION1:
        # Create rootfs child
        await resource.create_child(
            tags=(OpenWrtTrxRootfs,),
            data=data[trx_header.trx_rootfs_offset :],
        )
    else:  # TRX Version 2
        # Create rootfs child
        await resource.create_child(
            tags=(OpenWrtTrxRootfs,),
            data=data[trx_header.trx_rootfs_offset : trx_header.trx_binheader_offset],
        )
        # Create binHeader child
        await resource.create_child(
            tags=(OpenWrtTrxBinheader,),
            data=data[trx_header.trx_binheader_offset :],
        )

OpenWrtTrxVersion (Enum)

An enumeration.

openwrt_crc32(data)

Calculate CRC32 a-la OpenWrt. Original implementation: https://git.archive.openwrt.org/?p=14.07/openwrt.git;a=blob;f=tools/firmware-utils/src/trx.c

Implements CRC-32 Ethernet which requires XOR'ing the zlib.crc32 result with 0xFFFFFFFF

Source code in ofrak_components/openwrt.py
def openwrt_crc32(data: bytes) -> int:
    """
    Calculate CRC32 a-la OpenWrt. Original implementation:
    <https://git.archive.openwrt.org/?p=14.07/openwrt.git;a=blob;f=tools/firmware-utils/src/trx.c>

    Implements CRC-32 Ethernet which requires XOR'ing the zlib.crc32 result with 0xFFFFFFFF
    """
    return (zlib.crc32(data) & 0xFFFFFFFF) ^ 0xFFFFFFFF