# SPDX-License-Identifier: GPL-2.0-or-later # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ .NET RemoTing Protocol This implements: - [MS-NRTP] - .NET Remoting Core Protocol - [MS-NRBF] - .NET Remoting Binary Format """ import enum import functools import struct from scapy.automaton import Automaton, ATMT from scapy.config import conf from scapy.main import interact from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, LEIntField, LELongField, LEShortEnumField, LEShortField, LESignedIntField, LESignedLongField, LESignedShortField, LenField, MSBExtendedField, MultipleTypeField, PacketField, PacketListField, SignedByteField, StrField, StrFixedLenField, StrLenField, StrLenFieldUtf16, ) from scapy.packet import Packet from scapy.supersocket import StreamSocket # [MS-NRTP] sect 2.2.3.2.1 class CountedString(Packet): fields_desc = [ ByteEnumField( "StringEncoding", 0, { 0: "Unicode", 1: "UTF8", }, ), FieldLenField("Length", None, fmt="= 2: return cls.registered_headers.get( struct.unpack("= 14: cd = struct.unpack("= length: # Get content-type try: content_type = next( x.ContentTypeValue.StringData for x in pkt.Headers if x.HeaderToken == 6 ) session["content_type"] = content_type except StopIteration: # Not in this packet. Do we know it from the session? content_type = session.get("content_type", None) if not content_type: return pkt # We have a content-type. Parse it. if content_type == b"application/octet-stream": # pkt.payload is NRBF. pkt.payload = NRBF(bytes(pkt.payload)) return pkt return None # [MS-NRBF] .NET Remoting Binary Format class MSBExtendedFieldLen(MSBExtendedField): __slots__ = FieldLenField.__slots__ def __init__(self, name, default, length_of=None): FieldLenField.__init__(self, name, default, length_of=length_of) super(MSBExtendedFieldLen, self).__init__(name, default) i2m = FieldLenField.i2m # [MS-NRBF] sect 2.1.1.6 class NRBFLengthPrefixedString(Packet): fields_desc = [ MSBExtendedFieldLen("Length", None, length_of="String"), StrLenField("String", b"", length_from=lambda pkt: pkt.Length), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.1.1.8 class NRBFClassTypeInfo(Packet): fields_desc = [ PacketField("TypeName", NRBFLengthPrefixedString(), NRBFLengthPrefixedString), LESignedIntField("LibraryId", 0), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.1.2.3 class PrimitiveTypeEnum(enum.IntEnum): Boolean = 1 Byte = 2 Char = 2 Decimal = 5 Double = 6 Int16 = 7 Int32 = 8 Int64 = 9 SByte = 10 Single = 11 TimeSpan = 12 DateTime = 13 UInt16 = 14 UInt32 = 15 UInt64 = 16 Null = 17 String = 18 # [MS-NRBF] sect 2.1.2.2 class BinaryTypeEnum(enum.IntEnum): Primitive = 0 String = 1 Object = 2 SystemClass = 3 Class = 4 ObjectArray = 5 StringArray = 6 PrimitiveArray = 7 # [MS-NRBF] sect 2.2.2.1 class NRBFValueWithCode(Packet): fields_desc = [ ByteEnumField("PrimitiveType", 0, PrimitiveTypeEnum), MultipleTypeField( [ (ByteField("Value", 0), lambda pkt: pkt.PrimitiveType in [1, 2, 3, 4]), (LESignedShortField("Value", 0), lambda pkt: pkt.PrimitiveType == 7), (LESignedIntField("Value", 0), lambda pkt: pkt.PrimitiveType == 8), (LESignedLongField("Value", 0), lambda pkt: pkt.PrimitiveType == 9), (SignedByteField("Value", 0), lambda pkt: pkt.PrimitiveType == 10), (LEShortField("Value", 0), lambda pkt: pkt.PrimitiveType == 14), (LEIntField("Value", 0), lambda pkt: pkt.PrimitiveType == 15), (LELongField("Value", 0), lambda pkt: pkt.PrimitiveType == 16), ( PacketField( "Value", NRBFLengthPrefixedString(), NRBFLengthPrefixedString ), lambda pkt: pkt.PrimitiveType == 18, ), ], StrFixedLenField("Value", b"", length=0), ), ] def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.2.2.2 class NRBFStringValueWithCode(NRBFValueWithCode): PrimitiveType = 18 StringValueWithCode = lambda name: PacketField( name, NRBFStringValueWithCode(), NRBFStringValueWithCode ) # [MS-NRBF] sect 2.2.2.3 class NRBFArrayOfValueWithCode(Packet): fields_desc = [ FieldLenField("Length", None, fmt="= pkt.MemberCount: return None if hasattr(pkt, "BinaryTypeEnums"): if index < len(pkt.BinaryTypeEnums): typeEnum = pkt.BinaryTypeEnums[index] if typeEnum == BinaryTypeEnum.Primitive: # Get AdditionalInfo to get the matching primitive type. primitiveType = pkt.AdditionalInfos[ sum( 1 for x in pkt.BinaryTypeEnums[:index] if x not in [ BinaryTypeEnum.String, BinaryTypeEnum.Object, BinaryTypeEnum.ObjectArray, BinaryTypeEnum.StringArray, ] ) ].Value return functools.partial( NRBFMemberPrimitiveUnTyped, type=PrimitiveTypeEnum(primitiveType), ) return NRBFRecord class _NRBFMembers(Packet): fields_desc = [ PacketListField( "Members", [], None, next_cls_cb=_members_cb, ) ] # [MS-NRBF] sect 2.3.1.1 class NRBFClassInfo(Packet): fields_desc = [ LESignedIntField("ObjectId", 0), PacketField("Name", NRBFLengthPrefixedString(), NRBFLengthPrefixedString), FieldLenField("MemberCount", None, fmt="= index ) except StopIteration: return None typeEnum = BinaryTypeEnum(typeEnum) # Return BinaryTypeEnum tainted with a pre-selected type. return functools.partial( NRBFAdditionalInfo, type=typeEnum, ) class NRBFMemberTypeInfo(Packet): fields_desc = [ FieldListField( "BinaryTypeEnums", [], ByteEnumField("", 0, BinaryTypeEnum), count_from=lambda pkt: pkt.MemberCount, ), PacketListField( "AdditionalInfos", [], None, next_cls_cb=_member_type_infos_cb, ), ] # [MS-NRBF] 2.3.2.5 class NRBFClassWithId(NRBFRecord): RecordTypeEnum = 1 fields_desc = [ NRBFRecord, LESignedIntField("ObjectId", 0), LESignedIntField("MetadataId", 0), ] # [MS-NRBF] sect 2.5.2 class NRBFMemberPrimitiveUnTyped(Packet): __slots__ = ["type"] fields_desc = [ NRBFValueWithCode.fields_desc[1], ] def __init__(self, _pkt=None, **kwargs): self.type = kwargs.pop("type", PrimitiveTypeEnum.Byte) assert isinstance(self.type, PrimitiveTypeEnum) super(NRBFMemberPrimitiveUnTyped, self).__init__(_pkt, **kwargs) def clone_with(self, *args, **kwargs): pkt = super(NRBFMemberPrimitiveUnTyped, self).clone_with(*args, **kwargs) pkt.type = self.type return pkt def copy(self): pkt = super(NRBFMemberPrimitiveUnTyped, self).copy() pkt.type = self.type return pkt @property def PrimitiveType(self): return self.type def default_payload_class(self, payload): return conf.padding_layer # [MS-NRBF] sect 2.3.2.1 class NRBFClassWithMembersAndTypes(NRBFRecord): RecordTypeEnum = 5 fields_desc = [ NRBFRecord, NRBFClassInfo, NRBFMemberTypeInfo, LESignedIntField("LibraryId", 0), _NRBFMembers, ] # [MS-NRBF] sect 2.3.2.3 class NRBFSystemClassWithMembersAndTypes(NRBFRecord): RecordTypeEnum = 4 fields_desc = [ NRBFRecord, NRBFClassInfo, NRBFMemberTypeInfo, _NRBFMembers, ] # [MS-NRBF] sect 2.3.2.4 class NRBFSystemClassWithMembers(NRBFRecord): RecordTypeEnum = 2 fields_desc = [ NRBFRecord, NRBFClassInfo, _NRBFMembers, ] # [MS-NRBF] sect 2.4.2.1 class ArrayInfo(Packet): fields_desc = [LEIntField("ObjectId", 0), LEIntField("Length", None)] # [MS-NRBF] sect 2.4.3.2 class NRBFArraySingleObject(NRBFRecord): RecordTypeEnum = 16 Length = 1 fields_desc = [ NRBFRecord, ArrayInfo, ] # [MS-NRBF] sect 2.4.3.3 def _values_singleprim_cb(pkt, lst, cur, remain): index = len(lst) + (1 if cur is not None else 0) if index >= pkt.Length: return None return functools.partial( NRBFMemberPrimitiveUnTyped, type=PrimitiveTypeEnum(pkt.PrimitiveTypeEnum), ) class NRBFArraySinglePrimitive(NRBFRecord): RecordTypeEnum = 15 fields_desc = [ NRBFRecord, ArrayInfo, ByteEnumField("PrimitiveTypeEnum", 0, PrimitiveTypeEnum), MultipleTypeField( [ ( StrLenField("Values", [], length_from=lambda pkt: pkt.Length), lambda pkt: pkt.PrimitiveTypeEnum == PrimitiveTypeEnum.Byte, ) ], PacketListField( "Values", [], next_cls_cb=_values_singleprim_cb, max_count=1000, ), ), ] def post_build(self, p, pay): if self.Length is None: p = p[:5] + struct.pack("