Skip to content

Tools, Techniques, and Grimmie?: Fun with Assemblies and Reflection!

This is the first of a series I’ve been thinking about for some time, so without further ado, I present to you: “Tools, Techniques, and Grimmie?”. This series will focus on exploring techniques I come across that I find interesting and would like to share. This series will not be intended for beginners and will be observing more advanced concepts and will lean towards somewhat theory-heavy side.

In this first installment, I’m going to explore the concept of reflectively loading custom code which will allow us to run code entirely from memory. Before getting to the writing and compiling our custom code to run, let’s take a look at this reflective loading thing.

Reflective Loading Theory

The technique of Reflective loading leverages the System.Reflection namespace to access the contents of managed code via metadata, more on this namespace can be found here . Using this namespace alone isn’t too interesting, though combining it with some powershell, and we can weaponize this namespace in a rather interesting way (feature, not a bug). We can use the[Net.WebClient]::DownloadData() method to fetch our custom code and save it to a variable, which we can then load as an assembly object using the[Reflection.Assembly]::LoadData() method. From there, we’ll be able to interact with our custom code entirely from memory, no writing/saving to disk (more on assemblies here). Though this isn’t to say that this method doesn’t leave any artifacts, which we’ll also take look at.

Now that we have a general idea of how this technique functions, we can write a simple C# script ( nothing too crazy, I promise) that we’ll compile and then attempt to run as an assembly object.

Writing our Custom C# Code

We’re going to need Visual Studio for this, which can be found here. If it isn’t installed, go ahead and install it. Done? I’m gonna assume that’s a yes, moving on. Opening up Visual Studio, we’ll first have to create an executable and then compile that code into a managed DLL. Once Visual Studio is open, go to Create a New Project and select Console App (.NET Framework) then name it and create. Should look something like this:

Clear out the code auto generated and paste in the following:

using System;
using System.Runtime.InteropServices;

namespace loadme
{
    class Program
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options);

        static void Main(string[] args)
        {
            MessageBox(IntPtr.Zero, "This message box was opened leveraging the winAPI from within C#, pretty cool huh?", "Cool Message Box", 4);
        }
    }
}

Let’s go line by line and figure out what this code is doing.

using System;
using System.Runtime.InteropServices;

This part is telling C# what namespaces we’ll be using (basically libraries). The System is the base namespace and Runtime.InteropServices is the namespace that allows us to interact with winAPI using the DLLImport feature among many others.

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, int options);

This section of the code is importing the MessageBox method from user32.dll. The syntax for importing winAPI into C# can be found at P/Invoke. The MessageBox method has four arguments: a handle to the window (hWnd), the text to appear on the window (text), the caption for the window (caption), and the window option (option).

 MessageBox(IntPtr.Zero, "This message box was opened leveraging the winAPI from within C#, pretty cool huh?", "Cool Message Box", 4);

This last chunk is the part of the code that actually calls and interacts with the winAPI method. We’re leaving the window handle up to windows to decide, passed the text and caption respectively, and then selecting the window option 4 (feel free to play around with the different options). Executing this code will prompt a message box that looks like this:

Now that we have the code and have verified it works as intended, we’ll compile it as a managed DLL this time. Select the Create New Project option > Class Library (.NET Framework). Once created, copy the DLLImport snippet into the class and create a public static void method where the code from the Main will go and should look like this:

Once the code is transferred over, compile the managed DLL by building the solution. Build > Build Solution.

Playing with Assemblies: Act I

Now that we have our managed DLL compiled, we’ll use the Reflection.Assembly namespace to load our custom code into an assembly object and interact with it the way we would any other .NET object. (Note that this will also work using that executable we compiled as well)

And it works! We’re able to call our code the same way we would any other .NET assembly, this is kinda cool…but we can do better.

Playing with Assemblies: Act II

For this next part, we’ll use the same code but host it on another machine. We’ll then use the DownloadData method from Net.WebClient to load the data as a byte stream. This byte stream will then be loaded into an assembly and executed similar to the method above. This solves a few issues. There is no writing to disk, which is a big one, and we can interact with the code the same way with any other .NET assembly object. Oh yeah, and it’s fun. We’ll also take a look at the artifacts this method generates.

We’ll use the same code as before, the only difference, in this case, is our managed DLL will be stored in a different location. For this, I’ll be using my Ubuntu WSL to host the managed DLL.

Move the dll over to a WSL instance/VM and start up a webserver to host the file. We’ll then save the data as a byte stream using aNet.Webclient object into a file and reflectively load it.

We’ve successfully loaded a file from another machine as an assembly object on our machine and executed it! The DLL was never at any point actually downloaded to the machine, thus no writing to disk. In this case, the code uploaded only had a single class to serve as a PoC for this technique but there is nothing stopping us from using something with various methods (i.e something like Rubeus).

Hunting for Breadcrumbs

Let’s now take a look at ProcMon to verify nothing was written to disk and see what happens when these commands are run. The only process we want to view is powershell.exe so we’ll set a filter to include just this process.

We can open the filter menu by click on the funnel icon on the top left, we’ll also want to enable viewing of everything so light up all the boxes in the right most section save the last one (that one is process profiling and will output stuff we don’t want). From left to right the first shows registry related activity, the second shows file io operations, the next one shows network activity (we’ll want this for our first line), and the last displays thread info. Now that we’re semi acquainted with ProcMon, let’s to do some quick analysis to get an idea of what’s going on when these commands are executed.

We’ll run the commands line by line and check in with ProcMon to see what it does.

This line reaches out to the webserver hosted on my WSL instance and fetches the dll as a byte stream, which is stored in data.

Looking through the operation section, we can see that there are three types of operations being executed here: file io, thread activity, and network activity. Taking a closer look at the files, we can see that powershell.exe is writing to turns out to be the PS history file. We then see that a few threads are created followed by the (powershell.exe) process reaching out to my WSL instance and fetching some data. After the network operations complete, the threads that were opened are closed.

As expected, PS reaches out to the webserver and fetches the file but doesn’t write the data in that file anywhere, right now it’s just holding onto the the data.

Again we see PS logging the command executed, though nothing eventful past that. There are reports of threads being created and closing, which can be anything.

The final command, which executes our code, returns something slighty more interesting though still a bit uneventful (which is good for us).

The PS line is logged in the history files as expected, though this time we see something a bit more interesting. When we run the code, powershell.exe seems to interact with a few reg keys. The following keys in specific:

HKLM
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\LaunchUserOOBE
HKCU
HKCU\Software\Microsoft\CTF\DirectSwitchHotkeys
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\IsVailContainer
HKLM\Software\Microsoft\Input
HKLM\SOFTWARE\Microsoft\Input\ResyncResetTime
HKLM\SOFTWARE\Microsoft\Input\MaxResyncAttempts

I’ve gone overHKCU andHKLM here so I won’t be going over those in this write-up. We can see that there are calls made to both LM and CU, though mainly LM.

OOBE stands for Out-of-Box-Experience. From some quick reading around, I found that this OOBE is the process Windows puts users through when installing windows. HKCU\Software\Microsoft\CTF\DirectSwitchHotkeys seems to have something to do with keyboard settings, I’m thinking it’s called to check the keyboard layout for the message. The next few reg queries are looking at versions for windows and WSL (IsVailContainer seems to reference WSL). The remaining queries are to HKLM\SOFTWARE\Microsoft\Input, which seems to be another one related to keyboard and language settings.

Since there are quite a few keys relating to keyboard/lauguage settings, I think it may be safe to assume this is caused by calling the MessageBox method from the user32.dll (recall “Win32API” is really just a collection of dlls managing general tasks for the OS to function).

Here Strings? No, over here!

To verify these reg keys are being called from the MessageBox method from our code, we’ll use a PS feature called “here-strings”. Here-strings allow us to store multiple lines in a variable (this means we can write code and store it into a variable). Not only can code be stored into variables, but this code can be “imported” and executed in a similar manner to how an assembly is executed. We’ll then filter out everything except the powershell.exe process on ProcMon again and see what kind of activity this generates.

Here-strings is a feature that allows us to store multiple lines of data into a variable, used as follows:

$[varname] = @"
[code/comments here]
"@

We have a general template, lets write the C# code used to call the MessageBox method within a variable using here-strings.

Now that we have our code stored into a variable, we’ll use Add-Type to import the code. This will allow us to interact with the code contained in the Here-string.

We’ve used here-strings to run C# code that reaches out an interacts with Win32API (which I find pretty awesome). Now let’s take a look at the output of ProcMon and see how it compares to before.

Going from the top, we’re met with the usual writing to PS history. After a few ThreadCreate operations, we can see that there are reg keys being queried. While they aren’t exactly the same queries the populated when running the assembly object, these keys also serve to function as keyboard layout/language settings.

And the Verdict is…

Comparing the ProcMon output of the MessageBox method being called as an assembly object and as an imported here string, there isn’t much difference between the two. This solidifies the theory that the calling of the MessageBox method was the cause for the reg queries.

Taking a look at the activty on ProcMon when the DLL is called from the WSL webserver, we can see that my windows host (aegis.mshome.net) is (connecting from an ethereal port) to and receiving something from the WSL webserver (172.21.243.1..) . This could potentially be traced back though there are a number of ways to hide or redirect where Windows is reaching out to.

The loading of the byte stream into an assembly object doesn’t seem to make much noise, as the only thing noticed were ThreadCreate and ThreatExit operations which could be caused by a million benign things and wouldn’t raise any alarms. The only way to analyze further would be to do some in-mem forensics, which is beyond the scope of this write-up.

Compared to using something like wget to pull down a file from a webserver and write to disk, transferring a file using reflective loading would be far more favorable as it all happens in memory. There are a million other ways to transfer (and execute) files in memory, this is just one possible way to go about it.

Published inAdvanced TopicsTools, Techniques, and Grimmie?