From 7a46066a9a6fa16e32fc2a7d003bb0eb42cae673 Mon Sep 17 00:00:00 2001 From: Shiz Date: Mon, 1 Jun 2020 19:34:15 +0200 Subject: [PATCH] epoch --- .gitignore | 2 + dotnet/__init__.py | 0 dotnet/__main__.py | 17 ++ dotnet/common.py | 229 ++++++++++++++++++ dotnet/file.py | 175 ++++++++++++++ dotnet/streams.py | 93 ++++++++ dotnet/tables.py | 568 +++++++++++++++++++++++++++++++++++++++++++++ dotnet/types.py | 253 ++++++++++++++++++++ 8 files changed, 1337 insertions(+) create mode 100644 .gitignore create mode 100644 dotnet/__init__.py create mode 100644 dotnet/__main__.py create mode 100644 dotnet/common.py create mode 100644 dotnet/file.py create mode 100644 dotnet/streams.py create mode 100644 dotnet/tables.py create mode 100644 dotnet/types.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d35cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.pyc diff --git a/dotnet/__init__.py b/dotnet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dotnet/__main__.py b/dotnet/__main__.py new file mode 100644 index 0000000..5e149df --- /dev/null +++ b/dotnet/__main__.py @@ -0,0 +1,17 @@ +import sys + +from dotnet.types import CLRSignature +from dotnet.tables import CLRTableType +from dotnet.file import CLRFile + +file = CLRFile(sys.argv[1]) +print(f'Entrypoint: {file.entrypoint.name}') +for cls in file.get_table(CLRTableType.TypeDef): + print('---') + print(f'Class: {cls.name}') + print('Fields:') + for f in cls.fields: + print(f'- {f.name}: {f.signature}') + print('Methods:') + for m in cls.methods: + print(f'- {m.name}: {m.signature}') diff --git a/dotnet/common.py b/dotnet/common.py new file mode 100644 index 0000000..7d87880 --- /dev/null +++ b/dotnet/common.py @@ -0,0 +1,229 @@ +import enum +import math +import collections +from destruct import Type, Struct + +class CLRStreamType(enum.Enum): + Metadata = '#~' + String = '#Strings' + UserString = '#US' + Blob = '#Blob' + GUID = '#GUID' + +class CLRTableType(enum.Enum): + Assembly = 0x20 + AssemblyCPU = 0x21 + AssemblyOS = 0x22 + AssemblyRef = 0x23 + AssemblyRefCPU = 0x24 + AssemblyRefOS = 0x25 + ClassLayout = 0x0F + Constant = 0x0B + CustomAttribute = 0x0C + DeclSecurity = 0x0E + EncLog = 0x1E + EncMap = 0x1F + EventMap = 0x12 + Event = 0x14 + EventPointer = 0x13 + ExportedType = 0x27 + Field = 0x04 + FieldLayout = 0x10 + FieldMarshal = 0x0D + FieldPointer = 0x03 + FieldRVA = 0x1D + File = 0x26 + GenericParam = 0x2A + GenericParamConstraint = 0x2C + ImplMap = 0x1C + InterfaceImpl = 0x09 + ManifestResource = 0x28 + MemberRef = 0x0A + MethodDef = 0x06 + MethodImpl = 0x19 + MethodPointer = 0x05 + MethodSemantics = 0x18 + MethodSpec = 0x2B + Module = 0x00 + ModuleRef = 0x1A + NestedClass = 0x29 + Param = 0x08 + ParamPointer = 0x07 + Property = 0x17 + PropertyMap = 0x15 + PropertyPointer = 0x16 + StandAloneSig = 0x11 + TypeDef = 0x02 + TypeRef = 0x01 + TypeSpec = 0x1B + Document = 0x30 + MethodBody = 0x31 + LocalScope = 0x32 + LocalVariable = 0x33 + LocalConstant = 0x34 + ImportScope = 0x35 + StateMachineMethod = 0x36 + CustomDebugInformation = 0x37 + +class CLRElementType(enum.Enum): + End = 0x00 + Void = 0x01 + Boolean = 0x02 + Char = 0x03 + I1 = 0x04 + U1 = 0x05 + I2 = 0x06 + U2 = 0x07 + I4 = 0x08 + U4 = 0x09 + I8 = 0x0A + U8 = 0x0B + R4 = 0x0C + R8 = 0x0D + String = 0x0E + Pointer = 0x0F + ByRef = 0x10 + ValueType = 0x11 + Class = 0x12 + Var = 0x13 + Array = 0x14 + GenericInst = 0x15 + TypedByRef = 0x16 + I = 0x18 + U = 0x19 + FnPtr = 0x1B + Object = 0x1C + SZArray = 0x1D + MVar = 0x1E + ReqModifier = 0x1F + OptModifier = 0x20 + Internal = 0x21 + Modifier = 0x40 + Sentinel = 0x41 + Pinned = 0x45 + System = 0x50 + Boxed = 0x51 + Field = 0x53 + Property = 0x54 + Enum = 0x55 + +class CLRHeapFlags(enum.Flag): + BigStringStream = 1 + BigGUIDStream = 2 + BigBlobStream = 4 + +class CLRToken(Struct): + row = UInt(24) + table = Enum(CLRTableType, UInt(8)) + +class CLRCodedToken(Type): + def __init__(self, types): + self.types = types + + def parse(self, input, context): + metadata = context.user.metadata + row_counts = [] + for t in self.types: + row_counts.append(metadata.row_counts.get(t, 0)) + max_rows = max(row_counts) + table_bits = math.ceil(math.log(len(self.types), 2)) + if max_rows < (1 << 16 - table_bits): + size = 2 + else: + size = 4 + val = int.from_bytes(input.read(size), 'little') + row = val >> table_bits + tag = val & (1 << (table_bits - 1)) + return CLRToken(row=row, table=self.types[tag]) + +class CLRSectionReference(Struct): + rva = UInt(32) + size = UInt(32) + +class CLRTableIndex(Type): + def __init__(self, type): + self.type = type + + def parse(self, input, context): + metadata = context.user.metadata + row_count = metadata.row_counts.get(self.type, 0) + if row_count < (1 << 16): + size = 2 + else: + size = 4 + return int.from_bytes(input.read(size), 'little') + +class CLRTableRange(Type): + def __init__(self, type): + self.type = type + + def parse(self, input, context): + metadata = context.user.metadata + row_count = metadata.row_counts.get(self.type, 0) + if row_count < (1 << 16): + size = 2 + else: + size = 4 + return int.from_bytes(input.read(size), 'little') + +class CLRStreamIndex(Type): + def __init__(self, type, child=None): + self.type = type + self.child = child + + def parse(self, input, context): + metadata = context.user.metadata + big_mapping = { + CLRStreamType.String: CLRHeapFlags.BigStringStream, + CLRStreamType.Blob: CLRHeapFlags.BigBlobStream, + CLRStreamType.GUID: CLRHeapFlags.BigGUIDStream, + } + flag = big_mapping.get(self.type, None) + if flag: + if metadata.heap_flags & flag: + size = 4 + else: + size = 2 + else: + size = 2 + return int.from_bytes(input.read(size), 'little') + +class MultiEnumMeta(type): + @classmethod + def __prepare__(mcls, name, bases, **kwargs): + return collections.OrderedDict() + + def __new__(cls, name, bases, attrs, **kwargs): + parsers = {} + used = 0 + for key, value in attrs.copy().items(): + if isinstance(value, tuple): + if len(value) == 2: + child, mask = value + elif len(value) == 3: + child, offset, size = value + mask = ((1 << size) - 1) << offset + if issubclass(child, enum.Enum): + if mask is None: + mask = ~used + parsers[key] = (child, mask) + used |= mask + del attrs[key] + + attrs['_parsers_'] = parsers + return super().__new__(cls, name, bases, attrs) + +class MultiEnum(metaclass=MultiEnumMeta): + def __init__(self, value: int): + for field, (child, mask) in self._parsers_.items(): + setattr(self, field, child(value & mask)) + + def __str__(self): + return '{}({})'.format(self.__class__.__name__, + ', '.join('{}: {}'.format(n, getattr(self, n)) for n in self._parsers_) + ) + + def __repr__(self): + return '<{}({})>'.format(self.__class__.__name__, + ', '.join('{}: {!r}'.format(n, getattr(self, n)) for n in self._parsers_) + ) diff --git a/dotnet/file.py b/dotnet/file.py new file mode 100644 index 0000000..aa335c2 --- /dev/null +++ b/dotnet/file.py @@ -0,0 +1,175 @@ +import pefile +import destruct +from destruct import Type, Struct, Arr + +from .common import CLRSectionReference, CLRToken, CLRStreamType, CLRStreamIndex, CLRTableIndex, CLRTableRange +from .streams import STREAM_PARSERS + + +class CLRHeader(Struct): + size = UInt(32) + version_major = UInt(16) + version_minor = UInt(16) + metadata_info = CLRSectionReference + flags = UInt(32) + entrypoint = CLRToken + unk_info = CLRSectionReference + namesig_info = CLRSectionReference + _gap0x28 = Data(32) + +class CLRStreamMetadata(Struct): + offset = UInt(32) + size = UInt(32) + name = AlignTo(Str(kind='c'), 4) + +class CLRMetadataHeader(Struct): + magic = Sig(b'BSJB') + version_major = UInt(16) + version_minor = UInt(16) + _gap0x8 = Data(4) + version = Str(kind='pascal', length_type=UInt(32)) + _gap0x12 = Data(2) + stream_count = UInt(16) + streams = Arr(CLRStreamMetadata) + + def on_stream_count(self, spec, context): + spec.streams.count = self.stream_count + +class CLRTableWrapper: + __slots__ = ('__file', '__table', '__index', '__child') + + def __init__(self, file, table, index, child): + self.__file = file + self.__table = table + self.__index = index + self.__child = child + + def __getattr__(self, name): + val = getattr(self.__child, name) + if isinstance(self.__child, destruct.Struct): + type = self.__child._spec[name] + if isinstance(type, CLRStreamIndex): + stream = type.type + if stream == CLRStreamType.String: + val = self.__file.get_string_at(val) + elif stream == CLRStreamType.Blob: + val = self.__file.get_blob_at(val) + elif stream == CLRStreamType.UserString: + val = self.__file.get_user_string_at(val) + elif stream == CLRStreamType.GUID: + val = self.__file.get_guid_at(val) + if type.child: + c = destruct.Context(type.child) + c.user.metadata = self.__file.streams[CLRStreamType.Metadata] + val = destruct.parse(type.child, val, c) + return val + elif isinstance(type, CLRTableIndex): + table = type.type + return self.__file.get_table_entry(table, val) + elif isinstance(type, CLRTableRange): + table = type.type + if self.__index + 1 < self.__file.get_table_size(self.__table): + next = self.__file.get_table_entry(self.__table, self.__index + 1) + end = min(self.__file.get_table_size(table), getattr(next.__child, name)) + else: + end = self.__file.get_table_size(table) + return [self.__file.get_table_entry(table, i) for i in range(val, end)] + return val + + def __repr__(self): + return '<{}: {!r} in {!r}>'.format(self.__class__.__name__, self.__child, self.__file) + +class CLRFile: + def __init__(self, fn): + self.name = fn + self.pe = pefile.PE(fn) + self.header = None + self.metadata = None + self.streams = {} + self.parse() + + def __repr__(self): + return '<{}: "{}">'.format(self.__class__.__name__, self.name) + + def parse_at(self, c, offset, size=None): + if size is None: + size = destruct.sizeof(c) + buf = self.pe.get_data(rva=offset, length=size) + return destruct.parse(c, buf) + + def parse(self): + net_data_entry = pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR'] + if net_data_entry < len(self.pe.OPTIONAL_HEADER.DATA_DIRECTORY): + net_data_dir = self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[net_data_entry] + net_offset = net_data_dir.VirtualAddress + net_size = net_data_dir.Size + else: + for section in self.pe.sections: + if section.Name.rstrip(b'\x00') != b'.text': + continue + net_offset = section.PointerToRawData + 8 + net_size = 72 + + self.header = self.parse_at(CLRHeader, net_offset, net_size) + self.metadata = self.parse_at(CLRMetadataHeader, self.header.metadata_info.rva, self.header.metadata_info.size) + self.streams = {} + for s in self.metadata.streams: + type = CLRStreamType(s.name) + if type in STREAM_PARSERS: + stream = self.parse_at(STREAM_PARSERS[type], self.header.metadata_info.rva + s.offset, s.size) + else: + stream = None + self.streams[type] = stream + + def get_table(self, t): + return (CLRTableWrapper(self, t, i, x) for i, x in enumerate(self.streams[CLRStreamType.Metadata].tables[t])) + + def get_table_size(self, t): + return len(self.streams[CLRStreamType.Metadata].tables[t]) + + def get_table_entry(self, t, i): + return CLRTableWrapper(self, t, i, self.streams[CLRStreamType.Metadata].tables[t][i]) + + def get_string_at(self, i): + buf = bytearray() + while True: + c = self.streams[CLRStreamType.String].data[i] + if not c: + break + buf.append(c) + i += 1 + return buf.decode('utf-8') + + def _get_length_prefixed_value(self, stream, i): + length = self.streams[stream].data[i] + if (length >> 5) == 0b110: + length = length & 0b11111 + nbytes = 4 + elif (length >> 6) == 0b10: + length = length & 0b111111 + nbytes = 2 + else: + nbytes = 1 + for off in range(nbytes - 1): + length = (length << 8) | self.streams[stream].data[i + 1 + off] + return self.streams[stream].data[i + nbytes:i + nbytes + length] + + def get_blob_at(self, i): + return self._get_length_prefixed_value(CLRStreamType.Blob, i) + + def get_user_string_at(self, i): + return self._get_length_prefixed_value(CLRStreamType.UserString, i)[:-1].decode('utf-16le') + + def get_guid_at(self, i): + if i == 0: + return None + return self.streams[CLRStreamType.GUID].guids[i - 1] + + def get_by_token(self, t): + if not t.row: + return None + return self.get_table_entry(t.table, t.row - 1) + + @property + def entrypoint(self): + return self.get_by_token(self.header.entrypoint) diff --git a/dotnet/streams.py b/dotnet/streams.py new file mode 100644 index 0000000..daf6119 --- /dev/null +++ b/dotnet/streams.py @@ -0,0 +1,93 @@ +import enum +import uuid +from destruct import Type, Struct, Arr + +from .common import CLRStreamType, CLRTableType, CLRHeapFlags +from .tables import TABLE_PARSERS + + +STREAM_PARSERS = {} + +def stream_parser(name): + def inner(c): + STREAM_PARSERS[name] = c + return c + return inner + +class BitVector(Type): + ORDER_MAP = { + 'be': 'big', + 'le': 'little', + } + def __init__(self, size, child=None, order='be'): + self.size = size + self.order = order + self.child = child + + def parse(self, input, context): + data = int.from_bytes(input.read(self.size // 8), self.ORDER_MAP[self.order]) + values = [] + for i in range(self.size): + if data & (1 << i): + values.append(self.child(i) if self.child else i) + return values + + def emit(self, output, value, context): + data = 0 + for i in values: + data |= (1 << i) + output.write(data.to_bytes(self.size // 8, self.ORDER_MAP[self.order])) + + def __repr__(self): + return ''.format(self.size) + +@stream_parser(CLRStreamType.Metadata) +class CLRMetadataStream(Struct): + _gap0 = Data(4) + version_major = UInt(8) + version_minor = UInt(8) + heap_flags = Enum(CLRHeapFlags, UInt(8)) + _gap7 = Data(1) + present = BitVector(64, CLRTableType, order='le') + sorted = BitVector(64, CLRTableType, order='le') + row_counts = Arr(UInt(32)) + tables = Arr([]) + + def on_present(self, spec, context): + spec.row_counts.count = len(self.present) + spec.tables.count = len(self.present) + + def on_row_counts(self, spec, context): + counts = {} + for p, count in zip(self.present, self.row_counts): + spec.tables.child.append(Arr(TABLE_PARSERS[p], count=count)) + counts[p] = count + self.row_counts = counts + context.user.metadata = self + + def on_tables(self, spec, context): + tables = {} + for p, table in zip(self.present, self.tables): + tables[p] = table + self.tables = tables + +class GUID(Type): + def parse(self, input, context): + data = input.read(16) + return uuid.UUID(bytes=data) + + def emit(self, value, output, context): + output.write(value.bytes) + + def sizeof(self, value, context): + return 16 + +@stream_parser(CLRStreamType.GUID) +class CLRGUIDStream(Struct): + guids = Arr(GUID) + +@stream_parser(CLRStreamType.String) +@stream_parser(CLRStreamType.UserString) +@stream_parser(CLRStreamType.Blob) +class CLRDataStream(Struct): + data = Data(None) diff --git a/dotnet/tables.py b/dotnet/tables.py new file mode 100644 index 0000000..9035255 --- /dev/null +++ b/dotnet/tables.py @@ -0,0 +1,568 @@ +import enum +from destruct import Struct + +from .common import ( + MultiEnum, + CLRElementType, CLRStreamType, CLRTableType, + CLRCodedToken, CLRStreamIndex, CLRTableIndex, CLRTableRange +) +from .types import CLRSignature + + +TABLE_PARSERS = {} + +def table_parser(name): + def inner(c): + TABLE_PARSERS[name] = c + return c + return inner + + +@table_parser(CLRTableType.Module) +class CLRModuleTable(Struct): + generation = UInt(16) + name = CLRStreamIndex(CLRStreamType.String) + mvid = CLRStreamIndex(CLRStreamType.GUID) + encid = CLRStreamIndex(CLRStreamType.GUID) + encbaseid = CLRStreamIndex(CLRStreamType.GUID) + + +ResolutionScope = CLRCodedToken([ + CLRTableType.Module, CLRTableType.ModuleRef, + CLRTableType.AssemblyRef, CLRTableType.TypeRef +]) + +@table_parser(CLRTableType.TypeRef) +class CLRTypeRefTable(Struct): + scope = ResolutionScope + name = CLRStreamIndex(CLRStreamType.String) + namespace = CLRStreamIndex(CLRStreamType.String) + + +TypeDefOrRef = CLRCodedToken([ + CLRTableType.TypeDef, CLRTableType.TypeRef, CLRTableType.TypeSpec +]) + +class CLRTypeVisiblity(enum.Enum): + NotPublic = 0x0 + Public = 0x1 + NestedPublic = 0x2 + NestedPrivate = 0x3 + NestedFamily = 0x4 + NestedAssembly = 0x5 + NestedFamANDAssem = 0x6 + NestedFamORAssem = 0x7 + +class CLRTypeLayout(enum.Enum): + AutoLayout = 0x0 + SequentialLayout = 0x8 + ExplicitLayout = 0x10 + +class CLRTypeClassSemantics(enum.Enum): + Class = 0x0 + Interface = 0x20 + +class CLRTypeStringFormatting(enum.IntFlag): + ANSI = 0 + Unicode = 0x10000 + Custom = 0x30000 + +class CLRTypeFlags(enum.Flag): + Abstract = 0x80 + Sealed = 0x100 + SpecialName = 0x400 + RTSpecialName = 0x800 + Import = 0x1000 + Serializable = 0x2000 + HasSecurity = 0x4000 + BeforeFieldInit = 0x100000 + IsTypeForwarder = 0x200000 + +class CLRTypeAttributes(MultiEnum): + visibility = (CLRTypeVisiblity, 0x7) + layout = (CLRTypeLayout, 0x18) + semantics = (CLRTypeClassSemantics, 0x20) + formatting = (CLRTypeStringFormatting, 0xC30000) + flags = (CLRTypeFlags, None) + +@table_parser(CLRTableType.TypeDef) +class CLRTypeDefTable(Struct): + flags = Enum(CLRTypeAttributes, UInt(32)) + name = CLRStreamIndex(CLRStreamType.String) + namespace = CLRStreamIndex(CLRStreamType.String) + extends = TypeDefOrRef + fields = CLRTableRange(CLRTableType.Field) + methods = CLRTableRange(CLRTableType.MethodDef) + + +@table_parser(CLRTableType.FieldPointer) +def CLRFieldPointerTable(Struct): + field = CLRTableIndex(CLRTableType.Field) + + +class CLRAccess(enum.Enum): + CompilerControlled = 0 + Private = 1 + FamANDAssem = 2 + Assembly = 3 + Family = 4 + FamORAssem = 5 + Public = 6 + +class CLRFieldFlags(enum.Flag): + Static = 0x10 + InitOnly = 0x20 + Literal = 0x40 + NotSerialized = 0x80 + HasFieldRVA = 0x100 + SpecialName = 0x200 + RTSpecialName = 0x400 + HasFieldMarshal = 0x1000 + PInvokeImpl = 0x2000 + HasDefault = 0x8000 + +class CLRFieldAttributes(MultiEnum): + access = (CLRAccess, 0x7) + flags = (CLRFieldFlags, 0xFFF0) + +@table_parser(CLRTableType.Field) +class CLRFieldTable(Struct): + flags = Enum(CLRFieldAttributes, UInt(16)) + name = CLRStreamIndex(CLRStreamType.String) + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + + +@table_parser(CLRTableType.MethodPointer) +def CLRMethodPointerTable(Struct): + method = CLRTableIndex(CLRTableType.Method) + + +class CLRMemberVtableLayout(enum.Enum): + ReuseSlot = 0 + NewSlot = 0x100 + +class CLRMethodFlags(enum.Flag): + Static = 0x10 + Final = 0x20 + Virtual = 0x40 + HideBySig = 0x80 + Strict = 0x200 + Abstract = 0x400 + SpecialName = 0x800 + RTSpecialName = 0x1000 + PInvokeImpl = 0x2000 + HasSecurity = 0x4000 + RequireSecObject = 0x8000 + +class CLRMethodAttributes(MultiEnum): + access = (CLRAccess, 0x7) + vtable = (CLRMemberVtableLayout, 0x100) + flags = (CLRMethodFlags, 0xFEF0) + +class CLRMethodCodeType(enum.Enum): + IL = 0 + Native = 1 + OPTIL = 2 + Runtime = 3 + +class CLRMethodManaged(enum.Enum): + Managed = 0 + Unmanaged = 4 + +class CLRMethodImplFlags(enum.Flag): + NoInlining = 0x8 + ForwardRef = 0x10 + Synchronized = 0x20 + NoOptimization = 0x40 + PreserveSig = 0x80 + AggressiveInlining = 0x100 + InternalCall = 0x1000 + +class CLRMethodImplAttributes(MultiEnum): + code_type = (CLRMethodCodeType, 0x3) + managed = (CLRMethodManaged, 0x4) + flags = (CLRMethodImplFlags, 0xFFF8) + +@table_parser(CLRTableType.MethodDef) +class CLRMethodDefTable(Struct): + rva = UInt(32) + impl_flags = Enum(CLRMethodImplAttributes, UInt(16)) + flags = Enum(CLRMethodAttributes, UInt(16)) + name = CLRStreamIndex(CLRStreamType.String) + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + params = CLRTableRange(CLRTableType.Param) + + +@table_parser(CLRTableType.ParamPointer) +def CLRParamPointerTable(Struct): + param = CLRTableIndex(CLRTableType.Param) + + +class CLRParamAttributes(enum.Flag): + In = 0x1 + Out = 0x2 + Optional = 0x10 + HasDefault = 0x1000 + HasFieldMarshal = 0x2000 + +@table_parser(CLRTableType.Param) +class CLRParamTable(Struct): + flags = Enum(CLRParamAttributes, UInt(16)) + sequence = UInt(16) + name = CLRStreamIndex(CLRStreamType.String) + + +MemberRefParent = CLRCodedToken([ + CLRTableType.MethodDef, CLRTableType.ModuleRef, + CLRTableType.TypeDef, CLRTableType.TypeRef, CLRTableType.TypeSpec +]) + +@table_parser(CLRTableType.MemberRef) +class CLRMemberRefTable(Struct): + parent = MemberRefParent + name = CLRStreamIndex(CLRStreamType.String) + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + + +HasCustomAttribute = CLRCodedToken([ + CLRTableType.MethodDef, CLRTableType.Field, + CLRTableType.TypeDef, CLRTableType.TypeRef, + CLRTableType.Param, CLRTableType.InterfaceImpl, + CLRTableType.MemberRef, CLRTableType.Module, 'Permission', + CLRTableType.Property, CLRTableType.Event, + CLRTableType.StandAloneSig, CLRTableType.ModuleRef, CLRTableType.TypeSpec, + CLRTableType.Assembly, CLRTableType.AssemblyRef, + CLRTableType.File, CLRTableType.ExportedType, CLRTableType.ManifestResource, + CLRTableType.GenericParam, CLRTableType.GenericParamConstraint, + CLRTableType.MethodSpec +]) +CustomAttributeType = CLRCodedToken([ + None, None, CLRTableType.MethodDef, CLRTableType.MemberRef, None +]) + +@table_parser(CLRTableType.CustomAttribute) +class CLRCustomAttributeTable(Struct): + parent = HasCustomAttribute + type = CustomAttributeType + value = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.StandAloneSig) +class CLRStandAloneSigTable(Struct): + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + + +@table_parser(CLRTableType.TypeSpec) +class CLRTypeSpecTable(Struct): + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + + +class AssemblyHashAlgorithm(enum.Enum): + Null = 0 + MD5 = 0x8003 + SHA1 = 0x8004 + +class AssemblyFlags(enum.Flag): + PublicKey = 0x1 + Retargetable = 0x100 + DisableJITOptimizing = 0x4000 + EnableJITracking = 0x8000 + +@table_parser(CLRTableType.Assembly) +class CLRAssemblyTable(Struct): + hash_algo = Enum(AssemblyHashAlgorithm, UInt(32)) + version_major = UInt(16) + version_minor = UInt(16) + build_number = UInt(16) + rev_number = UInt(16) + flags = Enum(AssemblyFlags, UInt(32)) + public_key = CLRStreamIndex(CLRStreamType.Blob) + name = CLRStreamIndex(CLRStreamType.String) + culture = CLRStreamIndex(CLRStreamType.String) + + +@table_parser(CLRTableType.AssemblyCPU) +class CLRAssemblyCPUTable(Struct): + processor = UInt(32) + + +@table_parser(CLRTableType.AssemblyOS) +class CLRAssemblyOSTable(Struct): + platform_id = UInt(32) + version_major = UInt(32) + version_minor = UInt(32) + + +@table_parser(CLRTableType.AssemblyRef) +class CLRAssemblyRefTable(Struct): + version_major = UInt(16) + version_minor = UInt(16) + build_number = UInt(16) + rev_number = UInt(16) + flags = Enum(AssemblyFlags, UInt(32)) + public_key = CLRStreamIndex(CLRStreamType.Blob) + name = CLRStreamIndex(CLRStreamType.String) + culture = CLRStreamIndex(CLRStreamType.String) + hash_value = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.AssemblyRefCPU) +class CLRAssemblyRefCPUTable(Struct): + processor = UInt(32) + assembly = CLRTableIndex(CLRTableType.Assembly) + + +@table_parser(CLRTableType.AssemblyRefOS) +class CLRAssemblyRefOSTable(Struct): + platform_id = UInt(32) + version_major = UInt(32) + version_minor = UInt(32) + assembly = CLRTableIndex(CLRTableType.Assembly) + + +Implementation = CLRCodedToken([ + CLRTableType.File, CLRTableType.AssemblyRef, CLRTableType.ExportedType +]) + +class ManifestResourceAttributes(enum.Enum): + Public = 1 + Private = 2 + +@table_parser(CLRTableType.ManifestResource) +class CLRManifestResourceTable(Struct): + offset = UInt(32) + flags = Enum(ManifestResourceAttributes, UInt(32)) + name = CLRStreamIndex(CLRStreamType.String) + implementation = Implementation + + +@table_parser(CLRTableType.NestedClass) +class CLRNestedClassTable(Struct): + nested = CLRTableIndex(CLRTableType.TypeDef) + enclosing = CLRTableIndex(CLRTableType.TypeDef) + + +@table_parser(CLRTableType.InterfaceImpl) +class CLRInterfaceImplTable(Struct): + type = CLRTableIndex(CLRTableType.TypeDef) + interface = TypeDefOrRef + + +HasConstant = CLRCodedToken([ + CLRTableType.Param, CLRTableType.Field, CLRTableType.Property +]) + +@table_parser(CLRTableType.Constant) +class CLRConstantTable(Struct): + type = UInt(8) + _pad1 = UInt(8) + parent = HasConstant + value = CLRStreamIndex(CLRStreamType.Blob) + + +HasDeclSecurity = CLRCodedToken([ + CLRTableType.TypeDef, CLRTableType.MethodDef, CLRTableType.Assembly +]) + +class CLRSecurityAction(enum.Enum): + Demand = 2 + Assert = 3 + Deny = 4 + PermitOnly = 5 + LinkDemand = 6 + InheritanceDemand = 7 + RequestMinimum = 8 + RequestOptional = 9 + RequestRefuse = 10 + +@table_parser(CLRTableType.DeclSecurity) +class CLRDeclSecurityTable(Struct): + action = Enum(CLRSecurityAction, UInt(16)) + parent = HasDeclSecurity + permission_set = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.ClassLayout) +class CLRClassLayoutTable(Struct): + packing_size = UInt(16) + class_size = UInt(32) + parent = CLRTableIndex(CLRTableType.TypeDef) + + +@table_parser(CLRTableType.FieldLayout) +class CLRFieldLayoutTable(Struct): + offset = UInt(32) + field = CLRTableIndex(CLRTableType.Field) + + +@table_parser(CLRTableType.EventMap) +class CLREventMapTable(Struct): + parent = CLRTableIndex(CLRTableType.TypeDef) + events = CLRTableRange(CLRTableType.Event) + + +@table_parser(CLRTableType.EventPointer) +class CLREventPointerTable(Struct): + event = CLRTableIndex(CLRTableType.Event) + + +class CLREventAttributes(enum.Flag): + SpecialName = 0x200 + RTSpecialName = 0x400 + +@table_parser(CLRTableType.Event) +class CLREventTable(Struct): + flags = Enum(CLREventAttributes, UInt(16)) + name = CLRStreamIndex(CLRStreamType.String) + type = TypeDefOrRef + + +@table_parser(CLRTableType.PropertyMap) +class CLRPropertyMapTable(Struct): + parent = CLRTableIndex(CLRTableType.TypeDef) + properties = CLRTableRange(CLRTableType.Property) + + +@table_parser(CLRTableType.PropertyPointer) +class CLRPropertyPointerTable(Struct): + property = CLRTableIndex(CLRTableType.Property) + + +class CLRPropertyAttributes(enum.Flag): + SpecialName = 0x0200 + RTSpecialName = 0x0400 + HasDefault = 0x1000 + +@table_parser(CLRTableType.Property) +class CLRPropertyTable(Struct): + flags = Enum(CLRPropertyAttributes, UInt(16)) + name = CLRStreamIndex(CLRStreamType.String) + signature = CLRStreamIndex(CLRStreamType.Blob, CLRSignature) + + +HasSemantics = CLRCodedToken([CLRTableType.Event, CLRTableType.Property]) + +class CLRMethodSemanticsAttributes(enum.Enum): + Setter = 0x1 + Getter = 0x2 + Other = 0x4 + AddOn = 0x8 + RemoveOn = 0x10 + Fire = 0x20 + +@table_parser(CLRTableType.MethodSemantics) +class CLRMethodSemanticsTable(Struct): + semantics = Enum(CLRMethodSemanticsAttributes, UInt(16)) + method = CLRTableIndex(CLRTableType.MethodDef) + association = HasSemantics + + +MethodDefOrRef = CLRCodedToken([ + CLRTableType.MethodDef, CLRTableType.MemberRef +]) + +@table_parser(CLRTableType.MethodImpl) +class CLRMethodImplTable(Struct): + parent = CLRTableIndex(CLRTableType.TypeDef) + body = MethodDefOrRef + declaration = MethodDefOrRef + + +@table_parser(CLRTableType.ModuleRef) +class CLRModuleRefTable(Struct): + name = CLRStreamIndex(CLRStreamType.String) + + +MemberForwarded = CLRCodedToken([CLRTableType.Field, CLRTableType.MethodDef]) + +class CLRVariance(enum.Enum): + No = 0 + Covariant = 1 + Contravariant = 2 + +class CLRConstraint(enum.Flag): + ReferenceTypeConstraint = 0x4 + NotNullableValueTypeConstraint = 0x8 + DefaultConstructorConstraint = 0x10 + +class CLRPInvokeAttributes(MultiEnum): + variance = (CLRVariance, 0b11) + constraint = (CLRConstraint, 0b11100) + +@table_parser(CLRTableType.ImplMap) +class CLRImplMapTable(Struct): + flags = Enum(CLRPInvokeAttributes, UInt(16)) + forwarded = MemberForwarded + name = CLRStreamIndex(CLRStreamType.String) + scope = CLRTableIndex(CLRTableType.ModuleRef) + + +@table_parser(CLRTableType.FieldRVA) +class CLRFieldRVATable(Struct): + rva = UInt(32) + field = CLRTableIndex(CLRTableType.Field) + + +TypeOrMethodDef = CLRCodedToken([CLRTableType.TypeDef, CLRTableType.MethodDef]) + +class CLRGenericParamAttributes(MultiEnum): + variance = (CLRVariance, 0b11) + constraint = (CLRConstraint, 0b11100) + +@table_parser(CLRTableType.GenericParam) +class CLRGenericParamTable(Struct): + index = UInt(16) + flags = Enum(CLRGenericParamAttributes, UInt(16)) + owner = TypeOrMethodDef + name = CLRStreamIndex(CLRStreamType.String) + + +@table_parser(CLRTableType.MethodSpec) +class CLRMethodSpecTable(Struct): + parent = MethodDefOrRef + instantiation = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.GenericParamConstraint) +class CLRGenericParamConstraintTable(Struct): + owner = CLRTableIndex(CLRTableType.GenericParam) + constraint = TypeDefOrRef + + +HasFieldMarshal = CLRCodedToken([CLRTableType.Field, CLRTableType.Param]) + +@table_parser(CLRTableType.FieldMarshal) +class CLRFieldMarshalTable(Struct): + parent = HasFieldMarshal + native_type = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.EncLog) +class CLREncLogTable(Struct): + token = UInt(32) + func_code = UInt(32) + + +@table_parser(CLRTableType.EncMap) +class CLREncMapTable(Struct): + token = UInt(32) + + +class CLRFileAttributes(enum.Enum): + ContainsMetadata = 0 + ContainsNoMetadata = 1 + +@table_parser(CLRTableType.File) +class CLRFileTable(Struct): + flags = Enum(CLRFileAttributes, UInt(32)) + name = CLRStreamIndex(CLRStreamType.String) + hash = CLRStreamIndex(CLRStreamType.Blob) + + +@table_parser(CLRTableType.ExportedType) +class CLRExportedTypeTable(Struct): + flags = Enum(CLRTypeAttributes, UInt(32)) + type_id = UInt(32) + name = CLRStreamIndex(CLRStreamType.String) + namespace = CLRStreamIndex(CLRStreamType.String) + implementation = Implementation diff --git a/dotnet/types.py b/dotnet/types.py new file mode 100644 index 0000000..6a06271 --- /dev/null +++ b/dotnet/types.py @@ -0,0 +1,253 @@ +import enum +from itertools import zip_longest + +from destruct import parse, Type, Struct +from .common import CLRCodedToken, CLRTableType, CLRElementType, MultiEnum + + + +class CLRType: + pass + +class CLRPrimitiveType(CLRType): + def __init__(self, type: CLRElementType): + self.type = type + + def __str__(self): + return self.type.name.lower() + +class CLRPointerType(CLRType): + def __init__(self, child: CLRType): + self.child = child + + def __str__(self): + return str(self.child) + '*' + +class CLRRefType(CLRType): + def __init__(self, child: CLRType): + self.child = child + + def __str__(self): + return str(self.child) + '&' + +class CLRPinnedType(CLRType): + def __init__(self, child: CLRType): + self.child = child + + def __str__(self): + return str(self.child) + ' fixed&' + +class CLRUserTypeKind(enum.Enum): + Class = 'class' + ValueType = 'enum' + +class CLRUserType(CLRType): + def __init__(self, token, kind): + self.token = token + self.kind = kind + + def __str__(self): + if self.token.table == CLRTableType.TypeRef: + prefix = 'ext-' + else: + prefix = '' + return '{}{}#{}'.format(prefix, self.kind.value, self.token.row) + +class CLROptionalType(CLRType): + def __init__(self, child): + self.child = child + + def __str__(self): + return str(self.child) + '?' + +class CLRRequiredType(CLRType): + def __init__(self, child): + self.child = child + + def __str__(self): + return str(self.child) + '!' + +class CLRGenericParamScope(enum.Enum): + Type = 'type' + Method = 'method' + +class CLRGenericParamType(CLRType): + def __init__(self, index, scope): + self.index = index + self.scope = scope + + def __str__(self): + return '#' + str(self.index) + +class CLRGenericInstantiationType(CLRType): + def __init__(self, child, args): + self.child = child + self.args = args + + def __str__(self): + return str(self.child) + '[' + ', '.join(str(a) for a in self.args) + ']' + +class CLRArrayType(CLRType): + def __init__(self, child, rank, sizes=None, lowers=None): + self.child = child + self.rank = rank + self.sizes = sizes or [] + self.lowers = lowers or [] + + def __str__(self): + return str(self.child) + ''.join( + '[{}{}]'.format(str(lower) + '...' if lower else '', upper + (lower or 0) if upper else '') + for _, lower, upper in zip_longest(range(self.rank), self.lowers, self.sizes) + ) + +class CLRElement(Type): + def __init__(self, child): + self.child = child + + def parse(self, input, context): + nested_types = { + CLRElementType.Pointer: CLRPointerType, + CLRElementType.ByRef: CLRRefType, + CLRElementType.Pinned: CLRPinnedType, + } + token_types = { + CLRElementType.Class: lambda t: CLRUserType(t, CLRUserTypeKind.Class), + CLRElementType.ValueType: lambda t: CLRUserType(t, CLRUserTypeKind.ValueType), + CLRElementType.OptModifier: CLROptionalType, + CLRElementType.ReqModifier: CLRRequiredType, + } + int_types = { + CLRElementType.Var: lambda i: CLRGenericParamType(i, CLRGenericParamScope.Type), + CLRElementType.MVar: lambda i: CLRGenericParamType(i, CLRGenericParamScope.Method), + } + val = CLRElementType(parse(self.child, input, context)) + if val in nested_types: + with context.enter(val.name, self): + val = nested_types[val](self.parse(input, context)) + elif val in token_types: + with context.enter(val.name, self.child): + raw = parse(self.child, input, context) + token = parse(CLRCodedToken([CLRTableType.TypeDef, CLRTableType.TypeRef]), raw.to_bytes(4, 'little'), context) + val = token_types[val](token) + elif val in int_types: + with context.enter(val.name, self.child): + val = int_types[val](parse(self.child, input, context)) + elif val == CLRElementType.Array: + with context.enter(val.name, self): + type = self.parse(input, context) + rank = parse(self.child, input, context) + nbounds = parse(self.child, input, context) + bounds = [parse(self.child, input, context) for _ in range(nbounds)] + nlo = parse(self.child, input, context) + lo = [parse(self.child, input, context) for _ in range(nlo)] + val = CLRArrayType(type, rank, bounds, lo) + elif val == CLRElementType.GenericInst: + with context.enter(val.name, self): + type = self.parse(input, context) + nargs = parse(self.child, input, context) + args = [] + for i in range(nargs): + with context.enter(i, self): + args.append(self.parse(input, context)) + val = CLRGenericInstantiationType(type, args) + else: + val = CLRPrimitiveType(val) + return val + + +class CLRCompressedInt(Type): + def __init__(self, signed=True): + self.signed = signed + + def parse(self, input, context): + val = input.read(1)[0] + if (val >> 5) == 0b110: + nbytes = 4 + b = bytearray([val & 0b11111]) + elif (val >> 6) == 0b10: + nbytes = 2 + b = bytearray([val & 0b111111]) + else: + nbytes = 1 + b = bytearray([val]) + b.extend(input.read(nbytes - 1)) + return int.from_bytes(b, byteorder='big', signed=self.signed) + +class CLRCompressedUInt(Type): + def __new__(self): + return CLRCompressedInt(signed=False) + +class CLRSignatureType(enum.Enum): + Default = 0x0 + C = 0x1 + StdCall = 0x2 + ThisCall = 0x3 + FastCall = 0x4 + VarArg = 0x5 + Field = 0x6 + LocalVar = 0x7 + Property = 0x8 + +class CLRSignatureFlags(enum.Flag): + Generic = 0x10 + HasThis = 0x20 + ExplicitThis = 0x40 + +class CLRSignatureAttributes(MultiEnum): + type = (CLRSignatureType, 0x0F) + flags = (CLRSignatureFlags, 0xF0) + +class CLRMethodSignature(Struct): + param_count = CLRCompressedUInt() + ret_type = CLRElement(CLRCompressedUInt()) + params = Arr(CLRElement(CLRCompressedUInt())) + + def on_param_count(self, spec, context): + spec.params.count = self.param_count + + def __str__(self): + return '(' + ', '.join(str(p) for p in self.params) + ') -> ' + str(self.ret_type) + +class CLRFieldSignature(Struct): + type = CLRElement(CLRCompressedUInt()) + + def __str__(self): + return str(self.type) + +class CLRPropertySignature(Struct): + param_count = CLRCompressedUInt() + type = CLRElement(CLRCompressedUInt()) + params = Arr(CLRElement(CLRCompressedUInt())) + + def on_param_count(self, spec, context): + spec.params.count = self.param_count + + def __str__(self): + return '(' + ', '.join(str(p) for p in self.params) + ') -> ' + str(self.ret_type) + +class CLRLocalVarSignature(Struct): + count = CLRCompressedUInt() + vars = Arr(CLRElement(CLRCompressedUInt())) + + def on_count(self, spec, context): + spec.vars.count = self.count + +class CLRSignature(Struct): + attribs = Enum(CLRSignatureAttributes, UInt(8)) + signature = Switch(options={ + CLRSignatureType.Default: CLRMethodSignature, + CLRSignatureType.C: CLRMethodSignature, + CLRSignatureType.StdCall: CLRMethodSignature, + CLRSignatureType.ThisCall: CLRMethodSignature, + CLRSignatureType.FastCall: CLRMethodSignature, + CLRSignatureType.VarArg: CLRMethodSignature, + CLRSignatureType.Field: CLRFieldSignature, + CLRSignatureType.Property: CLRPropertySignature, + CLRSignatureType.LocalVar: CLRLocalVarSignature, + }) + + def on_attribs(self, spec, context): + spec.signature.selector = self.attribs.type + + def __str__(self): + return str(self.signature)