A Tale of .Net Deobfuscation - VirtualGuard Basics


This part focuses more on the basic obfuscation techniques used in .Net samples such as Control flow Obfuscation, Proxy Calls, Anti Debug and Anti Tamper. I write a de4dot plugin for VirtualGuard that patches the .Net binary and removes all the protections except VM Devirtualization.
For more on VM Devirtualization, You can move on to the next part in the series.

Introduction

Well I was thinking of doing a writeup of the flareon #8 challenge ie. Backdoor, but then I got inspired to dig more into .Net obfuscation. Browsing through crackmes.one a crackme featuring β€œVirtualGuardβ€œ protector, caught my eye as it was pretty new and unsolved.

In the end, I solved it through debugging but had a motivation to develop some tooling for it. I contacted mito on discord and told him about it. He shared his insights on it and I was set off for doing some more research on .Net protectors. During this time I read about various VM based protectors such as MemeVM, KoiVM, eazfuscator, etc. I was astonished by the tool β€œOldRod” ie. KoiVM Devirtualization Utility developed by Washi and began reading its source code. Days passed by and I get a message from Mito that he has developed a new VM which is more difficult than the SpiderVM. I was excited for it and planned to write a tool for VirtualGuard this time. I wrote a disassembler initially and Mito motivated me to do a full Devirt of it.

β€œVirtualGuard is still in the early phases of development and is not currently conducting any sales.”

The crackmes can be downloaded from the following links :
Spider VM πŸ•Έ | Crocodile VM 🐊


Initial Analysis πŸ”

Both of them are typical keygenme challenges. Crocodile crackme asks for a username and password whereas Spider crackme asks for a license key.

We can analyze the executables in dnspy. The control flow of all methods is obfuscated.

There is a state variable which is modified subsequently in different ways using arrays and conditions. At first it looked easy and I began writing a tool to debofuscate it.

We can look at the IL Instructions of the array assignments in dnspy.
Here is a good documentation of CIL Opcodes on MSDN. I wrote some code to get all the operation opcodes ie. Add/Sub and array constant assignments in order.

1
2
3
4
5
6
7
8
9
10
11
12
13
Instruction curIns = chunkIns[i];
Instruction nextIns = chunkIns[i + 1];

if (nextIns.OpCode == OpCodes.Add
|| nextIns.OpCode == OpCodes.Sub)
{
opcodeList.Add(nextIns.OpCode);
}
if (curIns.OpCode == OpCodes.Ldc_I4
&& nextIns.OpCode == OpCodes.Ldc_I4)
{
arrayList.Add((uint)nextIns.GetLdcI4Value());
}

Now the underlying algorithm for the array pass is quite simple. We can calculate the values in the array and therefore we have the resulting state variable value using the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static List<uint> CalculateArray(List<OpCode> opcodeList, List<uint> arrayList)
{
int j = 0;
for (int i = 0; i < arrayList.Count - 1; i++)
{
for (int k = i; k >= 0; k--)
{
OpCode op = opcodeList[j++];
if (op == OpCodes.Add) arrayList[i + 1] = arrayList[i + 1] + arrayList[k];
if (op == OpCodes.Sub) arrayList[i + 1] = arrayList[i + 1] - arrayList[k];
}
}
return arrayList;
}

There were some conditional passes as well ie. the next value of state variable depended on a previous evaluated condition. At this point, I asked mito about it and he told its just a mod of ConfuserEx lol. So I tried it and to my surprise it was quite similar except that array pass not being included in it. Now obviously as ConfuserEx is quite old, it would have some deobfuscation tools as well. I searched and found ConfuserEx Switch Killer which does a good job at deobfuscating control flow for ConfuserEx but the code is very hard to debug and understand. I also found de4dot-cex which emulates certain instructions and calculates the value of the state variable and thus the target switch block. BTW, ConfuserExSwitchKiller had multiple uses of de4dot.blocks to deobfuscate the blocks at different stages as well. I also found some Anti-Debug and Anti-Tamper methods in the crackme, so I thought why not write a de4dot plugin for VirtualGuard.

Writing a de4dot Plugin πŸ”Œ

The directory structure for the deobfuscators looks like the following

1
2
3
4
5
6
7
πŸ“‚de4dot
πŸ“‚de4dot.code
|__πŸ“‚deobfuscators
|__πŸ“ConfuserEx
|__πŸ“‚VirtualGuard
| |-- πŸ—‹ Deobfuscator.cs
|__πŸ“Unknown

You could read the β€œUnknownβ€œ Deobfuscator code to understand the basic code structure for a deobfuscator plugin.

Basically first we need to detect the protection used in the executable so we check it in the following manner.

1
2
3
4
5
6
7
8
9
10
11
private void DetectVirtualGuard()
{
List<string> VMNames = new List<string>() { "crocodile", "spider"};
if (module.Name != "π™‘π™žπ™§π™©π™ͺ𝙖𝙑𝙂π™ͺ𝙖𝙧𝙙")
return;
foreach (var res in module.Resources)
{
if(VMNames.Contains(res.Name))
_detectedVirtualGuard = true;
}
}

We match the module name (which is same for both of the crackmes) and check whether the specific named resources are present in the executable.

πŸ’‘ Do not forget to override the MetaDataFlags and set them to PreserveAll as it is very important here as deobfuscation seems to mess up the metadata tables like MemberRef table.

1
public override MetaDataFlags MetaDataFlags => MetaDataFlags.PreserveAll;

Cleaning Control Flow 🧹

Our control flow deobfuscator is similar to the ConfuserEx plugin with modifications to the Instruction Emulator for de4dot.blocks like adding instructions such as newarr, stelem.i4, ldelem.i4 and noping out the unused array instructions in the end.

For eg. the emulation for ldelem.i4 instruction looks like the following.

1
2
3
4
5
6
7
void Emulate_Ldelem_I4(Instruction instr)
{
Int32Value idx = (Int32Value)valueStack.Pop();
ObjectValue arrobj = (ObjectValue)valueStack.Pop();
List<Value> arr = (List<Value>)arrobj.obj;
valueStack.Push(arr[idx.Value]);
}

From MSDN docs, stack transition of ldelem.i4 instruction is :

  1. index and array are popped from the stack; the value stored at position index in array is looked up.
  2. The value is pushed onto the stack.

We emulate every switch case block and calculate the next state variable using emulation.
The ProcessBlock function looks like the following :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var targets = switchBlock.Targets;

_instructionEmulator.Emulate(block.Instructions, 0, block.Instructions.Count);

if (_instructionEmulator.Peek().IsUnknown())
throw new Exception("CRITICAL ERROR: STACK VALUE UNKNOWN");
//Logger.v("Emulated Result : " + _instructionEmulator.Peek());
int? key = CalculateKey(switchBlock.SwitchData);

if (!key.HasValue)
throw new Exception("CRITICAL ERROR: KEY HAS NO VALUE");

int? switchCaseIndex = CalculateSwitchCaseIndex(switchBlock, switchBlock.SwitchData, key.Value);
if (!switchCaseIndex.HasValue)
throw new Exception("CRITICAL ERROR: SWITCH CASE HAS NO VALUE");
if (targets.Count < switchCaseIndex)
throw new Exception("CRITICAL ERROR: KEY OUT OF RANGE");

var targetBlock = targets[switchCaseIndex.Value];

In the end, we can nop out the array assignments as well. Here is the result of the control flow deobfuscation pass on a method. Cheers! 🍻

Fixing Proxy Calls ⛓️

Some methods call specific functions for eg. ed.f3(). These method calls hide the original method being called and therefore we’ll refer to them Proxy calls here.

They are resolved from an array of objects (ie. object[] 9.5d5) initialized in the constructor in this case.

So we just need to replace the proxied usages of these methods. Easy! Lets get to work.
There are several markers for various stages in deobfuscation in de4dot.

1
2
3
4
DeobfuscateBegin()
DeobfuscateMethodBegin()
DeobfuscateMethodEnd()
DeobfuscateEnd()

I make use of the DeobfuscateMethodEnd() function as de4dot cleans the blocks by removing useless arithmetic instructions and calculates the index value in the proxy calls beforehand.

We first grab all the methods in the list and also note the list field name.

1
2
3
4
5
6
7
8
9
10
11
for (int i = 0; i < instrCount; i++)
{
if (instrs[i].OpCode == OpCodes.Ldftn)
{
objList.Add(instrs[i].Operand as IMethod);
}
if (instrs[i].OpCode == OpCodes.Stsfld)
{
realObj = instrs[i].Operand as FieldDef;
}
}

Then we can extract the original proxied methods by their index in the object list.

1
2
3
4
5
6
7
8
9
for (int i = 0; i < instrCount; i++)
{
if (instrs[i].OpCode == OpCodes.Ldsfld && instrs[i].Operand == realObj)
{
int objIdx = instrs[i + 1].GetLdcI4Value();
typesToRemove.Add(blocks.Method.DeclaringType);
ReplaceMethodCalls(blocks.Method, objList[objIdx]);
}
}

We replace the proxied method calls with the original ones from the list and also keep noting the types we need to remove from the executable as they are of no use now.
We can use the de4dot utility AddTypesToBeRemoved to remove them in the end.
We also need to remove the object list as it uses types which are now removed.

1
2
List<TypeDef> proxyTypes = proxyCallFixer.Deobfuscate(blocks);
AddTypesToBeRemoved(proxyTypes, "proxies");

To sum it up, the deobfuscation process looks like the following :

Anti Debug Remover 🐞

The Anti Debug method is as follows

It makes use of IsDebuggerPresent, CheckRemoteDebuggerPresent APIs & IsAttached property to detect the presence of debugger. As it makes use of LoadLibrary and GetProcAddress functions to invoke those anti debugger methods, we can find their definitions in the ImplMap Table.


The ImplMap Table contains information about unmanaged functions. Unmanaged functions can be called through managed code with the help of PInvoke.

As the proxy calls are now replaced, it is easier to search for methods called by the constructor. Now we just need to create signature of the method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public bool Find(MethodDef realCctor)
{
if (realCctor == null)
return false;
foreach (var method in DotNetUtils.GetCalledMethods(module, realCctor))
{
var type = method.DeclaringType;
if (!method.IsStatic || !DotNetUtils.IsMethod(method, "System.Void", "()"))
continue;
if (DotNetUtils.GetPInvokeMethod(type, "kernel32", "LoadLibrary") == null)
continue;
if (DotNetUtils.GetPInvokeMethod(type, "kernel32", "GetProcAddress") == null)
continue;
if (!DotNetUtils.HasString(method, "IsDebuggerPresent") &&
!DotNetUtils.HasString(method, "CheckRemoteDebuggerPresent") &&
!DotNetUtils.HasString(method, "get_IsAttached"))
continue;

antiDebuggerType = type;
antiDebuggerMethod = method;
return true;
}

return false;
}

I wrote a simple function to find the anti debug method in the executable. It checks whether the method is a simple static method with no parameters and no return value. We verify if the ImplMap contains entries for both LoadLibrary and GetProcAddress. We also search for strings specific to those APIs.

Anti Tamper Remover πŸ”¨

There is also an Anti-Tamper method which checks whether the executable is patched.

A MD5 Hash is created using the binary data from the executable except 16 bytes from the end to which it is compared.

We can verify this by the following commands :

1
2
Ξ» head -c -16 test-guarded_spider.exe | md5sum
ccd614578ea1684ef134f47887f1022d *-
1
2
Ξ» tail -c 16 test-guarded_spider.exe | xxd -p -c 100
ccd614578ea1684ef134f47887f1022d

Not spending much time on this, I made a trivial check. I noted all the methods used in the anti tamper method.

1
2
3
4
5
6
7
private List<string> HashCheckMethods = new List<string>() {
"System.Reflection.Assembly System.Reflection.Assembly::GetExecutingAssembly()",
"System.String System.Reflection.Assembly::get_Location()",
"System.Security.Cryptography.MD5 System.Security.Cryptography.MD5::Create()",
"System.Byte[] System.IO.File::ReadAllBytes(System.String)",
"System.Byte[] System.Security.Cryptography.HashAlgorithm::ComputeHash(System.Byte[])"
};

I just check whether all these methods are being called in any method in the same order and if yes, I mark it as the Anti-Tamper method. I know there could be some false positives but for now I didn’t feel like spending much on it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
foreach (var method in DotNetUtils.GetCalledMethods(module, realCctor))
{
int i = 0;
var allCalls = DotNetUtils.GetMethodCalls(method);
IEnumerable<string> allCallsNames = allCalls.Select(pt => pt.FullName);
foreach (var calledMethodName in allCallsNames)
{
if (calledMethodName == HashCheckMethods[i])
i++;
if (i == HashCheckMethods.Count)
{
antiTamperMethod = method;
return true;
}
}
}

Success! πŸ’ͺ

We have now successfully deobfuscated both the crackmes. It will now be easier to analyse other versions of VirtualGuard protected executables. Just FYI, after deobfuscation, the size of the Spider crackme decreased from 151 KB to 27 KB 😱.

You could download the clean version of the crackmes from the links below :
Cleaned Spider πŸ•Έ | Cleaned Crocodile 🐊
I’ve also open sourced the de4dot plugin for VirtualGuard.
https://github.com/mrT4ntr4/de4dot-vg

Thanks for making it till here. I hope you learned something from this post. If you find it interesting and want to check out how I devirtualized these crackmes, you could head to the next part in the series here.


References πŸ“š

https://www.codeproject.com/Articles/12585/The-NET-File-Format
https://github.com/NotPrab/.NET-Obfuscator
https://github.com/NotPrab/.NET-Deobfuscator