Module refinery.units.formats.pe.dotnet.dnsfx

Expand source code Browse git
from __future__ import annotations

from refinery.lib.dotnet.header import DotNetStructReader
from refinery.lib.id import buffer_contains
from refinery.lib.meta import SizeInt
from refinery.units.compression.zl import zl
from refinery.units.formats import PathExtractorUnit, UnpackResult


class dnsfx(PathExtractorUnit, docs='{0}{p}{PathExtractorUnit}'):
    """
    Extracts files from .NET single file applications.
    """
    _SIGNATURE = bytes([
        # 32 bytes represent the bundle signature: SHA-256 for '.net core bundle'
        0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
        0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
        0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
        0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
    ])

    def unpack(self, data):
        reader = DotNetStructReader(memoryview(data))
        reader.seek(self._find_bundle_manifest_offset(data))

        major_version = reader.u32()
        minor_version = reader.u32()
        self.log_info(F'version {major_version}.{minor_version}')

        count = reader.u32()
        bhash = reader.read_dn_string_primitive()
        self.log_info(F'bundle {bhash} contains {count} files')

        if major_version >= 2:
            reader.u64() # depsOffset
            reader.u64() # depsSize
            reader.u64() # runtimeConfigOffset
            reader.u64() # runtimeConfigSize
            reader.u64() # flags

        for _ in range(count):
            try:
                offset = reader.u64()
                size = reader.u64()
                compressed_size = 0
                if major_version >= 6:
                    compressed_size = reader.u64()
                type = reader.u8()
                path = reader.read_dn_string_primitive()

                def _logmsg():
                    _log = F'read item at offset 0x{offset:08X}, type 0x{type:02X}, size {SizeInt(size)!r}'
                    if compressed_size:
                        return F'{_log}, compressed to size {SizeInt(compressed_size)!r}'
                    return F'{_log}, uncompressed'

                self.log_debug(_logmsg)

                with reader.detour():
                    reader.seek(offset)
                    if compressed_size:
                        item_data = reader.read(compressed_size) | zl | bytearray
                    else:
                        item_data = reader.read(size)

                yield UnpackResult(path, item_data)
            except EOFError:
                self.log_warn('unexpected EOF while parsing bundle, terminating')
                break

    def _find_bundle_manifest_offset(self, data: bytearray) -> int:
        bundle_sig_offset = data.find(self._SIGNATURE, 0)
        if bundle_sig_offset < 0:
            raise ValueError('Cannot find valid Bundle Manifest offset. Is this a .NET Bundle?')
        return int.from_bytes(data[bundle_sig_offset - 8:bundle_sig_offset], 'little')

    @classmethod
    def handles(cls, data):
        return buffer_contains(data, cls._SIGNATURE)

Classes

class dnsfx (*paths, exclude=None, list=False, join_path=False, drop_path=False, fuzzy=0, exact=False, regex=False, path=b'path')

Extracts files from .NET single file applications.

This unit extracts items with an associated virtual path from a container; each extracted item is emitted as a separate chunk with a corresponding meta variable named "path".

Positional arguments to dnsfx are patterns to filter the extracted items. Use the -x flag to add an exclusion pattern. To extract all files with a foo or bar extension, but none that has the word "temp" in its path:

dnsfx .foo .bar -x temp

To view only the paths of all chunks, use the listing switch:

emit data | ... | dnsfx -l

Otherwise, extracted items are written to the standard output port and usually require a frame to properly process. In order to dump all extracted data to disk, the following pipeline can be used:

emit data | ... | dnsfx [| dump extracted/{path} ]

The value {path} is a placeholder which is substituted by the virtual path of the extracted item. When using dnsfx to unpack a file on disk, the following pattern can be useful:

ef pack.bin [| dnsfx -j | d2p ]

The unit ef is also a path extractor. By specifying -j (or --join), the paths of extracted items are combined. Here, d2p is a shortcut for dump {path}. It deconflicts the joined paths with the local file system: If pack.bin contains items one.txt and two.txt, the following local file tree would be the result:

pack.bin
pack/one.txt
pack/two.txt

Finally, the -d (or --drop) switch can be used to not create (or alter) the path metadata at all, which is useful in cases where path metadata from a previous unit should be preserved.

Expand source code Browse git
class dnsfx(PathExtractorUnit, docs='{0}{p}{PathExtractorUnit}'):
    """
    Extracts files from .NET single file applications.
    """
    _SIGNATURE = bytes([
        # 32 bytes represent the bundle signature: SHA-256 for '.net core bundle'
        0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
        0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
        0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
        0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
    ])

    def unpack(self, data):
        reader = DotNetStructReader(memoryview(data))
        reader.seek(self._find_bundle_manifest_offset(data))

        major_version = reader.u32()
        minor_version = reader.u32()
        self.log_info(F'version {major_version}.{minor_version}')

        count = reader.u32()
        bhash = reader.read_dn_string_primitive()
        self.log_info(F'bundle {bhash} contains {count} files')

        if major_version >= 2:
            reader.u64() # depsOffset
            reader.u64() # depsSize
            reader.u64() # runtimeConfigOffset
            reader.u64() # runtimeConfigSize
            reader.u64() # flags

        for _ in range(count):
            try:
                offset = reader.u64()
                size = reader.u64()
                compressed_size = 0
                if major_version >= 6:
                    compressed_size = reader.u64()
                type = reader.u8()
                path = reader.read_dn_string_primitive()

                def _logmsg():
                    _log = F'read item at offset 0x{offset:08X}, type 0x{type:02X}, size {SizeInt(size)!r}'
                    if compressed_size:
                        return F'{_log}, compressed to size {SizeInt(compressed_size)!r}'
                    return F'{_log}, uncompressed'

                self.log_debug(_logmsg)

                with reader.detour():
                    reader.seek(offset)
                    if compressed_size:
                        item_data = reader.read(compressed_size) | zl | bytearray
                    else:
                        item_data = reader.read(size)

                yield UnpackResult(path, item_data)
            except EOFError:
                self.log_warn('unexpected EOF while parsing bundle, terminating')
                break

    def _find_bundle_manifest_offset(self, data: bytearray) -> int:
        bundle_sig_offset = data.find(self._SIGNATURE, 0)
        if bundle_sig_offset < 0:
            raise ValueError('Cannot find valid Bundle Manifest offset. Is this a .NET Bundle?')
        return int.from_bytes(data[bundle_sig_offset - 8:bundle_sig_offset], 'little')

    @classmethod
    def handles(cls, data):
        return buffer_contains(data, cls._SIGNATURE)

Ancestors

Subclasses

Class variables

var reverse

The type of the None singleton.

Methods

def unpack(self, data)
Expand source code Browse git
def unpack(self, data):
    reader = DotNetStructReader(memoryview(data))
    reader.seek(self._find_bundle_manifest_offset(data))

    major_version = reader.u32()
    minor_version = reader.u32()
    self.log_info(F'version {major_version}.{minor_version}')

    count = reader.u32()
    bhash = reader.read_dn_string_primitive()
    self.log_info(F'bundle {bhash} contains {count} files')

    if major_version >= 2:
        reader.u64() # depsOffset
        reader.u64() # depsSize
        reader.u64() # runtimeConfigOffset
        reader.u64() # runtimeConfigSize
        reader.u64() # flags

    for _ in range(count):
        try:
            offset = reader.u64()
            size = reader.u64()
            compressed_size = 0
            if major_version >= 6:
                compressed_size = reader.u64()
            type = reader.u8()
            path = reader.read_dn_string_primitive()

            def _logmsg():
                _log = F'read item at offset 0x{offset:08X}, type 0x{type:02X}, size {SizeInt(size)!r}'
                if compressed_size:
                    return F'{_log}, compressed to size {SizeInt(compressed_size)!r}'
                return F'{_log}, uncompressed'

            self.log_debug(_logmsg)

            with reader.detour():
                reader.seek(offset)
                if compressed_size:
                    item_data = reader.read(compressed_size) | zl | bytearray
                else:
                    item_data = reader.read(size)

            yield UnpackResult(path, item_data)
        except EOFError:
            self.log_warn('unexpected EOF while parsing bundle, terminating')
            break

Inherited members