This is the first in a new series from our offensive security experts at X-Force Red sharing research, resources and recommendations to help you harden your defenses and protect your most important assets.
Attackers and offensive security professionals have been migrating from PowerShell to C# for post-exploitation toolkits due to advances in security product configurations and features. This has caused security product vendors to now focus on detecting C# post-exploitation toolkits. An example of one of these detection improvements has been the anti-malware scan interface (AMSI) for .NET, which allows the scanning of .NET assemblies in memory. Currently, the majority of detections for these C# tools rely on static signatures, rather than the behaviors of the tools themselves.
This blog post will review various static indicators that can be used within C# toolkits for detection, and how to bypass those static signatures by making manual modifications and through automated modification methods using X-Force Red’s proof-of-concept C# obfuscation tool InvisibilityCloak. Additionally, defensive considerations will be outlined.
Static Components of C# Tools
There are several components within a C# tool that are static. These static components provide opportunities for building signature-based detections. A few of the more important static components will be discussed below.
Tool Name
The name of the tool itself is one of the components that could be used as a part of an alert. This is not reliable as a standalone detection; an attacker can simply rename a tool to get around any type of tool name-based detection.
Project GUID
When you create a C# tool within Visual Studio, your tool is assigned a unique identifier called a globally unique identifier (GUID) in your C# project file. An example of this is shown below in the C# project file for Seatbelt.
Listing C# project GUID
While running strings on the compiled version of Seatbelt, you can see the project GUID listed as shown in the screenshot below.
Running strings to show project GUID
Using the project GUID is a better way of providing detection of a known malicious tool, rather than using the tool name, as the project GUID will not change if you simply change the tool name. However, compile-time GUIDs can be changed to bypass such detections.
Variables
Another static indicator that can be used as part of signature-based detection is a variable name. An example of this is shown below in SafetyKatz. This variable name has ‘Mimikatz’ within it.
Showing variable name in SafetyKatz
Methods
Method names are another static indicator that can be used as part of signature-based detection. An example of this is shown below in SharpView. This method name correlates to one of the features within SharpView for finding interesting file shares.
Showing method name in SharpView
Classes
Defensive signatures can also be developed by using class names as a piece of the detection criteria. An example of this is shown below in Seatbelt.
Listing class names in Seatbelt
Strings
Strings can also provide a static indicator for signature-based detections. An example of this is shown below in SafetyKatz. This string is the Base64 encoded representation of the Mimikatz binary.
Listing a string in SafetyKatz
String Manipulation
As all of the indicators previously discussed are static, they can be manipulated in order to evade signature-based detections. In this case, we are going to focus on string manipulation via three different methods. The goal of this modification is to bypass signature-based detection, while being able to revert the string back to normal at runtime.
ROT13
The first string encoding method we will use is ROT13. In order to get an encoded version of the string to put into a C# tool, the below python code snippet can be used.
import codecs
theString = “testing this!”
rot13String = codecs.encode(theString, “rot_13”)
print(rot13String)
As you can see in the below output, this will be a ROT13 encoded version of your string.
Encoding a string via ROT13
We will place the encoded string into our C# tool, while making sure it can be decoded at runtime. The below C# code can be used to perform the ROT13 decoding at runtime.
string origString = new string("grfgvat guvf!".Select(x => (x >= 'a' && x <= 'z') ? (char)((x - 'a' + 13) % 26 + 'a') : ((x >= 'A' && x <= 'Z') ? (char)((x - 'A' + 13) % 26 + 'A') : x)).ToArray());
Console.WriteLine(origString);
Console.ReadKey();
The output below shows the text being successfully decoded to its original state.
Decoding a string via ROT13
Base64
The next string encoding method we will use is Base64. In order to get an encoded version of the string to put into a C# tool, the below python code snippet can be used.
import base64
theString = “testing this!”
base64EncodedString = base64.b64encode(theString.encode(“utf-8”))
theBase64String = str(base64EncodedString)
theBase64String = theBase64String.replace(“b'”,””)
theBase64String = theBase64String.replace(“‘”,””)
print(theBase64String)
As you can see in the output, this will be a base64-encoded version of your string.
Encoding a string via Base64
The encoded string will now be placed into our C# tool, while making sure it can be decoded at runtime. The below C# code can be used to perform the Base64 decoding at runtime.
string origString = Encoding.UTF8.GetString(Convert.FromBase64String(@"dGVzdGluZyB0aGlzIQ=="));
Console.WriteLine(origString);
Console.ReadKey();
The output below shows the text being successfully decoded to its original state.
Decoding a string via Base64
Reversal
A third method that can be used to obfuscate a string is by reversing the order of the characters. We will reverse the string to put in our C# tool via the python code snippet below.
# method to reverse a given string
def reverseString(s):
str = “”
for i in s:
str = i + str
return str
theString = “testing this!”
reversedString = reverseString(theString)
print(reversedString)
As you can see in the output below, this will be a reversed version of your string.
Reversing a string
Now that the string is reversed, we are going to put it into our C# tool. However, we will want to put it in the correct order at runtime, so that it is not in its obfuscated state. The below C# code can be used to undo the reversal at runtime.
string origString = new string(@"!siht gnitset".ToCharArray().Reverse().ToArray());
Console.WriteLine(origString);
Console.ReadKey();
The output below shows the text being successfully put back to its original state.
Putting string back in original order
InvisibilityCloak
Background
At X-Force Red, we wanted to take the approach of string modification, GUID modification and tool-name modification and automate this in a proof-of-concept tool called InvisibilityCloak. The goal of this tool is to provide awareness of the ease of bypassing signature-based detections in C# post-exploitation tools and to encourage the detection of tool behaviors, rather than static signatures. InvisibilityCloak allows the user to provide a Visual Studio project, and it will rename the tool, generate a new project GUID and perform obfuscation on the strings within the project. Full documentation on the usage of InvisibilityCloak is included on the X-Force Red GitHub repository.
Usage and Examples
Below is an example of running InvisibilityCloak against the Seatbelt C# project and applying the ROT13 string encoding to strings within the project.
Running InvisibilityCloak against Seatbelt
The modified Seatbelt project ran through InvisibilityCloak is shown below.
Showing modified project files
You can open the newly modified and obfuscated project in Visual Studio. Below is a snippet of some of the strings that had the ROT13 encoding applied in Seatbelt.
Showing snippet of some modified strings
The modified project can be compiled in Visual Studio via the instructions for the original project’s documentation.
Showing successful compilation of new obfuscated tool
Notice that after running Seatbelt through InvisibilityCloak, it is no longer detected by Microsoft Defender. A tool called DefenderCheck was used in this case to check the Microsoft Defender signatures for the original and modified version of Seatbelt.
Defender scan results
You can also see the difference of detection in memory by AMSI for .NET between the original Seatbelt, and the InvisibilityCloak version below.
Showing detection difference in AMSI for .NET
The event log (event ID 1116 and 1117) within ‘Microsoft-Windows-Windows Defender/Operational’ is shown below highlighting the AMSI detection for Seatbelt in memory. This detection correlates to the ‘Failed to load the assembly w/hr 0x8007000b’ message within Cobalt Strike.
Event log details for AMSI for .NET detection
Defensive Considerations
In order to catch attackers running C# tools out of the box, defenders should validate that their host-based security product signatures are kept fully up to date on a regular basis. Additionally, defenders should ensure that .NET Framework v4.8 is installed on Windows endpoints and that your host-based security product supports AMSI for .NET. This allows the scanning of .NET assemblies in memory.
While the detection of static signatures can work well for attackers that use unmodified post-exploitation tools, defenders should focus on the detection of techniques that the tools perform themselves. As shown with InvisibilityCloak, these tools can be easily modified to bypass signature-based detections. For example, the C# tool Rubeus can be used to perform the Kerberoasting attack, which is technique T1558.003 in the MITRE ATT&CK framework. The tool itself can always be modified to evade a signature, but the technique it performs remains the same.
The obfuscation performed as part of InvisibilityCloak is a proof-of-concept. X-Force Red did not include additional obfuscation methods and functionality that are present within its non-public version of InvisibilityCloak in consideration of defenders. However, defenders should be aware that these additional obfuscation methods listed below can also be performed.
- Reverse string obfuscation
- XOR string obfuscation
- AES string obfuscation
- RC4 string obfuscation
- Class name obfuscation
- Function name obfuscation
- Variable name obfuscation
Adversary Simulation, X-Force Red