Building an Obfuscator to Evade Windows Defender

Introduction

Any redteamer working in a windows enterprise environment will eventually have to cross paths with Windows Defender and its anti-malware competent AMSI. For an operator the inability to drop the proper tools during an engagement can be very frustrating and may also be the difference between dumping hashes or walking away with nothing. In this post we will analyze possible techniques one could use to evade defender detection. note this will not cover EDR evasion as that requires its own blog post.

Refresher

Before jumping into evasion techniques its important to understand how antivirus engines work under the hood. Modern analysis usually falls into two main categories:

Static Analysis – An audit of an executable’s content before run-time, this usually involves searching for malicious signatures in a file. It is effective against unmodified/out of the box malware.

Dynamic Analysis – An audit on the behaviours of an executable during run-time, anything from process memory to the syscalls an assembly invokes can be analyzed for known malicious behaviour.

Bypassing Defender Signatures

Currently one of the best techniques for bypassing static analysis and its signature scanning is to simply modify the malicious signatures. There are a multitude of methods you can go about for finding said signatures, Matt Graeber had released a powershell script to decompress defender AV signatures database so they can be queried against,

you could use malware assessment software like Winitor and spot-modify on its indicators,

there is also a handy tool called DefenderCheck which automates the process of splitting a binary and utilizing defender itself to scan until it pinpoints the triggering signature.

While these are all great techniques for this post we are gonna build a custom obfuscator, using an obfuscator gives us a few advantages. First you don’t need the source code to obfuscate, all you need is the binary. Second it is a time save, once you build the obfuscator you don’t have to spend your time modifying each individual signature for each tool in your arsenal. Funny enough, the main purpose of an obfuscator is actually to prevent malicious activity, software developers will use these tools to protect their code from reverse engineering.

We will be focusing on building a obfuscator for .NET or more specifically Common Intermediate Language (CIL). When .NET languages like C# and Visual Basic are compiled they don’t actually get turned into machine code right away, rather they get compiled into an intermediate that is run on their Common Language Runtime (CLR). Since IL is far more readable then assembly it will make writing this tool exponentially easier.

You will notice if you’ve been playing around with defenders antivirus for any amount of time that the signatures it chooses are usually constants like strings, or variables names. It will not hold any common library methods names such as `System.Text.Encoding.UTF8.GetString` as that would trigger too many false positives on legitimate programs. Therefore we will be obfuscating Strings, Methods, Classes, Namespaces and the assembly compile info.

Lets Begin

We will be using dnlib as an assembly editor, you could also use mono cecil as they share very similar syntax.

using dnlib.DotNet;
using dnlib.DotNet.Emit;

First you have to load the assembly to obfuscate into a module,

ModuleDef md = ModuleDefMD.Load("/path/to/assembly");

using the module object we have access to the binary type, compile info, and classes. We will modify the assembly info like this.

string[] attri = { 
"AssemblyTitleAttribute", ... ,"AssemblyCompanyAttribute"};

foreach (CustomAttribute attribute in md.Assembly.CustomAttributes) {

	if (attri.Any(attribute.AttributeType.Name.Contains)) {
		string encAttri = RandomString(10);
		attribute.ConstructorArguments[0] = new CAArgument(md.CorLibTypes.String, new UTF8String(encAttri));
	}
}

Next we can iterate through the types of the assembly and change the class and namespace names.

foreach (var type in md.GetTypes()){
    type.Name = RandomString(10);
    type.Namespace = RandomString(10);

After that lets change the method names in each class, be wary as there are certain methods we can’t change such as constructors.

foreach (MethodDef method in type.Methods){
	if (!method.HasBody) continue;
	if (method.IsConstructor) continue;

	string encName = RandomString(10);
	method.Name = encName;

And finally lets modify the strings, this was provided courtesy of https://github.com/CodeOfDark/Tutorials-StringEncryption. It would be best to add your own custom string obfuscation Instructions.

for(int i = 0; i < method.Body.Instructions.Count(); i++){
	if(method.Body.Instructions[i].OpCode == OpCodes.Ldstr){
		String regString = method.Body.Instructions[i].Operand.ToString();
		String encString = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(regString));
		method.Body.Instructions[i] = new Instruction(OpCodes.Call, md.Import(typeof(System.Text.Encoding).GetMethod("get_UTF8", new Type[] { })));
		method.Body.Instructions.Insert(i + 1, new Instruction(OpCodes.Ldstr, encString));
		method.Body.Instructions.Insert(i + 2, new Instruction(OpCodes.Call, md.Import(typeof(System.Convert).GetMethod("FromBase64String", new Type[] { typeof(string) })))); 
		method.Body.Instructions.Insert(i + 3, new Instruction(OpCodes.Callvirt, md.Import(typeof(System.Text.Encoding).GetMethod("GetString", new Type[] { typeof(byte[]) }))));
		i += 4;
	}
}

Before we write the finalized assembly we need to cleanup the IL and correct errors that can occur with Instruction insertion like insufficient maxstack.

method.Body.SimplifyBranches();
method.Body.OptimizeBranches();

You may have wondered why we didn’t bother changing variables, well one of the benefits of dnlib is that those variables are simplified for you on write so there is no need to obfuscate them since they are already mangled.

Finally lets write the assembly to a file.

md.Write(outFile);

For a completed version of the code you can check it out here https://github.com/BinaryScary/NET-Obfuscate/blob/master/NET-Obfuscate/Program.cs

Testing it out

Aright now onto the fun part lets test out our obfuscator, remember to turn off ‘Automatic sample submission’ you don’t want all your work to go to waste.

First lets try it on the covenant C2 agent.

As you can see no antivirus trigger notification and defender-check hasn’t reported anything either.

Next lets try it on a larger tool like TikiTorch, this time well run a defender scan.

Defender reports no threats were found.

Conclusion

This post was meant to be a educational piece on how you can easily create your own obfuscator, as pointed out earlier taking any old offensive tool online and trying to use it on an engagement is a surefire way to get you caught. The best strategy to evade defender is to create your own obfuscate tools whether that be with a custom obfuscator or changing them manually by hand. There is a big obfuscation community with way larger obfuscation projects then this one, so another possible route is to modify one of those tools just enough as to not get caught by their old signatures.

The next step in your evasion journey could be to look into process injection using the popular Donut, and Tikitorch tools, or check out AMSI bypasses.

Share this post

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp