4.2. Declarative Obfuscation Using Custom Attributes
The .NET platform since version 2.0 provides two custom attributes designed to make it easy to change obfuscation behavior. Using these two attributes it is possible to override default decisions made by Eazfuscator.NET during obfuscation. Also these attributes can be used to configure advanced Eazfuscator.NET features. It is assumed that you have a knowledge about custom attributes and how to apply them in your development language.
This attribute can be applied to an assembly to tell obfuscator how to treat it. Setting the AssemblyIsPrivate
property to false tells obfuscator to treat an assembly as a library. Setting the AssemblyIsPrivate
property to true tells obfuscator to treat an assembly as an executable. The difference is how Eazfuscator.NET renames and seals public types and their public members. In case of an executable all public types and their public members are considered terminal so they gets renamed. In case of a library, those types and members may be used by other assemblies, thus they do not get renamed.
The default obfuscator behavior is to treat all .dll
assemblies as libraries, and all .exe
and .com
assemblies as executables. If the assembly has another file extension (neither .dll
nor .exe
/.com
) then Eazfuscator.NET treats such assemblies depending on the entry point presence. If the assembly has an entry point then it is treated as an executable; otherwise, as a library.
This attribute is the main troubleshooter of reflection-related problems that may occur during obfuscation. As it was mentioned above, Eazfuscator.NET has no formal reflection scenarios analysis engine and it uses heuristic algorithms instead. Decisions produced by those algorithms may be wrong in some rare cases so you may need to override them by using System.Reflection.ObfuscationAttribute
System.Reflection.ObfuscationAttribute
can be applied to a type. Possible feature property values are "all" (by default), "renaming" and "properties renaming". So if you want to disable renaming of your class you may write something like an example below (C#):
Example 4.1. Disabling class renaming
[System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = false)]
class MyOneThousandAndFirstClass
{
…
}
If you want to disable the renaming of your class and all its members, you may write:
Example 4.2. Disabling class and its members renaming
[System.Reflection.ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
class MyOneThousandAndSecondClass
{
…
}
If you want to disable the renaming of your class properties, you may write:
Example 4.3. Disabling class properties renaming
[System.Reflection.ObfuscationAttribute(Feature = "properties renaming")]
class MyOneThousandAndThirdClass
{
…
}
Sometimes it may be useful to disable the renaming of just some properties in a class. To do that, you may write:
Example 4.4. Disabling the renaming of a single class property
class MyOneThousandAndFourthClass
{
[System.Reflection.ObfuscationAttribute(Feature = "renaming")]
public string DisplayName
{
get;
set;
}
}
In some rare cases you may want to disable the renaming of properties of anonymous types. To do that, you should apply the following attribute at the assembly level:
Example 4.5. Disabling the renaming of anonymous type properties
using System.Reflection;
[assembly: Obfuscation(Feature = "anonymous type properties renaming", Exclude = true)]
Sometimes, it may be helpful to store configuration in a standalone file that is not part of the project. Eazfuscator.NET provides a way to achieve that. Please refer to the corresponding knowledge base article for more information.
Another purpose of System.Reflection.ObfuscationAttribute
is to configure Eazfuscator.NET features. You may find more information about this at the following places:
4.2.3. .NET Compact Framework, Silverlight, Windows Store and .NET Core Projects
There are no System.Reflection.ObfuscateAssemblyAttribute
and System.Reflection.ObfuscationAttribute
attributes available in .NET Compact Framework, Silverlight and WinRT runtimes. So if you want to use declarative obfuscation you must define corresponding attributes in your assembly. The easiest way to do this is to add ready-to-use file ObfuscationAttributes.cs (for C#) or ObfuscationAttributes.vb (for VB.NET) to your project. These files can be found at Start Menu → Eazfuscator.NET → Eazfuscator.NET Code Snippets menu item. Alternatively they can be found at C:\Program Files\Eazfuscator.NET\Code Snippets
path.
The path may differ depending on the installation options and operating system. For example, this path may look like C:\Program Files (x86)\Eazfuscator.NET\Code Snippets
on 64-bit operating systems.
Sometimes it may not be possible to access the class definition directly to apply a custom attribute for obfuscation tuning. If latter is the case then indirect declarative obfuscation comes to the rescue. It allows you to tune the obfuscation indirectly by using custom attributes defined at the assembly level.
Let's take a closer look on an example. Suppose there is a class MyNamespace.ResourceClass1
and its declaration can not be changed because it was automatically generated by a tool. So how to disable the renaming of that class during obfuscation? The solution is to use indirect declarative obfuscation. In order to do that, you should apply the following attribute at the assembly level (C#):
Example 4.6. Indirectly disable the renaming of a class
using System.Reflection;
[assembly: Obfuscation(Feature = "apply to type MyNamespace.ResourceClass1: renaming", Exclude = true, ApplyToMembers = false)]
Please note, the feature string starts with "apply to type" expression in the sample above. That expression signals Eazfuscator.NET that feature should be redirected to MyNamespace.ResourceClass1
class. There is a semicolon after the class name; just next to it, there is a real feature name "renaming". So, the above sample code is virtually equivalent to the code shown below (C#):
namespace MyNamespace
{
[System.Reflection.ObfuscationAttribute(Feature = "renaming", Exclude = true, ApplyToMembers = false)]
class ResourceClass1
{
…
}
}
The class name in indirect expression can contain a glob mask.
As for example, it's possible to disable all obfuscation features for all classes inside a given namespace. The following attribute should be applied at the assembly level in order to do that (C#):
Example 4.7. Indirectly disable all obfuscation features for a given namespace
using System.Reflection;
[assembly: Obfuscation(Feature = "apply to type SomeExcludedNamespace.*: all", Exclude = true, ApplyToMembers = true)]
A more powerful configuration syntax is available in conditional obfuscation.
Introduction
Sometimes you may want to define obfuscation attributes in several places.
For example, a given assembly may be configured by two files. One file, ObfuscationSettings.cs
, defines the obfuscation directives directly related to the given assembly. Another file, CommonObfuscationSettings.cs
, defines the obfuscation directives common to all obfuscated assemblies in a whole project. That file is shared among all obfuscated assemblies in a project.
Then you decide that string encryption is not needed for most assemblies, so you just disable it at the common level in CommonObfuscationSettings.cs
file:
[assembly: Obfuscation(Feature = "string encryption", Exclude = true)]
Some time after, it turns out that a particular assembly Contoso.Engine.dll
should have string encryption enabled. ObfuscationSettings.cs
file defined in Contoso.Engine
assembly looks like this:
[assembly: Obfuscation(Feature = "string encryption", Exclude = false)]
Please note that both ObfuscationSettings.cs
and CommonObfuscationSettings.cs
files are included in compilation of Contoso.Engine
assembly as source code files. So both directives find their ways into resulting compiled Contoso.Engine.dll
file.
What happens when Eazfuscator.NET sees conflicting obfuscation directives in input assembly? The exact semantics depends on a directive. Eazfuscator.NET may just take the least permissive directive for features like string encryption. Another strategy is to take the very first directive and abandon the rest (note that C# and many other .NET compilers do not guarantee the order of custom attributes in resulting compiled assembly). Nevertheless this is not what you want when it comes to several configuration sources where you want to have priorities. E.g. directives defined in ObfuscationSettings.cs
file should have a higher priority than those defined in CommonObfuscationSettings.cs
file.
So how to achieve the prioritization of obfuscation attributes? Priority syntax described below makes that possible.
Priority is defined by a numeric prefix:
[Obfuscation(Feature = "1. <some feature here>")]
The idea behind priority prefixes is to form a natural list:
[Obfuscation(Feature = "1. <some high priority feature here>")]
[Obfuscation(Feature = "2. <medium priority feature>")]
[Obfuscation(Feature = "3. <a feature with the lowest priority>")]
where the first item has the highest priority, the second is less important, and so on. Just like your typical TODO list.
Priority prefix can only contain a natural number (a positive integer). Zero and negative priorities are not allowed.
Complete Example
So here is how the aforementioned configuration files should be defined to make the prioritization work according to the task described in introduction. The content of CommonObfuscationSettings.cs
file:
[assembly: Obfuscation(Feature = "2. string encryption", Exclude = true)]
The content of ObfuscationSettings.cs
file for Contoso.Engine
assembly:
[assembly: Obfuscation(Feature = "1. string encryption", Exclude = false)]