.NET Process Injection

Tim MalcomVetter
5 min readJul 25, 2018

--

For a while now, I have been saying that PowerShell is dead in high security environments. Yes, it still works in environments where they haven’t figured out how to monitor PowerShell or at least process creation commands and arguments, but as soon as a defensive team implements visibility into this space, defense (the blue team) has all the advantages over an adversary playing in this space.

No, obfuscated PowerShell probably doesn’t help. It may help against a non-human control, such as a dumb search filter, but obfuscated PowerShell actually stands out more than regular looking PowerShell, and in practice my team finds that it can be an easy way to get caught.

Fast forward to yesterday. SpecterOps released a whole new slew of adversary kit written in C#, most of which is a re-write of the PowerShell tools that team has released over the past few years. Why is this relevant? Because C# and PowerShell share the same underlying technology — the dotnet runtime — but all of the defensive telemetry that has come out in the past few years has been focused on PowerShell itself, or if and endpoint security tool focused more abstractly, they focused on process creation, namely the executable name and its arguments.

In the latter example, both:

powershell -iex [blah]

and

net user [blah] /domain

will fall into the visibility of the defenders. This is why, in today’s most secure environments, adversaries should view process creation as EXPENSIVE. Creating a process comes with a high cost, and that cost is visibility by defenders.

Two key events — initial access and persistence — require living within a process and typically require creating a new one, so it is necessary overhead for the adversary. However, a wise operator will probably limit how often their adversary capital is spent on things like process creation.

In the past, that’s where things like DLL injection have been handy — there are less new processes in existence (plus sometimes there are added benefits from the parent process’s access). However, calls to CreateRemoteThread can be noisy and immediately picked up via endpoint telemetry, so it has less and less appeal to an adversary in a high security environment.

Given SpecterOps C# tools release yesterday, we can probably view that event as the high watermark that we are living within the golden age of offensive .NET assemblies. Why? Because the same powerful libraries behind PowerShell were behind C# for many years before PowerShell was ever created, and most of the Blue Team telemetry for PowerShell is irrelevant against C#. What’s old is new again, as they say.

But, as we traverse down this path together, process creation is still expensive and CreateRemoteThread still has its pitfalls. Not to mention that as specific tools are created and released with published binaries, then AV vendors will publish signatures for those binaries, which adversaries will want to bypass.

If only there were other ways to load these offensive .NET assemblies from memory straight into the CPU? How about a simple method that uses only native .NET runtime features, so no additional resources are required? Even better, since we have to build tools quickly and don’t often have time to refactor another offensive developer’s tools, wouldn’t it be nice to have a method that doesn’t know anything about the binary you’re injecting? How about a method that just takes a base64 encoded string of bytes?

There have been examples on StackOverflow and MSDN forums for years that show methods for doing this in C#, but every example I discovered requires the developer to know the assembly’s class names at compile time. A bit of digging into MSDN docs and exploring breakpoints in Visual Studio, and we now have something like this:

Let’s walk through the code …

First, this is a static class, which means it can be easily pulled into your toolkit in just one or two lines of code, like this:

string b64 = [some base64 encoded byte array of a .net assembly];

ManagedInjection.Inject(b64);

Inside the Inject() method, the bytes are reassembled from base64 and then the System.Reflection namespace (which is used all the time legitimately, adding to the complexity of good defensive telemetry options) iterates over the binary to determine the class/type names at runtime. The adversary doesn’t have to specify them in this loader code, which is good for both OPSEC reasons and because it makes the code much more complex.

Then the Inject() method instantiates the object — so if the binary you’re passing in can run from its constructor, then you’re done — it will do what it needs to do.

If it needs a little more help outside of the constructor, then you can pick an initial method name by a convention (in this case I’m choosing “Main()” since most offensive tools are console apps and console apps must have a Main() function). Use your imagination or just write a wrapper object that does what it needs to do via its constructor.

In the PoC repo in my github, you can see an example DLL executing by its constructor as well as a Main() method, and a console app being executed by only its Main() method. Keep in mind this PoC code reads the DLL/EXE files as byte arrays, transforms them to base64, and passes them in, but in practicality, this could be part of a larger adversary toolkit where the base64 strings are pulled as modules from a C2 server straight into memory, and possibly the underlying bytes could be encrypted from the C2 server and decrypted at the point of loading the assembly, which further minimizes opportunity for defensive inspection.

I’ll leave retrieving the results from the injected assembly as an exercise to the reader, but essentially all you have to do is grab the object and cast it to the correct class to retrieve data from its members.

As always, YMMV (your mileage may vary) in your environment.

Source Code: https://github.com/malcomvetter/ManagedInjection

--

--

Tim MalcomVetter
Tim MalcomVetter

Written by Tim MalcomVetter

Cybersecurity. I left my clever profile in my other social network: https://www.linkedin.com/in/malcomvetter

Responses (1)