I’ve recently been experimenting with the full, offensive capabilities of MSBuild.exe. As a reference, MSBuild.exe ships with the .NET framework and comes installed by default on Windows 10.
“Starting in .NET Framework version 4, you can create tasks inline in the project file. You do not have to create a separate assembly to host the task. This makes it easier to keep track of source code and easier to deploy the task. The source code is integrated into the script.”
Specifically, the aspect of MSBuild.exe that we are leveraging is called an Inline Task.
This feature allows us to specify C# code in an XML file that gets compiled and executed in memory when called. The C# code that is included in the XML task, is compiled and loaded as a byte array in memory. This gives us the huge advantage, since this will likely not be picked up by tools that monitor library loads from disk like Application Whitelisting or Anti Virus. Really, if you think about it, this is every bit as powerful as PowerShell, without all the logging and detection that comes with PowerShell these days. By leveraging pure C# you can avoid the PowerShell Logging.
This idea led me to exploring the full potential of using MSBuild.exe for bypassing UMCI (User Mode Code Integrity). For example, to expand our influence on the local system, we could exploit a local, approved vulnerable driver. Normally, this process is kicked off by a user mode process to interact with the driver. However… with UMCI in place, your PoC binary won’t execute and you are stuck… This blog will describe the details and methods that you can call to interact with the driver using the .NET framework, hosted inside MSBuild.exe.
If you would like to review the code sample. I’ve posted it here.
First a bit of background on the driver, and the exploit technique. I stumbled upon this driver on GitHub a couple weeks ago. All the research on this exploit was done by Shahriyar Jalayeri
( @ponez )
I wanted to see if I could port the exploit to C# so that I could use MSBuild.exe to execute it. And the answer is yes, yes you can ;-).
In this example, we are going to exploit a Write-What-Where vulnerability. This means that we can overwrite a location controlled by the Windows kernel. The question is, how do we identify the “what” and the “where”? There is an excellent book here that provided a lot of the background for me.
A couple of preliminary steps:
First, we need to locate the base address of ntoskrnl.exe. This can be done using the following routine here. All of this can be done as a normal user. As my colleague Matt Graeber (@mattifestation) pointed out, this is a fantastic heuristic for a sysmon rule. i.e. detection of any PID besides 4 (system process) that loads ntoskrnl.exe. There is no legitimate purpose for this. lt is almost certainly guaranteed to be malicious.
Second, once we have the base address of the kernel, we need to locate the offset to important structures and functions. Here, we load ntoskrnl.exe into our process so that we can calculate offsets and calculate the values we need to overwrite.
“After discovering the correct base address of the Kernel Executive, we will be able to relocate whichever exported function we’d like to move by simply loading the same binary image in user land and relocating the relative virtual address (RVA) using the real kernel base address leaked by that function. Do not confuse RVAs with virtual memory addresses. An RVA is a virtual address of an object (a symbol) from the binary file after being loaded into memory, minus the actual base address of the file image in memory. To convert an RVA to the corresponding virtual address, we have to add the RVA to the corresponding module image base address. The procedure to relocate Kernel Executive functions, hence, is straightforward. We have to load the kernel image into user-mode address space via the LoadLibrary() API, and then pass the HMODULE handle to a function which resolves the RVA…” - Guide to Kernel Exploitation p276.
Ok. Now we need to actually perform the overwrite. This is done by interacting with the driver and passing a malicious request that actually overwrites the locations we have found.
This is seen here.
I ended up just writing a bunch of shellcode with NOP instructions, and 0xCC breaks. I leave this up to you to experiment with to see what kind of interesting exploit you can come up with. My code is a simple POC. Be sure to check out https://www.fuzzysecurity.com/ to see some cool exploits using PowerShell, much of this can be ported to .NET easily.
Ok. So, what does this all mean? Well, I’m an advocate of Application Whitelisting, and one of the reasons for exploring this area was to demonstrate that a valid signed driver can undermine your local systems. The attacker can bring/install this driver and can use it to install rootkits or other malicious software running in the kernel context. The combination of a kernel mode and user mode bypass has significant implications.
Device Guard actually has a driver policy referred to as kernel mode code integrity (KMCI). This allows your organizations to approve drivers. Even if the driver is signed, you can prevent this driver from being loaded on your systems. It is important to have a UMCI policy. It is just as important to consider a KMCI policy as well. Drivers should be far less dynamic in your environment. You should know EXACTLY which drivers are deployed, and their versions. A good reference on that is here and on Matt’s blog.
That’s all I have today.