From 494fa765072f36d7414ae60723139d32df52f570 Mon Sep 17 00:00:00 2001 From: ODeux Date: Thu, 3 Jul 2025 15:20:02 +0200 Subject: [PATCH 1/3] Add dotnet BinaryFormatter pattern --- patterns/dotnet_binaryformatter.hexpat | 921 +++++++++++++++++++++++++ 1 file changed, 921 insertions(+) create mode 100644 patterns/dotnet_binaryformatter.hexpat diff --git a/patterns/dotnet_binaryformatter.hexpat b/patterns/dotnet_binaryformatter.hexpat new file mode 100644 index 00000000..ea17ecd8 --- /dev/null +++ b/patterns/dotnet_binaryformatter.hexpat @@ -0,0 +1,921 @@ + +/* + References: + .NET BinaryFormatter Specification "MS-NRBF": + https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/75b9fe09-be15-475f-85b8-ae7b7558cfe5 + .NET runtime: + https://github.com/dotnet/runtime/blob/v8.0.17/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs + .NET Library for Parsing MS-NRBF streams: + https://github.com/bbowyersmyth/BinaryFormatDataStructure +*/ + +#pragma author ODeux +#pragma description .NET BinaryFormatter (System.Runtime.Serialization.Formatters.Binary, obsolete) + +#pragma endian little + +import std.core; +import std.sys; +import std.mem; +import std.ptr; + +fn offsetOf(ref auto value, ref auto value_field){ + return addressof(value_field) - addressof(value); +}; + +struct NullableArrayPtr{ + pointerSize pointerValue [[no_unique_address, hidden]]; + if(pointerValue != 0) + T *data[size]: pointerSize; + else + padding[sizeof(pointerSize)]; +}; + +using _Ptr = std::ptr::NullablePtr; +using _ArrayPtr = NullableArrayPtr; + +fn append_value_to_section(ref auto value, std::mem::Section section){ + u128 old_section_size = std::mem::get_section_size(section); + std::mem::copy_value_to_section(value, section, old_section_size); + return old_section_size; +}; + +fn todo(auto message){ + std::error(std::format("@0x{:08X} TODO: " + message, $)); +}; + +using _Trackers; +std::mem::Section _TrackersSection = std::mem::create_section("_TrackersSection"); +_Trackers _trackers @ 0x0 in _TrackersSection; +bool NeedUpdateTrackers = false; +bool IsUpdatingTrackers = false; + +enum _ObjEnum: u64{ + Empty = 0, + SerializedStreamHeader = 1, + ClassWithId = 2, Object = _ObjEnum::ClassWithId, + SystemClassWithMembers = 3, ObjectWithMap = _ObjEnum::SystemClassWithMembers, + ClassWithMembers = 4, ObjectWithMapAssemId = _ObjEnum::ClassWithMembers, + SystemClassWithMembersAndTypes = 5, ObjectWithMapTyped = _ObjEnum::SystemClassWithMembersAndTypes, + ClassWithMembersAndTypes = 6, ObjectWithMapTypedAssemId = _ObjEnum::ClassWithMembersAndTypes, + BinaryObjectString = 7, ObjectString = _ObjEnum::BinaryObjectString, + BinaryArray = 8, Array = _ObjEnum::BinaryArray, + MemberPrimitiveTyped = 9, + MemberReference = 10, + BinaryLibrary = 11, Assembly = _ObjEnum::BinaryLibrary, + ObjectNullMultiple256 = 12, + ObjectNullMultiple = 13, + ArraySinglePrimitive = 14, + ArraySingleObject = 15, + ArraySingleString = 16, + CrossAppDomainMap = 17, + CrossAppDomainString = 18, + CrossAppDomainAssembly = 19, + MethodCall = 20, + MethodReturn = 21 +}; + +fn zeroedCurrObjTrackers(){ + _trackers.currentObj.TypeName.pointerValue = 0; + _trackers.currentObj.AssemblyName.pointerValue = 0; + _trackers.currentObj.objEnum = _ObjEnum::Empty; + _trackers.currentObj.RawPtr = 0; +}; + +fn copyCurrObjAtIdTrackers(auto id){ + NeedUpdateTrackers = true; + _trackers.objs[id].TypeName.pointerValue = _trackers.currentObj.TypeName.pointerValue; + _trackers.objs[id].AssemblyName.pointerValue = _trackers.currentObj.AssemblyName.pointerValue; + /* ! Enum does not get copied if we don't use a cast here for some reason ! */ + _trackers.objs[id].objEnum = u64(_trackers.currentObj.objEnum); + _trackers.objs[id].RawPtr = _trackers.currentObj.RawPtr; +}; + +using BitfieldOrder = std::core::BitfieldOrder; + +using TimeSpan = s64; + +enum PrimitiveTypeEnum: u8{ + Invalid = 0, + Boolean = 1, + Byte = 2, + Char = 3, + Currency = 4, /* Not Used in this protocol */ + 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 +}; + +struct PrimitiveTypeEnumT{ + PrimitiveTypeEnum primitiveTypeEnumT = _primitiveTypeEnumT; + PrimitiveTypeEnum primitiveTypeEnum; + if(_primitiveTypeEnumT > 0) + std::assert(primitiveTypeEnum == primitiveTypeEnumT, std::format("Expected {} but got {}", primitiveTypeEnumT, primitiveTypeEnum)); +}; + +enum BinaryTypeEnum: u8{ + Primitive = 0, + String = 1, + Object = 2, + SystemClass = 3, ObjectUrt = BinaryTypeEnum::SystemClass, + Class = 4, ObjectUser = BinaryTypeEnum::Class, + ObjectArray = 5, + StringArray = 6, + PrimitiveArray = 7 +}; + +enum BinaryArrayTypeEnum: u8{ + Single = 0, + Jagged = 1, + Rectangular = 2, + SingleOffset = 3, + JaggedOffset = 4, + RectangularOffset = 5 +}; + +enum RecordTypeEnum: u8{ + SerializedStreamHeader = 0, + ClassWithId = 1, Object = RecordTypeEnum::ClassWithId, + SystemClassWithMembers = 2, ObjectWithMap = RecordTypeEnum::SystemClassWithMembers, + ClassWithMembers = 3, ObjectWithMapAssemId = RecordTypeEnum::ClassWithMembers, + SystemClassWithMembersAndTypes = 4, ObjectWithMapTyped = RecordTypeEnum::SystemClassWithMembersAndTypes, + ClassWithMembersAndTypes = 5, ObjectWithMapTypedAssemId = RecordTypeEnum::ClassWithMembersAndTypes, + BinaryObjectString = 6, ObjectString = RecordTypeEnum::BinaryObjectString, + BinaryArray = 7, Array = RecordTypeEnum::BinaryArray, + MemberPrimitiveTyped = 8, + MemberReference = 9, + ObjectNull = 10, + MessageEnd = 11, + BinaryLibrary = 12, Assembly = RecordTypeEnum::BinaryLibrary, + ObjectNullMultiple256 = 13, + ObjectNullMultiple = 14, + ArraySinglePrimitive = 15, + ArraySingleObject = 16, + ArraySingleString = 17, + CrossAppDomainMap = 18, + CrossAppDomainString = 19, + CrossAppDomainAssembly = 20, + MethodCall = 21, + MethodReturn = 22 +}; +using BinaryHeaderEnum = RecordTypeEnum; + +struct RecordTypeEnumT{ + RecordTypeEnum recordTypeEnumT = _recordTypeEnumT; + RecordTypeEnum recordTypeEnum; + if(_recordTypeEnumT > 0) + std::assert(recordTypeEnum == recordTypeEnumT, std::format("Expected {} but got {}", recordTypeEnumT, recordTypeEnum)); +}; + +bitfield MessageFlags{ + bool NoArgs: 1; /* Arg Category */ + bool ArgsInline: 1; /* Arg Category */ + bool ArgsIsArray: 1; /* Arg Category */ + bool ArgsInArray: 1; /* Arg Category */ + bool NoContext: 1; /* Context Category */ + bool ContextInline: 1; /* Context Category */ + bool ContextInArray: 1; /* Context Category */ + bool MethodSignatureInArray: 1; /* Signature Category */ + bool PropertiesInArray: 1; /* Property Category */ + bool NoReturnValue: 1; /* Return Category */ + bool ReturnValueVoid: 1; /* Return Category */ + bool ReturnValueInline: 1; /* Return Category */ + bool ReturnValueInArray: 1; /* Return Category */ + bool ExceptionInArray: 1; /* Exception Category */ + bool GenericMethod: 1; /* Generic Category */ + unsigned unused: 17; +} [[bitfield_order(BitfieldOrder::LeastToMostSignificant, 32)]]; + +fn validate_MessageFlags(MessageFlags flags){ + u8 arg_cnt = flags.NoArgs + flags.ArgsInline + flags.ArgsIsArray + flags.ArgsInArray; + u8 ctx_cnt = flags.NoContext + flags.ContextInline + flags.ContextInArray; + u8 sig_cnt = flags.MethodSignatureInArray; + u8 ret_cnt = flags.NoReturnValue + flags.ReturnValueVoid + flags.ReturnValueInline + flags.ReturnValueInArray; + u8 excep_cnt = flags.ExceptionInArray; + u8 prop_cnt = flags.PropertiesInArray; + u8 gen_cnt = flags.GenericMethod; + if(arg_cnt > 1 || ctx_cnt > 1 || sig_cnt > 1 || ret_cnt > 1 || excep_cnt > 1 || prop_cnt > 1 || gen_cnt > 1) + return -1; + if(arg_cnt != 0 && excep_cnt != 0) return -1; + if(ret_cnt != 0 && excep_cnt != 0) return -1; + if(ret_cnt != 0 && sig_cnt != 0) return -1; + if(excep_cnt != 0 && sig_cnt != 0) return -1; + return 1; +}; + +enum DateTimeKind: u8{ + NOT_SPECIFIED = 0, + UTC = 1, + Local = 2 +}; + +bitfield DateTime{ + s64 Ticks: 62; + DateTimeKind kind: 2; +} [[bitfield_order(BitfieldOrder::LeastToMostSignificant, 64)]]; + +struct vLength{ + /* + Can't use that, it breaks when struct get re-parsed in _TrackersSection + u8 data[while(std::mem::read_unsigned($, 1) & 0x80)]; + u8 last; + */ + u64 bytes [[no_unique_address, hidden]]; + u8 cnt = 0; + if(bytes & 0x80){ + if(bytes & 0x8000){ + if(bytes & 0x800000){ + if(bytes & 0x80000000){ + if(bytes & 0x8000000000){ + /* exceeding vLength 5 bytes, caller should crash */ + cnt = 5; + }else cnt = 4; + }else cnt = 3; + }else cnt = 2; + }else cnt = 1; + }else cnt = 0; + u8 data[cnt]; + u8 last; +} [[sealed, transform("LPS_Length_decode"), format("LPS_Length_decode")]]; + +fn LPS_Length_decode(auto Length){ + u64 length = 0; + u8 i = 0; + for(i = 0, i < sizeof(Length.data), i += 1) + length |= u64(Length.data[i] & 0x7F) << i * 7; + length |= u64(Length.last) << i * 7; + return length; +}; + +struct LengthPrefixedString{ + vLength Length; + std::assert(sizeof(Length) <= 5, "LengthPrefixedString.Length must be at most 5 bytes long"); + char String[Length]; +}; + +using Decimal = LengthPrefixedString; + +struct ClassTypeInfo{ + LengthPrefixedString TypeName; + s32 LibraryId; +}; + +struct ValueWithCode{ + PrimitiveTypeEnum PrimitiveType; + match(PrimitiveType){ + (PrimitiveTypeEnum::Boolean): bool Value; + (PrimitiveTypeEnum::Byte): u8 Value; + (PrimitiveTypeEnum::Char): char Value; + (PrimitiveTypeEnum::Currency): std::error("Primitive currency not used in this protocol"); + (PrimitiveTypeEnum::Decimal): Decimal Value; + (PrimitiveTypeEnum::Double): double Value; + (PrimitiveTypeEnum::Int16): s16 Value; + (PrimitiveTypeEnum::Int32): s32 Value; + (PrimitiveTypeEnum::Int64): s64 Value; + (PrimitiveTypeEnum::SByte): s8 Value; + (PrimitiveTypeEnum::Single): float Value; + (PrimitiveTypeEnum::TimeSpan): TimeSpan Value; + (PrimitiveTypeEnum::DateTime): DateTime Value; + (PrimitiveTypeEnum::UInt16): u16 Value; + (PrimitiveTypeEnum::UInt32): u32 Value; + (PrimitiveTypeEnum::UInt64): u64 Value; + (PrimitiveTypeEnum::Null): {} + (PrimitiveTypeEnum::String): LengthPrefixedString Value; + (_): std::error(std::format("Unexpected {}", PrimitiveType)); + } +}; + +struct StringValueWithCode: PrimitiveTypeEnumT{ + LengthPrefixedString StringValue; +}; + +struct ArrayOfValueWithCode{ + s32 Length; + ValueWithCode ListOfValueWithCode[Length]; +}; + +struct ArrayInfo{ + s32 ObjectId; + s32 Length; +}; + +struct ClassInfo{ + s32 ObjectId; + LengthPrefixedString Name; + s32 MemberCount; + LengthPrefixedString MemberNames[MemberCount]; +}; + +struct AdditionalInfo{ + BinaryTypeEnum binaryTypeEnum = _binaryTypeEnum; + match(binaryTypeEnum){ + (BinaryTypeEnum::SystemClass): /* ObjectUrt */ + LengthPrefixedString String; + (BinaryTypeEnum::Class): /* ObjectUser */ + ClassTypeInfo classTypeInfo; + (BinaryTypeEnum::Primitive | BinaryTypeEnum::PrimitiveArray): { + PrimitiveTypeEnum primitiveType; + std::assert(primitiveType != PrimitiveTypeEnum::Null && + primitiveType != PrimitiveTypeEnum::String, "Must not be Null or String"); + } + (BinaryTypeEnum::String | BinaryTypeEnum::Object | + BinaryTypeEnum::ObjectArray | BinaryTypeEnum::StringArray): + {/* not using continue here, need to keep array index matching */} + (_): std::error(std::format("Unrecognized {}", binaryTypeEnum)); + } +}; + +struct MemberTypeInfo{ + BinaryTypeEnum binaryTypeEnums[MemberCount]; + AdditionalInfo additionalInfo[MemberCount]; +}; + +struct UntypedMember{ + str className = _className; + u64 MemberCount = classInfo.MemberCount; + if(className == "System.Guid" && MemberCount == 11){ + match(std::core::array_index()){ + (0): s32 _a; + (1): s16 _b; + (2): s16 _c; + (3): u8 _d; + (4): u8 _e; + (5): u8 _f; + (6): u8 _g; + (7): u8 _h; + (8): u8 _i; + (9): u8 _j; + (10): u8 _k; + (_): std::error("unreachable"); + } + }else if(MemberCount == 1 && classInfo.MemberNames[0].String == "value__"){ + str Name = classInfo.MemberNames[0].String; + s32 member [name(Name)]; + }else + std::error(std::format("Unsupported untyped member: {}", className)); +}; + +using Record; + +struct Members{ + u64 i = std::core::array_index(); + str Name = _Name; + BinaryTypeEnum binaryTypeEnum = memberTypeInfo.binaryTypeEnums[i]; + if(binaryTypeEnum == BinaryTypeEnum::Primitive){ + PrimitiveTypeEnum primitiveTypeEnum = memberTypeInfo.additionalInfo[i].primitiveType; + match(primitiveTypeEnum){ + (PrimitiveTypeEnum::Boolean): bool Value [[name(Name)]]; + (PrimitiveTypeEnum::Byte): u8 Value [[name(Name)]]; + (PrimitiveTypeEnum::Char): char Value [[name(Name)]]; + (PrimitiveTypeEnum::Currency): std::error("Primitive currency not used in this protocol"); + (PrimitiveTypeEnum::Decimal): Decimal Value [[name(Name)]]; + (PrimitiveTypeEnum::Double): double Value [[name(Name)]]; + (PrimitiveTypeEnum::Int16): s16 Value [[name(Name)]]; + (PrimitiveTypeEnum::Int32): s32 Value [[name(Name)]]; + (PrimitiveTypeEnum::Int64): s64 Value [[name(Name)]]; + (PrimitiveTypeEnum::SByte): s8 Value [[name(Name)]]; + (PrimitiveTypeEnum::Single): float Value [[name(Name)]]; + (PrimitiveTypeEnum::TimeSpan): TimeSpan Value [[name(Name)]]; + (PrimitiveTypeEnum::DateTime): DateTime Value [[name(Name)]]; + (PrimitiveTypeEnum::UInt16): u16 Value [[name(Name)]]; + (PrimitiveTypeEnum::UInt32): u32 Value [[name(Name)]]; + (PrimitiveTypeEnum::UInt64): u64 Value [[name(Name)]]; + (PrimitiveTypeEnum::Null): {} + (PrimitiveTypeEnum::String): LengthPrefixedString Value [[name(Name)]]; + (_): std::error(std::format("Unexpected {}", primitiveTypeEnum)); + } + }else{ + Record record [[name(Name)]]; + } +}; + +struct ClassWithMembersAndTypes: RecordTypeEnumT{ + ClassInfo classInfo; + MemberTypeInfo memberTypeInfo; + s32 LibraryId; + Members members[classInfo.MemberCount]; +}; + +struct ClassWithMembers: RecordTypeEnumT{ + ClassInfo classInfo; + s32 LibraryId; + UntypedMember members[classInfo.MemberCount]; +}; + +struct SystemClassWithMembersAndTypes: RecordTypeEnumT{ + ClassInfo classInfo; + MemberTypeInfo memberTypeInfo; + Members members[classInfo.MemberCount]; +}; + +struct SystemClassWithMembers: RecordTypeEnumT{ + ClassInfo classInfo; + UntypedMember members[classInfo.MemberCount]; +}; + +struct ClassWithId: RecordTypeEnumT{ + s32 ObjectId; + s32 MetadataId; + if(!IsUpdatingTrackers && NeedUpdateTrackers){ + IsUpdatingTrackers = true; + _Trackers _trackers @ 0x0 in _TrackersSection; + IsUpdatingTrackers = false; + NeedUpdateTrackers = false; + } + match(_trackers.objs[MetadataId].objEnum){ + (_ObjEnum::ClassWithMembersAndTypes): { + u32 MemberCount = _trackers.objs[MetadataId].classWithMembersAndTypes.data.classInfo.MemberCount; + Members<_trackers.objs[MetadataId].classWithMembersAndTypes.data.classInfo.MemberNames[std::core::array_index()].String, _trackers.objs[MetadataId].classWithMembersAndTypes.data.memberTypeInfo> members[MemberCount]; + } + (_ObjEnum::SystemClassWithMembersAndTypes): { + u32 MemberCount = _trackers.objs[MetadataId].systemClassWithMemberAndTypes.data.classInfo.MemberCount; + Members<_trackers.objs[MetadataId].systemClassWithMemberAndTypes.data.classInfo.MemberNames[std::core::array_index()].String, _trackers.objs[MetadataId].systemClassWithMemberAndTypes.data.memberTypeInfo> members[MemberCount]; + } + (_ObjEnum::SystemClassWithMembers): { + str className = _trackers.objs[MetadataId].TypeName.data.String; + u32 MemberCount = _trackers.objs[MetadataId].systemClassWithMembers.data.classInfo.MemberCount; + UntypedMember members[MemberCount]; + } + (_ObjEnum::ClassWithMembers): { + str className = _trackers.objs[MetadataId].TypeName.data.String; + u32 MemberCount = _trackers.objs[MetadataId].classWithMembers.data.classInfo.MemberCount; + UntypedMember members[MemberCount]; + } + (_): std::error(std::format("Unexpected {}", _trackers.objs[MetadataId].objEnum)); + } +}; + +struct BinaryArray: RecordTypeEnumT{ + s32 ObjectId; + BinaryArrayTypeEnum binaryArrayTypeEnum; + s32 Rank; + s32 Length[Rank]; + if(binaryArrayTypeEnum == BinaryArrayTypeEnum::SingleOffset || + binaryArrayTypeEnum == BinaryArrayTypeEnum::JaggedOffset || + binaryArrayTypeEnum == BinaryArrayTypeEnum::RectangularOffset) + s32 LowerBounds[Rank]; + BinaryTypeEnum TypeEnum; + AdditionalInfo additionalInfo; +}; + +struct ArraySinglePrimitive: RecordTypeEnumT{ + ArrayInfo arrayInfo; + PrimitiveTypeEnum primitiveTypeEnum; + std::assert(primitiveTypeEnum != PrimitiveTypeEnum::Null && + primitiveTypeEnum != PrimitiveTypeEnum::String, "Can't be one of those"); + match(primitiveTypeEnum){ + (PrimitiveTypeEnum::Boolean): bool Values[arrayInfo.Length]; + (PrimitiveTypeEnum::Byte): u8 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Char): char Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Currency): std::error("Primitive currency not used in this protocol"); + (PrimitiveTypeEnum::Decimal): Decimal Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Double): double Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Int16): s16 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Int32): s32 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Int64): s64 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::SByte): s8 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Single): float Value[arrayInfo.Length]; + (PrimitiveTypeEnum::TimeSpan): TimeSpan Value[arrayInfo.Length]; + (PrimitiveTypeEnum::DateTime): DateTime Value[arrayInfo.Length]; + (PrimitiveTypeEnum::UInt16): u16 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::UInt32): u32 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::UInt64): u64 Value[arrayInfo.Length]; + (PrimitiveTypeEnum::Null): {} + (PrimitiveTypeEnum::String): LengthPrefixedString Value[arrayInfo.Length]; + (_): std::error(std::format("Unexpected {}", primitiveTypeEnum)); + } +}; + +u64 ArraySinglElementSkipCount = 0; +struct ArraySingleElement{ + RecordTypeEnum recordTypeEnum = _recordTypeEnum; + if(ArraySinglElementSkipCount > 0) + ArraySinglElementSkipCount -= 1; + else{ + if(recordTypeEnum == RecordTypeEnum::ArraySingleString){ + Record record [[inline]]; + match(record.recordTypeEnum){ + (RecordTypeEnum::ObjectNullMultiple): + ArraySinglElementSkipCount = record.objectNullMultiple.NullCount; + (RecordTypeEnum::ObjectNullMultiple256): + ArraySinglElementSkipCount = record.objectNullMultiple256.NullCount; + (_): {} + } + }else{ + Record record [[inline]]; + if(record.recordTypeEnum == RecordTypeEnum::BinaryLibrary){ + Record record2 [[inline]]; + match(record2.recordTypeEnum){ + (RecordTypeEnum::ObjectNullMultiple): + ArraySinglElementSkipCount = record2.objectNullMultiple.NullCount; + (RecordTypeEnum::ObjectNullMultiple256): + ArraySinglElementSkipCount = record2.objectNullMultiple256.NullCount; + (_): {} + } + }else{ + match(record.recordTypeEnum){ + (RecordTypeEnum::ObjectNullMultiple): + ArraySinglElementSkipCount = record.objectNullMultiple.NullCount; + (RecordTypeEnum::ObjectNullMultiple256): + ArraySinglElementSkipCount = record.objectNullMultiple256.NullCount; + (_): {} + } + } + } + } +}; + +struct ArraySingleObject: RecordTypeEnumT{ + ArrayInfo arrayInfo; + ArraySinglElementSkipCount = 0; + ArraySingleElement records[arrayInfo.Length]; +}; + +struct ArraySingleString: RecordTypeEnumT{ + ArrayInfo arrayInfo; + ArraySinglElementSkipCount = 0; + ArraySingleElement records[arrayInfo.Length]; +}; + +struct MethodReturnCallArray{ + if(parent.MessageEnum.ReturnValueInArray) + ArraySingleObject ReturnValue; + if(parent.MessageEnum.ArgsInArray) + ArraySingleObject OutputArguments; + if(parent.MessageEnum.ExceptionInArray) + ArraySingleObject Exception; + if(parent.MessageEnum.ContextInArray) + ArraySingleObject CallContext; + if(parent.MessageEnum.PropertiesInArray) + ArraySingleObject MessageProperties; +}; + +struct MethodCallArray{ + if(parent.MessageEnum.ArgsInArray) + ArraySingleObject InputArguments; + if(parent.MessageEnum.GenericMethod) + ArraySingleObject GenericTypeArguments; + if(parent.MessageEnum.MethodSignatureInArray) + ArraySingleObject MethodSignature; + if(parent.MessageEnum.ContextInArray) + ArraySingleObject CallContext; + if(parent.MessageEnum.PropertiesInArray) + ArraySingleObject MessageProperties; +}; + +struct BinaryMethodReturn: RecordTypeEnumT{ + MessageFlags MessageEnum; + std::assert(validate_MessageFlags(MessageEnum) >= 0, "Validation Failed"); + std::assert(!MessageEnum.MethodSignatureInArray && !MessageEnum.GenericMethod, "Can't be one of those"); + if(MessageEnum.ReturnValueInline) + ValueWithCode ReturnValue; + if(MessageEnum.ContextInline) + StringValueWithCode CallContext; + if(MessageEnum.ArgsInline) + ArrayOfValueWithCode Args; + MethodReturnCallArray ReturnCallArray; +}; + +struct BinaryMethodCall: RecordTypeEnumT{ + MessageFlags MessageEnum; + std::assert(validate_MessageFlags(MessageEnum) >= 0, "Validation Failed"); + std::assert(!MessageEnum.NoReturnValue && !MessageEnum.ReturnValueVoid && + !MessageEnum.ReturnValueInline && !MessageEnum.ReturnValueInArray && + !MessageEnum.ExceptionInArray, "Can't be one of those"); + StringValueWithCode MethodName; + StringValueWithCode TypeName; + if(MessageEnum.ContextInline) + StringValueWithCode CallContext; + if(MessageEnum.ArgsInline) + ArrayOfValueWithCode Args; + MethodCallArray CallArray; +}; + +struct MemberPrimitiveTyped: RecordTypeEnumT{ + PrimitiveTypeEnum primitiveTypeEnum; + std::assert(primitiveTypeEnum != PrimitiveTypeEnum::Null && + primitiveTypeEnum != PrimitiveTypeEnum::String, "Can't be one of those"); + match(primitiveTypeEnum){ + (PrimitiveTypeEnum::Boolean): bool Value; + (PrimitiveTypeEnum::Byte): u8 Value; + (PrimitiveTypeEnum::Char): char Value; + (PrimitiveTypeEnum::Currency): std::error("Primitive currency not used in this protocol"); + (PrimitiveTypeEnum::Decimal): Decimal Value; + (PrimitiveTypeEnum::Double): double Value; + (PrimitiveTypeEnum::Int16): s16 Value; + (PrimitiveTypeEnum::Int32): s32 Value; + (PrimitiveTypeEnum::Int64): s64 Value; + (PrimitiveTypeEnum::SByte): s8 Value; + (PrimitiveTypeEnum::Single): float Value; + (PrimitiveTypeEnum::TimeSpan): TimeSpan Value; + (PrimitiveTypeEnum::DateTime): DateTime Value; + (PrimitiveTypeEnum::UInt16): u16 Value; + (PrimitiveTypeEnum::UInt32): u32 Value; + (PrimitiveTypeEnum::UInt64): u64 Value; + (_): std::error(std::format("Unexpected {}", primitiveTypeEnum)); + } +}; + +struct MemberPrimitiveUnTyped{ + match(PrimitiveType){ + (PrimitiveTypeEnum::Boolean): bool Value; + (PrimitiveTypeEnum::Byte): u8 Value; + (PrimitiveTypeEnum::Char): char Value; + (PrimitiveTypeEnum::Currency): std::error("Primitive currency not used in this protocol"); + (PrimitiveTypeEnum::Decimal): Decimal Value; + (PrimitiveTypeEnum::Double): double Value; + (PrimitiveTypeEnum::Int16): s16 Value; + (PrimitiveTypeEnum::Int32): s32 Value; + (PrimitiveTypeEnum::Int64): s64 Value; + (PrimitiveTypeEnum::SByte): s8 Value; + (PrimitiveTypeEnum::Single): float Value; + (PrimitiveTypeEnum::TimeSpan): TimeSpan Value; + (PrimitiveTypeEnum::DateTime): DateTime Value; + (PrimitiveTypeEnum::UInt16): u16 Value; + (PrimitiveTypeEnum::UInt32): u32 Value; + (PrimitiveTypeEnum::UInt64): u64 Value; + (PrimitiveTypeEnum::Null): {} + (PrimitiveTypeEnum::String):std::error("Can't be String"); + (_): std::error(std::format("Unexpected {}", PrimitiveType)); + } +}; + +struct MemberReference: RecordTypeEnumT{ + s32 IdRef; +}; + +struct ObjectNull: RecordTypeEnumT{ +}; + +struct ObjectNullMultiple: RecordTypeEnumT{ + s32 NullCount; +}; + +struct ObjectNullMultiple256: RecordTypeEnumT{ + u8 NullCount; +}; + +struct BinaryObjectString: RecordTypeEnumT{ + s32 ObjectId; + LengthPrefixedString Value; +}; + +struct SerializationHeaderRecord: RecordTypeEnumT{ + s32 RootId; + s32 HeaderId; + s32 MajorVersion; + std::assert_warn(MajorVersion == 1, "MajorVersion should be 1"); + s32 MinorVersion; + std::assert_warn(MinorVersion == 0, "MinorVersion should be 0"); +}; + +struct BinaryLibrary: RecordTypeEnumT{ + s32 LibraryId; + LengthPrefixedString LibraryName; +}; + +struct MessageEnd: RecordTypeEnumT{ +}; + +struct CrossAppDomainMap: RecordTypeEnumT{ + s32 crossAppDomainArrayIndex; +}; + +struct CrossAppDomainString: RecordTypeEnumT{ + s32 ObjectId; + s32 Value; +}; + +struct CrossAppDomainAssembly: RecordTypeEnumT{ + s32 LibraryId; + s32 LibraryIndex; +}; + +struct Record{ + RecordTypeEnum recordTypeEnum [[no_unique_address, hidden]]; + match(recordTypeEnum){ + (RecordTypeEnum::SerializedStreamHeader): + std::error("SerializationStreamHeader can only appear at the top of the document"); + (RecordTypeEnum::ClassWithId): { /* RecordTypeEnum::Object */ + ClassWithId classWithId; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::ClassWithId; + _trackers.currentObj.RawPtr = append_value_to_section(classWithId, _TrackersSection); + _trackers.currentObj.TypeName.pointerValue = _trackers.objs[classWithId.MetadataId].TypeName.pointerValue; + _trackers.currentObj.AssemblyName.pointerValue =_trackers.objs[classWithId.MetadataId].AssemblyName.pointerValue ; + if(classWithId.ObjectId != 0) + copyCurrObjAtIdTrackers(classWithId.ObjectId); + } + } + (RecordTypeEnum::SystemClassWithMembers): { /* RecordTypeEnum::ObjectWithMap */ + SystemClassWithMembers systemClassWithMembers; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::SystemClassWithMembers; + _trackers.currentObj.RawPtr = append_value_to_section(systemClassWithMembers, _TrackersSection); + _trackers.currentObj.TypeName.pointerValue = _trackers.currentObj.RawPtr + offsetOf(systemClassWithMembers, systemClassWithMembers.classInfo.Name); + if(systemClassWithMembers.classInfo.ObjectId != 0) + copyCurrObjAtIdTrackers(systemClassWithMembers.classInfo.ObjectId); + } + } + (RecordTypeEnum::ClassWithMembers): { /* RecordTypeEnum::ObjectWithMapAssemId */ + ClassWithMembers classWithMembers; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::ClassWithMembers; + _trackers.currentObj.RawPtr = append_value_to_section(classWithMembers, _TrackersSection); + _trackers.currentObj.TypeName.pointerValue = _trackers.currentObj.RawPtr + offsetOf(classWithMembers, classWithMembers.classInfo.Name); + _trackers.currentObj.AssemblyName.pointerValue = _trackers.libs[classWithMembers.LibraryId].pointerValue; + if(classWithMembers.classInfo.ObjectId != 0) + copyCurrObjAtIdTrackers(classWithMembers.classInfo.ObjectId); + } + } + (RecordTypeEnum::SystemClassWithMembersAndTypes): { /* RecordTypeEnum::ObjectWithMapTyped */ + SystemClassWithMembersAndTypes systemClassWithMembersAndTypes; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::SystemClassWithMembersAndTypes; + _trackers.currentObj.RawPtr = append_value_to_section(systemClassWithMembersAndTypes, _TrackersSection); + _trackers.currentObj.TypeName.pointerValue = _trackers.currentObj.RawPtr + offsetOf(systemClassWithMembersAndTypes, systemClassWithMembersAndTypes.classInfo.Name); + if(systemClassWithMembersAndTypes.classInfo.ObjectId != 0) + copyCurrObjAtIdTrackers(systemClassWithMembersAndTypes.classInfo.ObjectId); + } + } + (RecordTypeEnum::ClassWithMembersAndTypes): { /* RecordTypeEnum::ObjectWithMapTypedAssemId */ + ClassWithMembersAndTypes classWithMembersAndTypes; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::ClassWithMembersAndTypes; + _trackers.currentObj.RawPtr = append_value_to_section(classWithMembersAndTypes, _TrackersSection); + _trackers.currentObj.TypeName.pointerValue = _trackers.currentObj.RawPtr + offsetOf(classWithMembersAndTypes, classWithMembersAndTypes.classInfo.Name); + _trackers.currentObj.AssemblyName.pointerValue = _trackers.libs[classWithMembersAndTypes.LibraryId].pointerValue; + if(classWithMembersAndTypes.classInfo.ObjectId != 0) + copyCurrObjAtIdTrackers(classWithMembersAndTypes.classInfo.ObjectId); + } + } + (RecordTypeEnum::BinaryObjectString): { /* RecordTypeEnum::ObjectString */ + BinaryObjectString binaryObjectString; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::BinaryObjectString; + _trackers.currentObj.RawPtr = append_value_to_section(binaryObjectString, _TrackersSection); + if(binaryObjectString.ObjectId != 0) + copyCurrObjAtIdTrackers(binaryObjectString.ObjectId); + } + } + (RecordTypeEnum::BinaryArray): { /* RecordTypeEnum::Array */ + BinaryArray binaryArray; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::BinaryArray; + _trackers.currentObj.RawPtr = append_value_to_section(binaryArray, _TrackersSection); + if(binaryArray.ObjectId != 0) + copyCurrObjAtIdTrackers(binaryArray.ObjectId); + } + } + (RecordTypeEnum::MemberPrimitiveTyped): { + MemberPrimitiveTyped memberPrimitiveTyped; + } + (RecordTypeEnum::MemberReference): { + MemberReference memberReference; + } + (RecordTypeEnum::ObjectNull): {} + (RecordTypeEnum::MessageEnd): break; + (RecordTypeEnum::BinaryLibrary): { /* RecordTypeEnum::Assembly */ + BinaryLibrary library; + if(!IsUpdatingTrackers) + _trackers.libs[library.LibraryId].pointerValue = append_value_to_section(library.LibraryName, _TrackersSection); + } + (RecordTypeEnum::ObjectNullMultiple256): { + ObjectNullMultiple256 objectNullMultiple256; + } + (RecordTypeEnum::ObjectNullMultiple): { + ObjectNullMultiple objectNullMultiple; + } + (RecordTypeEnum::ArraySinglePrimitive): { + ArraySinglePrimitive arraySinglePrimitive; + if(!IsUpdatingTrackers){ + zeroedCurrObjTrackers(); + _trackers.currentObj.objEnum = _ObjEnum::ArraySinglePrimitive; + _trackers.currentObj.RawPtr = append_value_to_section(arraySinglePrimitive, _TrackersSection); + if(arraySinglePrimitive.arrayInfo.ObjectId != 0) + copyCurrObjAtIdTrackers(arraySinglePrimitive.arrayInfo.ObjectId); + } + } + (RecordTypeEnum::ArraySingleObject): { + ArraySingleObject arraySingleObject; + } + (RecordTypeEnum::ArraySingleString): { + ArraySingleString arraySingleString; + } + (RecordTypeEnum::CrossAppDomainMap): + CrossAppDomainMap crossAppDomainMap; + (RecordTypeEnum::CrossAppDomainString): + CrossAppDomainString crossAppDomainString; + (RecordTypeEnum::CrossAppDomainAssembly): + CrossAppDomainAssembly crossAppDomainAssembly; + (RecordTypeEnum::MethodCall): { + BinaryMethodCall binaryMethodCall; + } + (RecordTypeEnum::MethodReturn): { + BinaryMethodReturn binaryMethodReturn; + } + (_): std::error(std::format("Unrecognized {}", recordTypeEnum)); + } +}; + +struct DotNetBinnaryFormatter{ + SerializationHeaderRecord Header; + Record records[while(std::mem::read_unsigned($, 1) != RecordTypeEnum::MessageEnd)]; + MessageEnd End; + if(!IsUpdatingTrackers && NeedUpdateTrackers){ + IsUpdatingTrackers = true; + _Trackers _trackers @ 0x0 in _TrackersSection; + IsUpdatingTrackers = false; + NeedUpdateTrackers = false; + } +}; + +struct _ObjTracker{ + _Ptr TypeName; + _Ptr AssemblyName; + _ObjEnum objEnum; + u64 RawPtr [[no_unique_address]]; + /* Only enable the one we actually use to avoid significant slow down */ + match(objEnum){ + (_ObjEnum::Empty): + u64 ptr; + /* + (_ObjEnum::ClassWithId): // _ObjEnum::Object + _Ptr classWithId; + */ + (_ObjEnum::SystemClassWithMembers): // _ObjEnum::ObjectWithMap + _Ptr systemClassWithMembers; + (_ObjEnum::ClassWithMembers): // _ObjEnum::ObjectWithMapAssemId + _Ptr classWithMembers; + (_ObjEnum::SystemClassWithMembersAndTypes): // _ObjEnum::ObjectWithMapTyped + _Ptr systemClassWithMemberAndTypes; + (_ObjEnum::ClassWithMembersAndTypes): // _ObjEnum::ObjectWithMapTypedAssemId + _Ptr classWithMembersAndTypes; + /* + (_ObjEnum::BinaryObjectString): // _ObjEnum::ObjectString + _Ptr binaryObjectString; + (_ObjEnum::BinaryArray): // _ObjEnum::Array + _Ptr binaryArray; + (_ObjEnum::MemberPrimitiveTyped): + _Ptr memberPrimitiveTyped; + (_ObjEnum::MemberReference): + _Ptr memberReference; + (_ObjEnum::BinaryLibrary): // _ObjEnum::Assembly + _Ptr library; + (_ObjEnum::ObjectNullMultiple256): + _Ptr objectNullMultiple256; + (_ObjEnum::ObjectNullMultiple): + _Ptr objectNullMultiple; + (_ObjEnum::ArraySinglePrimitive): + _Ptr arraySinglePrimitive; + (_ObjEnum::ArraySingleObject): + _Ptr arraySingleObject; + (_ObjEnum::ArraySingleString): + _Ptr arraySingleString; + (_ObjEnum::CrossAppDomainMap): + _Ptr crossAppDomainMap; + (_ObjEnum::CrossAppDomainString): + _Ptr crossAppDomainString; + (_ObjEnum::CrossAppDomainAssembly): + _Ptr crossAppDomainAssembly; + (_ObjEnum::MethodCall): + _Ptr binaryMethodCall; + (_ObjEnum::MethodReturn): + _Ptr binaryMethodReturn; + */ + (_): u64 ptr; //std::error(std::format("Unexpected {}", objEnum)); + } +}; + +/* Should be an In variable with default non zero value in the future until we use a hash map */ +#define _OBJECTS_TRACKER_CNT 232 +#define _LIBRARIES_CNT 8 + +struct _Trackers{ + _ObjTracker currentObj; + /* TODO: this should really be an hash map, the algorithm that generated is unspecified */ + _ObjTracker objs[_OBJECTS_TRACKER_CNT]; + _Ptr libs[_LIBRARIES_CNT]; +}; + +std::mem::set_section_size(_TrackersSection, sizeof(_trackers)); + +DotNetBinnaryFormatter dotNetBinnaryFormatter @ 0x0; From a371de5ceb02e04d1a4648109a1082cc559c49ec Mon Sep 17 00:00:00 2001 From: ODeux Date: Thu, 3 Jul 2025 15:21:20 +0200 Subject: [PATCH 2/3] Add dotnet BinaryFormatter test --- .../test_data/dotnet_binaryformatter.hexpat.bin | Bin 0 -> 372 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/patterns/test_data/dotnet_binaryformatter.hexpat.bin diff --git a/tests/patterns/test_data/dotnet_binaryformatter.hexpat.bin b/tests/patterns/test_data/dotnet_binaryformatter.hexpat.bin new file mode 100644 index 0000000000000000000000000000000000000000..a3bb135c16f1a33b8949bf6a42bce0745d22ff55 GIT binary patch literal 372 zcmb_XO=|)%5M4huQtUy+o_fg-Sc0xr^soo9r5pkjkp^VjrtW>wN}bQU60)3_Gt$vhuVIZfKo}6qHHSg7w{Rs(#`lg z_*y$#OvOaxi#!*z=`3Gld=m@?4~2z*UYd-*gioUn&(OTNBiOyf6b<>X=B{{?^2XpH~> literal 0 HcmV?d00001 From a4a172e60ecb850dba9f2f898f0236b41268043a Mon Sep 17 00:00:00 2001 From: ODeux Date: Thu, 3 Jul 2025 15:23:30 +0200 Subject: [PATCH 3/3] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 55e004aa..13657731 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | DICOM | `application/dicom` | [`patterns/dicom.hexpat`](patterns/dicom.hexpat) | DICOM image format | | DMG | | [`patterns/dmg.hexpat`](patterns/dmg.hexpat) | Apple Disk Image Trailer (DMG) | | DMP | | [`patterns/dmp64.hexpat`](patterns/dmp64.hexpat) | Windows Kernel Dump(DMP64) | +| DOTNET_BinaryFormatter | | [`patterns/dotnet_binaryformatter.hexpat`](patterns/dotnet_binaryformatter.hexpat) | .NET BinaryFormatter | | DPAPI_Blob | | [`patterns/dpapblob.hexpat`](patterns/dpapiblob.hexpat) | Data protection API Blob File Format | | DPAPI_MasterKey | | [`patterns/dpapimasterkey.hexpat`](patterns/dpapimasterkey.hexpat) | Data protection API MasterKey | | DS_Store | | [`patterns/dsstore.hexpat`](patterns/dsstore.hexpat) | .DS_Store file format |