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
"""
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.
"""
from __future__ import annotations
import re
from refinery.lib.dotnet.deserialize import BinaryFormatterParser
from refinery.lib.dotnet.types import (
    Blob,
    Box,
    Byte,
    Char,
    DateTime,
    Double,
    EncodedInteger,
    Int16,
    Int32,
    Int64,
    LengthPrefixedString,
    Null,
    SByte,
    Single,
    StreamReader,
    StringPrimitive,
    Struct,
    TimeSpan,
    UInt16,
    UInt32,
    UInt64,
    unpack,
)
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().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 = f'failed to deserialize entry data: {error}'
                    continue
                try:
                    _, _, _, Data = Deserialized
                except ValueError:
                    Entry.Error = f'deserialized entry has {len(Deserialized)} records, 4 were expected.'
                    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 = f'UNKNOWN TYPE 0x{TypeCode:X}'
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 gitclass NoManagedResource(AssertionError): passAncestors- builtins.AssertionError
- builtins.Exception
- builtins.BaseException
 
- class String (reader)
- 
Expand source code Browse gitclass String(LengthPrefixedString): def __init__(self, reader): LengthPrefixedString.__init__(self, reader, codec='UTF-8')Ancestors
- class Boolean (reader, fmt=None)
- 
Expand source code Browse gitclass Boolean(Byte): @property def Value(self): return bool(super().Value)AncestorsInstance variables- var Value
- 
Expand source code Browse git@property def Value(self): return bool(super().Value)
 
- class Decimal (reader)
- 
Expand source code Browse gitclass 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')AncestorsInstance 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 gitclass ByteArray(Struct): def parse(self): self.Size = self.expect(UInt32) self.Value = self._reader.read(self.Size) def __bytes__(self): return self.ValueAncestorsMethods- def parse(self)
- 
Expand source code Browse gitdef 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 gitclass 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 = f'failed to deserialize entry data: {error}' continue try: _, _, _, Data = Deserialized except ValueError: Entry.Error = f'deserialized entry has {len(Deserialized)} records, 4 were expected.' 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 = f'UNKNOWN TYPE 0x{TypeCode:X}'AncestorsClass variables- var USERTYPES
- var PRIMITIVE
 Methods- def parse(self)
- 
Expand source code Browse gitdef 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 = f'failed to deserialize entry data: {error}' continue try: _, _, _, Data = Deserialized except ValueError: Entry.Error = f'deserialized entry has {len(Deserialized)} records, 4 were expected.' 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 = f'UNKNOWN TYPE 0x{TypeCode:X}'
 
- 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 gitclass NetStructuredResources(list): def __init__(self, data): list.__init__(self, NetManifestResource(StreamReader(data)).Resources)Ancestors- builtins.list