Wednesday, 2 August 2017

DG on Windows 10 S: Analysing the System

In the previous post, we got arbitrary .NET code execution on Win10S without needing a copy of Office or upgrading to Windows 10 Pro. This, however doesn’t really achieve our ultimate goal of trying to run any applications we like, while UMCI is enforced. We can use the arbitrary code execution to run some analysis tools to understand Win10S better and facilitate further modifications to the system.

This post is mainly about how to implement a harness to load more complex .NET content in as simple a way as possible including getting back a full PowerShell environment (within reason) without needing to run powershell.exe - one of the many blacklisted applications.

Dealing with Assembly Loading

Our simple .NET assembly we used in the last post only had dependencies on built-in, system assemblies. These system assemblies are supplied with the OS and so are signed with a Microsoft Windows Publisher certificate. This means the system assemblies are permitted to be loaded as image files by the system integrity policy. Anything we build ourselves of course isn’t going to be permitted to be loaded from files.

As we’re loading our assembly from a byte array, the SI policy doesn’t apply. For simple assemblies with only system dependencies, this isn’t necessarily an issue. However, if we want to load more complex assemblies, which reference other untrusted assemblies, we’ll have more difficulty. Due to .NET using late binding it’s possible you might not immediately see an assembly loading issue, only when you try and access a method or type from that assembly will the Framework try and load it, leading to exceptions.

When an assembly is loaded, the Framework will parse an assembly name. Can we not just pre-load a dependent assemblies from a byte array, then let the loader resolve it when required? Let’s try that by loading an assembly from a byte array and then reloading it by name. If preloading works, the load by name should be successful. Compile the following simple C# application which will load the assembly from a byte array then try and load it again by its full name:

using System;
using System.IO;
using System.Reflection;

class Program
{
   static void Main(string[] args)
   {
       try
       {
           Assembly asm = Assembly.Load(File.ReadAllBytes(args[0]));
           Console.WriteLine(asm.FullName);
           Console.WriteLine(Assembly.Load(asm.FullName));
       }
       catch (Exception ex)
       {
           Console.WriteLine(ex.Message);
       }
   }
}

Now run this application and pass it the path to an assembly to load (ensure the assembly is outside of the directory you compiled the above code to). You should see output similar to the following:

C:\build\LoadAssemblyTest> LoadAssemblyTest.exe ..\test.dll
test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Could not load file or assembly 'test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

I guess it doesn’t work as loading the assembly by name throws an exception. This is a limitation in the way .NET loads assemblies from byte arrays. The name of the loaded assembly isn’t registered in any global assembly table. On the one hand this is good, it allows multiple assemblies with the same name to coexist in the same process. On the other hand it’s bad, as it means that if we don’t directly reference the Assembly instance we can’t access anything in that assembly. As referenced assemblies are always loaded by name, this means that no amount of pre-loading it going to help get more complex assemblies to work.

The .NET Framework provides a solution to this problem, you can specify an Assembly Resolver event handler. The Assembly Resolver event is called whenever the runtime fails to find an assembly either from the loaded assembly list or from a file on disk. This typically happens if the assembly is located outside of the application’s base directory. It should be noted however that if the runtime finds a file on disk which matches its criteria it will try and load it. If this file isn’t permitted by the SI policy, the load will fail, however, the runtime does not consider that to be a failure from a resolving perspective so the runtime will not call our event handler in that case.

The event handler is passed a name to resolve. This name could be a partial name, or a full Assembly Name with additional information such as PublicKeyToken and Version. Therefore, we’ll just pass the name string to the AssemblyName class and use that to extract just the name of the assembly, nothing else. We can then use that name to search for a file with that name and a DLL or EXE extension. In the example bootstrap I’ve put up on Github, this defaults to searching either in the assembly directory in your user’s documents or an arbitrary list of paths specified in the ASSEMBLY_PATH environment variable. Finally, if we find the assembly file, we’ll load it from a byte array and return it to the event’s caller, making sure to cache the assembly file for later queries.

AssemblyName name = new AssemblyName(args.Name);
string path = FindAssemblyPath(name.Name, ".exe") ??
             FindAssemblyPath(name.Name, ".dll");
if (path != null) {
   Assembly asm = Assembly.Load(File.ReadAllBytes(path));
   _resolved_asms[args.Name] = asm;
   _resolved_asms[asm.FullName] = asm;
}
else {
   _resolved_asms[args.Name] = null;
}

The final step in the bootstrap code is to load an entry assembly using the ExecuteAssemblyByName method. This entry assembly should contain a Main entry point, be called startasm.exe and placed in the search path. You could put all your analysis code inside a single the bootstrap assembly but that can quickly get large, and sending the serialized data to the AddInProcess named pipe isn’t exactly efficient. Plus by starting a new executable it’s possible to quickly replace the functionality you want to run without needing to regenerate the scriptlet everytime you change the assembly.

Note that if any assembly you want to load contains native code (e.g mixed mode CIL and C++) then this isn’t going to work. To run native code you need to make load the assembly as an image, it doesn’t work from the a byte array. Of course pure managed CIL can pretty do everything native code can do, so always write your tools in .NET.

Bootstrapping a PowerShell Console

With the ability to execute any .NET assemblies by name including any dependencies, it’s time we get an interactive environment running. And what better interactive environment to run than our old friend PowerShell. With PowerShell being written in .NET it would make perfect sense that powershell.exe is also a .NET assembly.

powershell.PNG

I guess not, though it’s worth noting that the Powershell ISE is a full .NET assembly, so we could load that instead. But I prefer the command line version in most cases. There’s research into getting PowerShell without powershell.exe, but at least in the examples I know such as https://github.com/p3nt4/PowerShdll they don’t tend to do it in a nice and easy way. Typically, they implement their own shell and pass PS scripts from the command line into a PowerShell runspace. Fortunately we don’t have to guess how powershell.exe works, while we could reverse engineer the binary the core of the executable is now open source. For example here’s the unmanaged code for starting the console.

Boiling it down to the simplest code possible, the native entry point creates an instance of the UnmanagedPSEntry class and calls the Start method. As long as there exists a console for the process, calling Start present a fully working PowerShell interactive environment. While AddInProcess is a console application already, you can call AllocConsole or AttachConsole to create a new console or attach to an existing console if necessary. We can even set the console title and icon while we’re at it to give us the warm feeling of running full PowerShell.

AllocConsole();
SetConsoleTitle("Windows Powershell");
UnmanagedPSEntry ps = new UnmanagedPSEntry();
ps.Start(null, new string[0]);

That should be it, we’ve got PowerShell running, everything's fine, at least until you start using the console. At which point you might encounter an error:

constrained.PNG

Seems that while we’ve successfully bypassed the UMCI check for image loading, PowerShell still tries to enforce Constrained Language mode. This makes sense, all we’ve done is cut out loading powershell.exe, not the rest of the UMCI lockdown policy for PowerShell. The check for what mode to run is through the GetSystemLockdownPolicy method in the SystemPolicy class. This calls into the WldpGetLockdownPolicy function in the Windows Lockdown Policy DLL (WLDP) to query for the what to do for PowerShell. By passing null as the source path the function returns the general system policy. This function is also the entry point for checking for the policy for individual files, by passing a path to a signed script the policy can be enforced selectively for scripts. This is how signed Microsoft modules run with Full Language mode while the main shell might run as Constrained. Having a look around it’s clear that the SystemPolicy class is caching the result of the policy lookup in the private systemLockdownPolicy static field. Therefore, if we use reflection to set this value to SystemEnforcementMode.None before calling into any other PS code we’ll disable the lockdown.

var fi = typeof(SystemPolicy).GetField("systemLockdownPolicy",
       BindingFlags.NonPublic | BindingFlags.Static);
fi.SetValue(null, SystemEnforcementMode.None);

Doing this results in our desired PowerShell with no lockdown restrictions.

full_language.PNG

I’ve uploaded the RunPowershell implementation to Github. Build the executable and copy it to %USERPROFILE\Documents\assembly\startasm.exe then execute the bootstrap code using the previous DG bypass.

Poking Around the System

With PowerShell up and running, we can now do some inspection of the system. One of the first things I ensured I could do was to install my NtObjectManager module. The Install-Module cmdlet doesn’t work so well as it tries to install the NuGet module which won’t load under the lockdown policy. Instead you can just download the module’s files and if you specify the module’s directory in the list of assembly paths for the bootstrap you can just import the PSD1 file and it should load successfully.

At this point, you can poke around yourself. I’ve added a couple of methods to the NtApiDotNet assembly to dump system information about the SI policy. For example there’s NtSystemInfo.CodeIntegrityOptions which dumps the current CI enabled flags as well as NtSystemInfo.CodeIntegrityFullPolicy which is a new option on Windows Creator Update (presumably for Win10S support) which dumps all configured CI policies. The interesting thing about this when run on Win10S is that there’s actually two policies enforced, the SI policy and what seems to be a revocation policy of some sort. By extracting policies this way, we should be able to ensure we’ve got the correct policy information that the system is enforcing, not just the file we think is the policy.
code_integrity.PNG

Finally, I’ve added a PowerShell cmdlet New-NtKernelCrashDump to create a kernel crash dump (don’t worry it doesn’t crash the system) as long as you’ve got SeDebugPrivilege, which you can get by running AddInProcess as an administrator. While this doesn’t allow you to modify the system, it does at least allow you to poke at the internal data structures to see what’s what. Of course you’ll need to copy the kernel dump to another system in order to run WinDBG.

kernel_dump.PNG

Wrap Up

This blog post was a very quick write up of getting more complex .NET content running on Win10S once you’ve got your foot in the door. I’d advocate writing your analysis tools in .NET wherever possible as it just makes running them on a locked down system that much easier. Sure, you could use a reflective DLL loader, but why go to that level of effort when .NET already wrote it for you.