Module refinery.lib.dotnet.resources

Parsing of managed .NET resources, which are .NET resource directories which begin with the magic sequence 0xBEEFCACE. These resources can contain several entries of serialized data. The main reference used for this parser was the dnSpy source code.

Expand source code Browse git
# -*- coding: utf-8 -*-
"""
Parsing of managed .NET resources, which are .NET resource directories which
begin with the magic sequence `0xBEEFCACE`. These resources can contain several
entries of serialized data. The main reference used for this parser was the
dnSpy source code.
"""
import re

from refinery.lib.dotnet.deserialize import BinaryFormatterParser
from refinery.lib.dotnet.types import (
    Blob,
    Box,
    Byte,
    Char,
    LengthPrefixedString,
    StreamReader,
    StringPrimitive,
    EncodedInteger,
    Struct,
    UInt16,
    UInt32,
    Int16,
    Int32,
    Int64,
    SByte,
    Single,
    Double,
    Null,
    UInt64,
    unpack,
    DateTime,
    TimeSpan
)


class NoManagedResource(AssertionError):
    pass


class String(LengthPrefixedString):
    def __init__(self, reader):
        LengthPrefixedString.__init__(self, reader, codec='UTF-8')


class Boolean(Byte):
    @property
    def Value(self):
        return bool(super(Boolean, self).Value)


class Decimal(Blob):
    def __init__(self, reader):
        Blob.__init__(self, reader, 16)

    @property
    def Value(self):
        # TODO: Unknown whether this is correct
        return int.from_bytes(self._data, 'big')


class ByteArray(Struct):
    def parse(self):
        self.Size = self.expect(UInt32)
        self.Value = self._reader.read(self.Size)

    def __bytes__(self):
        return self.Value


class NetManifestResource(Struct):
    USERTYPES = 0x40
    PRIMITIVE = {
        0x00: Null,
        0x01: String,
        0x02: Boolean,
        0x03: Char,
        0x04: Byte,
        0x05: SByte,
        0x06: Int16,
        0x07: UInt16,
        0x08: Int32,
        0x09: UInt32,
        0x0A: Int64,
        0x0B: UInt64,
        0x0C: Single,
        0x0D: Double,
        0x0E: Decimal,
        0x0F: DateTime,
        0x10: TimeSpan,
        0x20: ByteArray,
        0x21: ByteArray,
    }

    def parse(self):
        self.Signature = self.expect(UInt32)
        if self.Signature != 0xBEEFCACE:
            raise NoManagedResource

        self.ReaderCount = self.expect(UInt32)
        self.ReaderTypeLength = self.expect(UInt32)

        tr = StreamReader(self._reader.read(self.ReaderTypeLength))
        self.ReaderType = tr.expect(StringPrimitive)
        self.ResourceSetType = tr.expect(StringPrimitive)

        if not re.match(r"^System\.Resources\.ResourceReader,\s*mscorlib", self.ReaderType):
            raise AssertionError('unknown resource reader')

        self.Version = self.expect(UInt32)
        ResourceCount = self.expect(UInt32)
        RsrcTypeCount = self.expect(UInt32)

        ResourceTypes = [
            self.expect(LengthPrefixedString)
            for _ in range(RsrcTypeCount)
        ]

        self._reader.align(8)
        self._reader.skip(4 * ResourceCount)

        # Since we do not require the resouce hashes, we skip over them.
        # The following would be the code to read in the hashes:
        #
        # ResourceHashes = [
        #     self.expect(UInt32)
        #     for _ in range(ResourceCount)
        # ]

        ResourceNameOffsets = [
            self.expect(UInt32)
            for _ in range(ResourceCount)
        ]

        self.DataSectionOffset = self.expect(UInt32)

        self.Resources = []

        for k in range(ResourceCount):
            with self._reader.checkpoint():
                self._reader.skip(ResourceNameOffsets[k])
                Name = self.expect(LengthPrefixedString, codec='UTF-16LE')
                Offset = self.expect(UInt32) + self.DataSectionOffset
                self.Resources.append(Box(Offset=Offset, Name=Name))

        self.Resources.sort(key=lambda r: r.Offset)
        self.Resources.append(Box(Offset=len(self._reader)))
        self.Resources = [
            Box(Size=b.Offset - a.Offset - 1, **a)
            for a, b in zip(self.Resources, self.Resources[1:])
        ]

        for Index, Entry in enumerate(self.Resources):

            self._reader.seek(Entry.Offset)
            TypeCode = self.expect(EncodedInteger)
            Entry.Error = None
            Entry.Value = Entry.Data = self._reader.read(Entry.Size)

            if TypeCode >= self.USERTYPES:
                Entry.TypeName = ResourceTypes[TypeCode - self.USERTYPES]
                try:
                    Deserialized = BinaryFormatterParser(
                        Entry.Data,
                        ignore_errors=False,
                        dereference=False,
                        keep_meta=False
                    )
                except Exception as error:
                    Entry.Error = 'failed to deserialize entry data: {}'.format(error)
                    continue
                try:
                    _, _, _, Data = Deserialized
                except ValueError:
                    Entry.Error = 'deserialized entry has {} records, 4 were expected.'.format(len(Deserialized))
                    continue
                if Data not in Entry.Data:
                    Entry.Error = 'the computed entry value is not a substring of the entry data.'
                    Entry.Value = Entry.Data
                else:
                    Entry.Value = Data

            elif TypeCode in self.PRIMITIVE:
                Type = self.PRIMITIVE[TypeCode]
                Entry.TypeName = repr(Type)
                package = StreamReader(Entry.Data).expect_with_meta(Type)
                Entry.Value = unpack(package)
            else:
                Entry.TypeName = 'UNKNOWN TYPE 0x{:X}'.format(TypeCode)


class NetStructuredResources(list):
    def __init__(self, data):
        list.__init__(self, NetManifestResource(StreamReader(data)).Resources)

Classes

class NoManagedResource (*args, **kwargs)

Assertion failed.

Expand source code Browse git
class NoManagedResource(AssertionError):
    pass

Ancestors

  • builtins.AssertionError
  • builtins.Exception
  • builtins.BaseException
class String (reader)
Expand source code Browse git
class String(LengthPrefixedString):
    def __init__(self, reader):
        LengthPrefixedString.__init__(self, reader, codec='UTF-8')

Ancestors

class Boolean (reader, fmt=None)
Expand source code Browse git
class Boolean(Byte):
    @property
    def Value(self):
        return bool(super(Boolean, self).Value)

Ancestors

Instance variables

var Value
Expand source code Browse git
@property
def Value(self):
    return bool(super(Boolean, self).Value)
class Decimal (reader)
Expand source code Browse git
class Decimal(Blob):
    def __init__(self, reader):
        Blob.__init__(self, reader, 16)

    @property
    def Value(self):
        # TODO: Unknown whether this is correct
        return int.from_bytes(self._data, 'big')

Ancestors

Instance variables

var Value
Expand source code Browse git
@property
def Value(self):
    # TODO: Unknown whether this is correct
    return int.from_bytes(self._data, 'big')
class ByteArray (reader, cleanup=True, **kw)

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

Expand source code Browse git
class ByteArray(Struct):
    def parse(self):
        self.Size = self.expect(UInt32)
        self.Value = self._reader.read(self.Size)

    def __bytes__(self):
        return self.Value

Ancestors

Methods

def parse(self)
Expand source code Browse git
def parse(self):
    self.Size = self.expect(UInt32)
    self.Value = self._reader.read(self.Size)
class NetManifestResource (reader, cleanup=True, **kw)

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

Expand source code Browse git
class NetManifestResource(Struct):
    USERTYPES = 0x40
    PRIMITIVE = {
        0x00: Null,
        0x01: String,
        0x02: Boolean,
        0x03: Char,
        0x04: Byte,
        0x05: SByte,
        0x06: Int16,
        0x07: UInt16,
        0x08: Int32,
        0x09: UInt32,
        0x0A: Int64,
        0x0B: UInt64,
        0x0C: Single,
        0x0D: Double,
        0x0E: Decimal,
        0x0F: DateTime,
        0x10: TimeSpan,
        0x20: ByteArray,
        0x21: ByteArray,
    }

    def parse(self):
        self.Signature = self.expect(UInt32)
        if self.Signature != 0xBEEFCACE:
            raise NoManagedResource

        self.ReaderCount = self.expect(UInt32)
        self.ReaderTypeLength = self.expect(UInt32)

        tr = StreamReader(self._reader.read(self.ReaderTypeLength))
        self.ReaderType = tr.expect(StringPrimitive)
        self.ResourceSetType = tr.expect(StringPrimitive)

        if not re.match(r"^System\.Resources\.ResourceReader,\s*mscorlib", self.ReaderType):
            raise AssertionError('unknown resource reader')

        self.Version = self.expect(UInt32)
        ResourceCount = self.expect(UInt32)
        RsrcTypeCount = self.expect(UInt32)

        ResourceTypes = [
            self.expect(LengthPrefixedString)
            for _ in range(RsrcTypeCount)
        ]

        self._reader.align(8)
        self._reader.skip(4 * ResourceCount)

        # Since we do not require the resouce hashes, we skip over them.
        # The following would be the code to read in the hashes:
        #
        # ResourceHashes = [
        #     self.expect(UInt32)
        #     for _ in range(ResourceCount)
        # ]

        ResourceNameOffsets = [
            self.expect(UInt32)
            for _ in range(ResourceCount)
        ]

        self.DataSectionOffset = self.expect(UInt32)

        self.Resources = []

        for k in range(ResourceCount):
            with self._reader.checkpoint():
                self._reader.skip(ResourceNameOffsets[k])
                Name = self.expect(LengthPrefixedString, codec='UTF-16LE')
                Offset = self.expect(UInt32) + self.DataSectionOffset
                self.Resources.append(Box(Offset=Offset, Name=Name))

        self.Resources.sort(key=lambda r: r.Offset)
        self.Resources.append(Box(Offset=len(self._reader)))
        self.Resources = [
            Box(Size=b.Offset - a.Offset - 1, **a)
            for a, b in zip(self.Resources, self.Resources[1:])
        ]

        for Index, Entry in enumerate(self.Resources):

            self._reader.seek(Entry.Offset)
            TypeCode = self.expect(EncodedInteger)
            Entry.Error = None
            Entry.Value = Entry.Data = self._reader.read(Entry.Size)

            if TypeCode >= self.USERTYPES:
                Entry.TypeName = ResourceTypes[TypeCode - self.USERTYPES]
                try:
                    Deserialized = BinaryFormatterParser(
                        Entry.Data,
                        ignore_errors=False,
                        dereference=False,
                        keep_meta=False
                    )
                except Exception as error:
                    Entry.Error = 'failed to deserialize entry data: {}'.format(error)
                    continue
                try:
                    _, _, _, Data = Deserialized
                except ValueError:
                    Entry.Error = 'deserialized entry has {} records, 4 were expected.'.format(len(Deserialized))
                    continue
                if Data not in Entry.Data:
                    Entry.Error = 'the computed entry value is not a substring of the entry data.'
                    Entry.Value = Entry.Data
                else:
                    Entry.Value = Data

            elif TypeCode in self.PRIMITIVE:
                Type = self.PRIMITIVE[TypeCode]
                Entry.TypeName = repr(Type)
                package = StreamReader(Entry.Data).expect_with_meta(Type)
                Entry.Value = unpack(package)
            else:
                Entry.TypeName = 'UNKNOWN TYPE 0x{:X}'.format(TypeCode)

Ancestors

Class variables

var USERTYPES
var PRIMITIVE

Methods

def parse(self)
Expand source code Browse git
def parse(self):
    self.Signature = self.expect(UInt32)
    if self.Signature != 0xBEEFCACE:
        raise NoManagedResource

    self.ReaderCount = self.expect(UInt32)
    self.ReaderTypeLength = self.expect(UInt32)

    tr = StreamReader(self._reader.read(self.ReaderTypeLength))
    self.ReaderType = tr.expect(StringPrimitive)
    self.ResourceSetType = tr.expect(StringPrimitive)

    if not re.match(r"^System\.Resources\.ResourceReader,\s*mscorlib", self.ReaderType):
        raise AssertionError('unknown resource reader')

    self.Version = self.expect(UInt32)
    ResourceCount = self.expect(UInt32)
    RsrcTypeCount = self.expect(UInt32)

    ResourceTypes = [
        self.expect(LengthPrefixedString)
        for _ in range(RsrcTypeCount)
    ]

    self._reader.align(8)
    self._reader.skip(4 * ResourceCount)

    # Since we do not require the resouce hashes, we skip over them.
    # The following would be the code to read in the hashes:
    #
    # ResourceHashes = [
    #     self.expect(UInt32)
    #     for _ in range(ResourceCount)
    # ]

    ResourceNameOffsets = [
        self.expect(UInt32)
        for _ in range(ResourceCount)
    ]

    self.DataSectionOffset = self.expect(UInt32)

    self.Resources = []

    for k in range(ResourceCount):
        with self._reader.checkpoint():
            self._reader.skip(ResourceNameOffsets[k])
            Name = self.expect(LengthPrefixedString, codec='UTF-16LE')
            Offset = self.expect(UInt32) + self.DataSectionOffset
            self.Resources.append(Box(Offset=Offset, Name=Name))

    self.Resources.sort(key=lambda r: r.Offset)
    self.Resources.append(Box(Offset=len(self._reader)))
    self.Resources = [
        Box(Size=b.Offset - a.Offset - 1, **a)
        for a, b in zip(self.Resources, self.Resources[1:])
    ]

    for Index, Entry in enumerate(self.Resources):

        self._reader.seek(Entry.Offset)
        TypeCode = self.expect(EncodedInteger)
        Entry.Error = None
        Entry.Value = Entry.Data = self._reader.read(Entry.Size)

        if TypeCode >= self.USERTYPES:
            Entry.TypeName = ResourceTypes[TypeCode - self.USERTYPES]
            try:
                Deserialized = BinaryFormatterParser(
                    Entry.Data,
                    ignore_errors=False,
                    dereference=False,
                    keep_meta=False
                )
            except Exception as error:
                Entry.Error = 'failed to deserialize entry data: {}'.format(error)
                continue
            try:
                _, _, _, Data = Deserialized
            except ValueError:
                Entry.Error = 'deserialized entry has {} records, 4 were expected.'.format(len(Deserialized))
                continue
            if Data not in Entry.Data:
                Entry.Error = 'the computed entry value is not a substring of the entry data.'
                Entry.Value = Entry.Data
            else:
                Entry.Value = Data

        elif TypeCode in self.PRIMITIVE:
            Type = self.PRIMITIVE[TypeCode]
            Entry.TypeName = repr(Type)
            package = StreamReader(Entry.Data).expect_with_meta(Type)
            Entry.Value = unpack(package)
        else:
            Entry.TypeName = 'UNKNOWN TYPE 0x{:X}'.format(TypeCode)
class NetStructuredResources (data)

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

Expand source code Browse git
class NetStructuredResources(list):
    def __init__(self, data):
        list.__init__(self, NetManifestResource(StreamReader(data)).Resources)

Ancestors

  • builtins.list