When I was newbie in .NET, it was a shocked for me to learn that .NET
assemblies can be decompile. But today I know that this is a normal
situation for a language which is compiling to an intermediate language.
As we know that, after compiling a .NET project the output will be in
MSIL (Microsoft Intermediate Language). And during the application
lifecycle this codes will be compiled to Native Codes by the JIT
(Just-In-Time Compiler) when they first used and cached in that computer
for the future usages. Anyway this is another subject for us how JIT
works, but here the problem is our assemblies are in MSIL format which
has about 229 opcodes and all these opcodes has a standard working
procedure. So if we know these opcodes and how they are working, we can
produce the .NET codes by reverse engineering. This is simply what
decompilers do. But why there are no stable decompilers for the native
exes? The answer is simple, because there are no metadata for the native
exes. In the .NET I can say that, metadata of that assembly is really
very important to resolve the Type and Member structures of that
assembly. Anyway there are a lot of decompilers for the .NET which can
decompile codes as same as with the original codes.
Xenocode Fox
Decompiler and
Reflector are the most know
ones.
Below is an example which shows us what a decompiler can do.
| Original Code |
Decompiled Code |
private void btnRun_Click(object sender, EventArgs
e)
{
Stopwatch watch = new Stopwatch();
int iteration = 0;
if (!int.TryParse(txtNumIteration.Text, out iteration))
iteration = 10000000;
int value = 0;
if (!int.TryParse(txtNumParam.Text, out value))
value = 50;
IExample ex = null;
int caseCount;
switch (cmbCases.SelectedIndex)
{
case 0:
caseCount = 5;
ex = new Example5();
break;
case 1:
caseCount = 10;
ex = new Example10();
break;
case 2:
caseCount = 20;
ex = new Example20();
break;
case 3:
caseCount = 50;
ex = new Example50();
break;
default:
caseCount = 99;
ex = new Example99();
break;
}
int r = 0;
watch.Start();
for (int i = 0; i < iteration; i++)
{
r = ex.GetSwitchResult(value);
}
watch.Stop();
long swResult = watch.ElapsedMilliseconds;
watch.Start();
for (int i = 0; i < iteration; i++)
{
r = ex.GetIfResult(value);
}
watch.Stop();
AppendItem(iteration, caseCount, value, swResult,
watch.ElapsedMilliseconds);
} |
private void btnRun_Click (object sender, EventArgs e)
{
int caseCount;
Stopwatch watch = new Stopwatch();
int iteration = 0;
if (!int.TryParse(txtNumIteration.Text, out iteration))
{
iteration = 10000000;
}
int value = 0;
if (!int.TryParse(txtNumParam.Text, out value))
{
value = 50;
}
IExample ex = null;
switch (cmbCases.SelectedIndex)
{
case 0:
{
caseCount = 5;
ex = new Example5();
break;
}
case 1:
{
caseCount = 10;
ex = new Example10();
break;
}
case 2:
{
caseCount = 20;
ex = new Example20();
break;
}
case 3:
{
caseCount = 50;
ex = new Example50();
break;
}
default:
{
caseCount = 99;
ex = new Example99();
break;
}
}
int r = 0;
watch.Start();
for(int i = 0; i < iteration < i++)
{
r = ex.GetSwitchResult(value);
}
watch.Stop();
long swResult = watch.ElapsedMilliseconds;
watch.Start();
for (int i = 0;i < iteration; i++)
{
r = ex.GetIfResult(value);
}
watch.Stop();
AppendItem(iteration, caseCount, value, swResult,
watch.ElapsedMilliseconds);
} |
Decompiled code was generated by using Xenocode Fox
As you see here, the output is nearly as same as the original code when
we decompile the assembly with Xenocode Fox.
So the question is how we can protect our assemblies from decompiling.
Actually the answers is simple, except we don’t make our .net assemblies
native, we cannot. By one way or other decompilers can decompile our
codes back to the source codes. But against the decompilers there are
Obfuscators which helps us to protect our codes from decompiling. If we
have to explain what an obfuscator do, simply, Obfuscators changes the
names of our Types and Members (properties, methods, fields, events
etc.) to unreadable or unmeaning full names in the metadata of our
assembly. By this way when someone decompiles our codes they can only
see the unmeaning full class and member names which is really hard to
understand the code. Generally we make the type and member names as
understandable. For example if we write a class which indicates a User’s
information, we will probably make the class name as
User. So
after decompiling the person who decompiles the code, sees this User
class and he/she understands that this class is for the user
information. So if we change the name of this class to an understandable
name (for ex:
xbc3453vbgf345) then it will be
really hard to understand what this class is for.
Except from changing the type and member names
some obfuscators also can make .net assemblies as native, or some of
them can injects useless codes to our methods which cause our methods
hard to understand.
There are a lot of Obfuscators like
Xenocode PostBuild,
CodeVeil,
Salamander etc. I’m using
PostBuild
to obfuscate my codes and I can say that it is really enough to protect
my assemblies. Not only changing the type and member names but also we
can make our assemblies Native by using PostBuild, or we can allow it to
injects some codes which protects our methods to be decompiled (by
injecting some untargeted branches) etc. etc. Making our assemblies as
native will stops cares about decompiling. Code injection will obstruct
Reflector and Fox to decompile that methods.
PostBuild
also can make our assemblies smaller by compressing and removing unused
metadata by dead code elimination. This will increase our assemblies’
performance. Anyway you can check
PostBuild by
downloading its trial version.
More About Xenocode Postbuild |
Download PostBuild
Evaluation
As conclusion, I can say that using a good obfuscator is enough to
protect our codes. There is no need to care about decompilers. Even I
develop a decompiler, I still continue to develop my other projects by
using .NET and I have no care about this.