Towards generic .NET assembly obfuscation (Pt. 1)
- 4 minsAbout 2 years ago when I entered the red teaming field, PowerShell was huge. It was an easy, elegant and clean way to evade anti-malware solutions. But largely due to the efforts from Microsoft to implement defence capabilities such as AMSI and Script Logging into PowerShell (v5), those happy PowerShell days for red teamers are over. Sure, it’s still possible:
[PSObject]Assmebly.GetType('System.Management.Automation'+'Utils'),GetType('amsiIni'+'tFailed', 'nonPublic, static').setValue($null, $true)
or
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
but it’s getting more difficult.
So as often, the red team finds other low hanging fruit with which it’s easier to achieve its goal: .NET.
Efforts in the industry are shifting from PowerShell towards .NET based toolkits, GhostPack, SharpView, SharpWeb and reconerator are examples of those efforts.
Just like with PowerShell modules, it’s often possible to execute those .NET assemblies in memory without touching disk:
or using Cobalt Strike’s 3.11 beacon functionality execute-assembly
[1].
Obfuscating .NET binaries
But sometimes it’s inevitable to drop a .NET assembly to disk, or you want to adhere to general good OpSec practices and want to obfuscate your binaries, just in case. I’d be nice to have an obfuscator for .NET assemblies that can obfuscate any .NET assembly, while leaving its functionality intact.
The idea described here is centred around encapsulation of the .NET assembly and loading the encapsulated assembly via the (not logged or monitored) Assembly.Load(byte[])
.NET method at runtime. The output of our obfuscator should be an assembly that loads the original (malicious) assembly into its own process space. Our obfuscator should perform the following steps:
1. Take a .NET assembly as input, obfuscate / encrypt the .NET assembly and encode it to a base64 string:
2. Create C# code that deobfuscates / decrypts the base64 string and loads the output via Assembly.Load(byte[])
:
The srcTemplate
variable contains a template for the (outer) assembly output of the obfuscator. Into this template, we copy the obfuscated / encrypted malicious assembly. At runtime, this obfuscated assembly will be deobfuscated and loaded via Assembly.Load(byte[])
. The tricky bit here is that after loading the assembly, we don’t know which method in the assembly Main
is. We can solve this by matching on its features: public
, static
and arguments String[]
. If it fails, we’ll move on to find the next method with these features. When we’ve found the method that matches these features, we’ll invoke it and pass it the arguments obtained from the “outer” assembly.
3. Compile a new .NET assembly at runtime:
When the template is filled in, we compile the output assembly:
When implementing this yourself, I encourage you to implement your own obfuscation / encryption routines, as well as some sandbox evasion techniques. While this technique bypasses all traditional AV products, leaving the base64 string as is in the “outer” .NET assembly will trigger some “ML engines”, since the assembly looks at lot like a loader: limited code and a large blob of String
. In a following part, I will describe some evasion methods for these “ML engines”.