top of page
Search
perlafourceper

Injecting .Net Assemblies Into Unmanaged Processes



Offensive and red team tradecraft have changed significantly in the past year. As anti-malware systems improve their capability to detect and deter offensive tools, attackers are shifting their focus to technologies that are not observed by AV. Currently, that means operating entirely in memory and avoiding dropping files onto disk. In the Windows world, the .NET Framework provides a convenient mechanism for this. It is, however, severely restricted in that .NET programs cannot be injected directly into remote processes. In this article, we will address this issue by describing how to inject .NET code into processes via shellcode.




Injecting .Net Assemblies Into Unmanaged Processes



Donut is a shellcode generation tool that creates x86 or x64 shellcode payloads from .NET Assemblies. This shellcode may be used to inject the Assembly into arbitrary Windows processes. Given an arbitrary .NET Assembly, parameters, and an entry point (such as Program.Main), it produces position-independent shellcode that loads it from memory. The .NET Assembly can either be staged from a URL or stageless by being embedded directly in the shellcode. Either way, the .NET Assembly is encrypted with the Chaskey block cipher and a 128-bit randomly generated key. After the Assembly is loaded through the CLR, the original reference is erased from memory to deter memory scanners. The Assembly is loaded into a new Application Domain to allow for running Assemblies in disposable AppDomains.


Use ProcessManager, a sub-project provided in the donut repo, to enumerate processes. ProcessManager enumerates all running processes and makes a best effort to obtain information about them. It is specifically designed to aid in determining what process to inject / migrate into. The picture below demonstrates its general usage.


However, as I mentioned, this analytic fails to detect CLR Injection into processes that already have the CLR loaded. As such, an operator could evade the analytic by simply injecting into processes that are already managed. I would recommend the following standard operating procedure:


Offensive .NET tradecraft is faced with several important challenges. One of them is the lack of means to inject into remote processes at will. While this can normally be performed with shellcode, there is no way to produce shellcode that can run a .NET Assembly directly on hardware. Any shellcode that runs a .NET Assembly must first bootstrap the Common Language Runtime and load the Assembly through it. Enter Donut. With Donut, we now have a framework for generating flexible shellcode that loads a .NET Assembly from memory. This can be combined with existing techniques and tooling to advance tradecraft in a number of ways. Hopefully, this will break down the current barriers in .NET-based exploitation and provide tool designers with a foundation for crafting more excellent tools.


The dot net Framework can be found on almost every device running Microsoft Windows. It is popular among professionals involved in both attacking (Red Team) and defending (Blue Team) a Windows-based device. In 2015, the Antimalware Scan Interface (AMSI) was integrated with various Windows components used to execute scripts (VBScript, JScript, PowerShell). Around the same time, enhanced logging or Script Block Logging was added to PowerShell that allows capturing the full contents of scripts being executed, thereby defeating any obfuscation used. To remain ahead of blue teams, red teams had to go another layer deeper into the dot net framework by using assemblies. Typically written in C#, assemblies provide red teams with all the functionality of PowerShell, but with the distinct advantage of loading and executing entirely from memory. In this post, I will briefly discuss a tool called Donut, that when given a .NET assembly, class name, method, and optional parameters, will generate a position-independent code (PIC) or shellcode that can load a .NET assembly from memory. The project was a collaborative effort between myself and TheWover who has blogged about donut here.


Oracle.ManagedDataAccessDTC.dll - Only required when using distributed transactions. The assembly is fully managed, but has 32-bit and x64 versions depending on the .NET Framework's bitness in which it runs. The assembly makes calls to unmanaged assemblies.


OK. Now we're ready to talk about DLL injection. We're going to be pulling in a few more functions from the Windows API. We'll also be converting BloogBot from a console application to a WPF application so we a nicer GUI to work with. Additionally, we have a big problem. BloogsQuest is written in C++, but we're writing BloogBot in C# which runs on the .NET framework. BloogsQuest hasn't loaded the .NET runtime, so if we inject our C# DLL into BloogsQuest, BloogBot won't have access to the .NET runtime it depends on. This means we're going to need a separate Loader that first bootstraps the .NET runtime, then invokes BloogBot.exe at its entry point. So instead of injecting BloogBot into BloogsQuest's process, we're going to inject the Loader which will then load BloogBot from within BloogsQuest's process. We're also going to create a Bootstrapper console application that is responsible for starting our game in a new process, then injecting Loader.dll into that process.


First we're going to add a button to our GUI that we'll be able to click to invoke the yell function in BloogsQuest (again, I'm going to gloss over the details of the WPF code because it's far less interesting). Then I'll create a new Functions class that will be responsible for wiring up the function calls into unmanaged code. Take a look at the code:


The most important part of this snippet is the method GetDelegateForFunctionPointer. That's part of the System.Runtime.InteropServices namespace that allows us to wire up a delegate with an unmanaged function pointer. The first parameter we pass in is the memory address of that unmanaged function (BaseAddress + 114B0 in this case). Remember, at this point we've injected BloogBot into the BloogsQuest process, so we're able to use MainModule.BaseAddress to get the base address of BloogsQuest. The actual delegate we're wiring the fuction up with is defined toward the top of the class. You'll notice I have a single line of code commented out. That's to demonstrate an important note about calling conventions.


Module initializers are a feature of the CLR that is not available in C#. C# cannot do this because it puts everything in a class/struct and module initializers need to be globally defined. This is why we will be injecting this module initializer into the MSIL (or as it is called: IL weaving) after the C# code is compiled.


Since injecting the module initializer into the assembly must be done in the MSIL, obviously this process needs to be a post-build step. I created a console application for this, so I can easily add it as a post-build event for any assembly I want to use this technique for.


This means that this is an IoC, and if we find a process that should not have this DLL loaded, particularly if it is an unmanaged code binary (so not .NET) then it is highly likely that this is a process that has been injected into by a PowerShell implant, PoshC2 or otherwise.


A blacklist can be created of common target processes that are unmanaged, always available and should not be loading the .NET runtime, and these can be monitored, as well as noting this during manual triage.


In therms of unmanaged code, it simply relates to C/C++ and how the programmer is in charge of pretty much everything. The actual program is, essentially, a binary that the operating system loads into memory and starts. Everything else, from memory management to security considerations are a burden of the programmer.


Now, if we look back into our code, on line #9, we define our delegate that matches the signature of the callback from unmanaged code. Since we are doing this in C#, we replaced the C++ pointers with IntPtr - which is the the C# equivalent of pointers.


Donut generates x86 or x64 shellcode from VBScript, JScript, EXE, DLL (including .NET Assemblies) and XSL files. This shellcode can be injected into an arbitrary Windows processes for in-memory execution. Given a supported file type, parameters and an entry point where applicable (such as Program.Main), it produces position-independent shellcode that loads and runs entirely from memory. A module created by donut can either be staged from a URL or stageless by being embedded directly in the shellcode. Either way, the module is encrypted with the Chaskey block cipher and a 128-bit randomly generated key. After the file is loaded through the PE/ActiveScript/CLR loader, the original reference is erased from memory to deter memory scanners. For .NET Assemblies, they are loaded into a new Application Domain to allow for running Assemblies in disposable AppDomains.


I came across your page from your profile on the StackOverflow beta. Good article, but you should update it to mention that if your class library is a different version of .NET (i.e., injecting a .NET 3.5 library into a .NET 2.0 host) you can cause problems for the target app.


The .NET framework supports the loading of an unmanaged (something outside the .NET framework like C++, which interacts directly with hardware) profiler DLL as a code profiler to monitor a managed application. This feature is intended to allow the unmanaged profiler DLL to load into any .NET process and interact directly with callback interfaces through a profiling API to receive information about the state of the profiled application. Essentially, this means .NET developers can measure their managed code performance with an unmanaged DLL, which aids in troubleshooting and debugging their managed application.


In our experience with the COR_PROFILER technique, what results is a stealthy persistence mechanism that executes each time any process loads the .NET CLR. Native Windows processes like PowerShell and the Microsoft Management Console (mmc.exe) load the .NET CLR, and any installed application written in .NET will load the CLR. This results in the malicious profiling DLL loading into the memory space of each of those processes and deploying more traditional forms of persistence like services and scheduled tasks that seemingly appear out of thin air. 2ff7e9595c


1 view0 comments

Recent Posts

See All

Comments


bottom of page