This is the final blog post I’m going to do on Windows 10 S. The previous parts are here, here and here. Originally, I was going to describe a way of completely removing the SI policy on Win10S without upgrading to Pro so that you can install any applications you like *ahem* while keeping secure boot etc. intact.
However, I decided that enforcing the SI policy was more about licensing than it was about security so I’ve thought better of it. If you really want to run arbitrary applications on your own computer:
- Don’t buy a Windows 10 S machine in the first place.
- Or, failing that, upgrade to Pro, at least for the Surface Laptop that’s still currently free.
- Or, failing that, work out how I removed the policy, it’s not that hard ;-)
Instead of disabling the entire policy, I’ll detail another DG bypass. In this case, it’s exploiting the same root cause as the previous one I disclosed, .NET loading untrusted code from a byte array through serialization, but with an interesting twist (*spoiler* it’s not using BinaryFormatter, well mostly). Therefore, I don’t think it makes that much difference to disclose. MS, or at least the .NET team (hi Barry), are unlikely to fix the fundamental incompatibility between DG and .NET any time soon.
Is That You, NetDataContractSerializer?
It turns out that BinaryFormatter and .NET remoting was just too dangerous to let live and MS finally removed it from .NET. Just kidding, MS did no such thing. While MS might try and put scary, if somewhat small, warnings when you try and search for documentation on .NET remoting and BinaryFormatter, both technologies are still there in the .NET framework and no warnings are produced when using them. In fact BinaryFormatter is so awesome, it’s coming back in .NET Core 2.0 which is a bit of a shame IMHO.
What did happen in version 3.0 of the .NET Framework was the introduction of Windows Communication Foundation (WCF), a new object communication stack for accessing remote services. Learning well from the past, MS chose to use XML Web Services (well perhaps didn’t learn that well from the past) and instead of BinaryFormatter, they implemented a new serialization mechanism, Data Contracts. The canonical implementation of the WCF Data Contracts is the DataContractSerializer (DCS) class. In order to use the DCS class for serialization you are supposed to annotate your classes and properties with the DataContractAttribute and DataMemberAttribute. Explicitly annotated, Data Contracts are not that interesting, however, clearly someone decided that it’d be great if there was a way of serializing existing serializable classes. Therefore, DCS also supports serializing arbitrary classes as long as they have the SerializableAttribute annotation, for example if you have the following C# class:
namespace DCSerializer {
[Serializable]
public class Contract {
public int Value;
}
}
[Serializable]
public class Contract {
public int Value;
}
}
<Contract
xmlns="http://schemas.datacontract.org/2004/07/DCSerializer"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Value>1234</Value>
</Contract>
xmlns="http://schemas.datacontract.org/2004/07/DCSerializer"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Value>1234</Value>
</Contract>
In theory, there’s enough information to deserialize this XML file without any special knowledge, the namespace (DCSerializer) and the class name (Contract) and reflected in the default XML namespace and root element name respectively. However, what’s missing here is a reference to what assembly the Contract type exists in. This ambiguity is resolved by requiring that all known types (outside of some specific system types) must be specified during construction or through a resolver. This isn’t a problem in a simple, well defined web service. But it does make DCS less useful as a general, exploitable serializer.
While DCS is awesome in its own way, the requirement for specifying all types is a weakness, from a lazy developer point of view. It would be nice if you could get some of the flexibility of the more general serializers such as BinaryFormatter. This is where the similar but different NetDataContractSerializer (NDCS) class comes into the picture. Both DCS and NDCS (and the related DataContractJsonSerializer) derive from the XmlObjectSerializer class. This allows NDCS to be used for WCF services instead of DCS if you so desire. Serializing the previous class through NDCS generates the following:
<Contract
z:Id="1"
z:Type="DCSerializer.Contract"
z:Assembly="DCSerializer, Version=1.0.0.0"
xmlns="http://schemas.datacontract.org/2004/07/DCSerializer"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Value>1234</Value>
</Contract>
z:Id="1"
z:Type="DCSerializer.Contract"
z:Assembly="DCSerializer, Version=1.0.0.0"
xmlns="http://schemas.datacontract.org/2004/07/DCSerializer"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Value>1234</Value>
</Contract>
The output from NDCS includes assembly information. Therefore NDCS works in a similar way to BinaryFormatter in that it doesn’t need any prior knowledge of the types being deserialized. This makes NDCS the equivalent of BinaryFormatter but in XML format, though to be fair .NET already had something similar with the SoapFormatter. This is a long winded way of saying, if you can find an application which will load an untrusted NDCS XML file, you can exploit it with the exact same set of serialization gadgets as you can with BinaryFormatter from my previous post. The question therefore is, does such an application exist? Let’s see just one example.
The Ways of InstallUtil
InstallUtil is a .NET utility which is pre-installed with the .NET Framework. The utility has been available since at least v1.1 (I don’t have anything with v1.0 to check). Its purpose is to allow you to run installation code from an assembly so that you can configure system state and install your code. To use it normally, you first define a class which derives from the Installer class, annotate your class with the RunInstallerAttribute and then implement one of the main callback methods such as Install.
For example, the following class is sufficient to be executed by InstallUtil:
For example, the following class is sufficient to be executed by InstallUtil:
[RunInstaller(true)]
public class TestInstaller : Installer {
public override void Install(IDictionary stateSaver) {
Console.WriteLine("Hello from the Installer");
base.Install(stateSaver);
}
}
public class TestInstaller : Installer {
public override void Install(IDictionary stateSaver) {
Console.WriteLine("Hello from the Installer");
base.Install(stateSaver);
}
}
If you compile the class into an assembly, you can then run the installer using the following command line and it will execute the Install method in your assembly:
InstallUtil path\to\installer.dll
The interesting thing about InstallUtil is it’s a known Application Whitelisting bypass (specifically against something like AppLocker). The executable is Microsoft signed, located in a system directory and will execute code from an arbitrary assembly file passed on the command line. However, what it isn’t is a DG bypass. InstallUtil loads the assembly from a file, the file needs to be allowed to load in the SI policy, which means for Win10S we can only load existing assemblies signed by Microsoft. We might be able to find an assembly with an installer we could abuse, but I didn’t look very hard. Still, that doesn’t mean we can’t abuse InstallUtil in other ways.
If you run the simpler installer through InstallUtil, you might notice a file which gets created next to the installer assembly file which has an InstallState extension. This file begs for closer inspection. Opening it in a text editor you’ll encounter content which looks awfully familiar:
<ArrayOfKeyValueOfanyTypeanyType
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema"
z:Id="1"
z:Type="System.Collections.Hashtable"
z:Assembly="0"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
...
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema"
z:Id="1"
z:Type="System.Collections.Hashtable"
z:Assembly="0"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
...
This looks a lot like the output from a NDCS serialization. To confirm we can just go looking at the code in a decompiler, the assembly doesn’t seem to be available in the reference source. InstallUtil is actually just a thin wrapper around the ManagedInstallerClass class which is implemented in the System.Configuration.Installer assembly. Poking around a bit, we find that AssemblyInstaller is using NDCS in a number of places. We’re not too interested in places where the NDCS is used to write out objects, rather, we’re more interested in places where it’s reading. For example in the Uninstall method, there’s the following code:
public override void Uninstall(IDictionary savedState) {
string installStatePath = GetInstallStatePath(Path);
if (File.Exists(installStatePath)) {
FileStream fileStream = new FileStream(installStatePath);
XmlReader xmlReader = XmlReader.Create(fileStream);
var ser = new NetDataContractSerializer();
IDictionary savedState = ser.ReadObject(xmlReader);
// Run uninstaller...
base.Uninstall(savedState);
}
}
string installStatePath = GetInstallStatePath(Path);
if (File.Exists(installStatePath)) {
FileStream fileStream = new FileStream(installStatePath);
XmlReader xmlReader = XmlReader.Create(fileStream);
var ser = new NetDataContractSerializer();
IDictionary savedState = ser.ReadObject(xmlReader);
// Run uninstaller...
base.Uninstall(savedState);
}
}
From this snippet of code, we can see that the untrusted install state file it loaded verbatim with a insecure NDCS class instance. If we can can convince InstallUtil to load a crafted install state file which contains a deserialization chain to load an assembly from a byte array, we can bypass DG. While we can’t load untrusted assemblies, the utility doesn’t need a specific assembly so we can just instruct it to uninstall a system assembly such as mscorlib. Don’t worry, it won’t actually do anything as mscorlib doesn’t contain any installers. Also, looking at the documentation there’s a InstallStateDir parameter we can pass to specify where the utility will look for our install state. If we copy the serialized file to c:\dummy\mscorlib.InstallState then we can get the DG bypass by running the following command:
InstallUtil /u /InstallStateDir=c:\dummy /AssemblyName mscorlib
I’ve updated my DG bypass Github repository to include this bypass as well. Run the CreateInstallState utility passing the path to the assembly to load (again it will instantiate the first public type it finds) and the output filename such as mscorlib.InstallState. Execute the previous InstallUtil command and you should get your assembly executed. Note that InstallUtil will try and delete the InstallState file after use, if you don’t want that to happen you can just set the Read-Only flag on the file and the delete will fail.
The main advantage to this DG bypass over the previous one I disclosed in AddInProcess is that it’s easy to use for persistence. Just add a scheduled task which runs InstallUtil or a LNK file in the startup folder with the appropriate command line and the code DG bypass will run when you login.
As a final note, you might wonder how InstallUtil serialized the install state prior to v4 of the Framework, specifically as NDCS was only introduced in v3.0? Dropping the v2 System.Configuration.Installer assembly into the decompiler we find it uses, *drum roll* SoapFormatter. So it’s just as vulnerable to this attack in v2, assuming you’ve got v2 compatible gadgets (most v2 installs are really v3.5 so that’s typically a yes as the gadgets I presented in the previous post were introduced in v3.0).
While this bypass exists currently in Win10S and likely in many custom DG policies, it’s easy to just ban InstallUtil as you would with AddInProcess and this would eliminate the bypass. Again I’ll give you a link to Matt Graeber’s blog post about adding new executables to your DG policy.
Final Wrap Up
This is the end of my planned series on Win10S. Hopefully, I’ve demonstrated that regardless of the PR coming out of Microsoft that it’s not 100% secure, at least against anyone who knows you run Win10S and is willing to customize an attack to you or your organization. There will always be bypasses for DG, and the way Windows works, it’s almost impossible to completely lock it down. If it wasn’t .NET, it’d be a memory corruption vulnerability from an overlong command line parameter or something equally silly.
Does Win10S have no value what so ever? Of course not, DG is a good, if not perfect, way of limiting a system to a very a specific set of signed executables. I’d be less sceptical about Win10S if it hadn’t become so transparently a marketing ploy rather than a goal to really push the Windows platform forward. Unfortunately, I can’t see the goal of a secure Windows platform ever being reached without completely jettisoning all the reasons that Windows works for people at the moment.
I’d like to thank Matt Graeber for his knowledge of Device Guard and for doing reviews of these posts to make sure I’m not talking complete rubbish. Also shout outs to everyone looking for these sorts of issues such as Matt Nelson, Casey Smith, Alvaro Muñoz and no doubt others I’m forgetting.
0 Comments