ArmDot Attributes
With the help of ArmDot, developers can use a declarative way to control the obfuscator. ArmDot provides attributes that specify obfuscation options. A developer can add them to a class, a method, a field, or an entire assembly.
You can find the ArmDot attributes in the ArmDot.Client package available on NuGet. To use the attributes, add the package to a project to obfuscate it. ArmDot removes all such attributes after obfuscation, so an output assembly doesn't need ArmDot.Client.dll to run.
Disabling obfuscation options and obfuscation options inheritance
A developer adds an obfuscation attribute to an assembly, a type, a method, or a field. If an assembly or a type has an obfuscation attribute, the corresponded obfuscation option is also implicitly applied or not applied to child items (types of an assembly; types methods, fields, nested types and their methods, fields, nested types). Put another way: child items inherit or don't inherit obfuscation options. For example, if one wants to obfuscate names of all methods and fields of some type, it is enough to add the attribute ObfuscateNamesAttribute to the type only.
Two attribute properties control the obfuscation options inheritance: Enable and Inherit. If Enable is false, an obfuscation option is turned off (Enable is true by default). If Inherit is false, an obfuscation option is no longer applied recursively starting with this item (Inherit is true by default).
By default, both Enable and Inherit are true. It means that by default, an obfuscation option is enabled for an item and its children (recursively).
Each obfuscation feature has a corresponding attribute. Let's review the features.
Names obfuscation: ObfuscateNamesAttribute.
Developers use names obfuscation to confuse ones who explore an assembly: as item names (e.g., method names) have been changed, it's not easy to determine for what purpose they were created. For example, compare Initialize and getCompilationRelaxationsStaticIndexRangePartitionForArray. The former name is obvious; the method seems to initialize something. The latter one gets some range of an index. Well, it makes no sense, and that's great because an obfuscator must be confusing.
To change names, use the attribute ObfuscateNamesAttribute.
To rename all possible things, apply the attribute to an entire assembly.
To rename a method, a field, add the attribute to it.
To rename a type with its methods, fields, nested types and their methods, fields, and nested types, apply the attribute to the type. If you want to exclude some item from name obfuscation, use the attribute ObfuscateNamesAttribute with Enable = false; in such a case, child items (methods, fields, properties, and nested types) are also excluded from obfuscation. To change this behavior, specify Inherit = false.
The example:
[ArmDot.Client.ObfuscateNames] // Obfuscate the type ifself and also all child items
class SomeClass
{
[ArmDot.Client.ObfuscateNames(Enable = false, Inherit = false)] // Obfuscate child items, but not the type
class NestedType
{
[ArmDot.Client.ObfuscateNames(Enable = false)]
int _dontObfuscateThisField;
[ArmDot.Client.ObfuscateNames(Enable = true)]
int _obfuscateThisField;
int _obfuscateThisField2;
void ObfuscateMe()
{
}
void ObfuscateMe2()
{
}
[ArmDot.Client.ObfuscateNames(Enable = false)] // Exclude from obfuscation
void DontObfuscateMe()
{
}
}
}
ArmDot doesn't rename types and methods that can be accessed by an external assembly, even if the names obfuscation option is inherited. However, you can still enable it for a particular type, a method, or a field by adding the attribute ObfuscateNamesAttribute.
⚠ Assembly obfuscation attributes
ArmDot attributes, like any others, can be applied to the entire assembly.
In such a case, you would place a statement like
[assembly: ArmDot.Client.ObfuscateNamesAttribute()]
in a single C# file of your project, rather than in each one.
The example:
// Enable obfuscation for the entire assembly
[assembly: ArmDot.Client.ObfuscateNamesAttribute()]
// SomePublicType is not renamed as it is public and can be used externally
public class SomePublicType
{
// DoSomething is renamed because the attribute is specified explicitly
[ArmDot.Client.ObfuscateNamesAttribute()]
public DoSomething()
{
}
}
Control flow obfuscation: ObfuscateControlFlowAttribute.
Developers use control flow obfuscation as a cheap (in terms of performance) and a practical (in terms of hacker's confusion) way to obfuscate methods. Without obfuscation, a method has a clear logic: anyone can quickly determine how the method decides to change control flow (e.g., by comparison, a result of some function with some value). Control flow obfuscation hides such decisions by spreading the instructions among tons of small methods; an original method is replaced with a loop that executes these small methods one by one. Each of such small methods returns an index of the following method.
To obfuscate the control flow of a method, apply ObfuscateControlFlowAttribute to the method. If you want to use control flow obfuscation in all methods of a particular type, apply ObfuscateControlFlowAttribute to the type. If you're going to apply control obfuscation to all methods of all types, apply ObfuscateControlFlowAttribute to the assembly.
You can permanently exclude a particular type or a method from obfuscation, using Enable and Inherit, as was shown earlier.
Code virtualization: VirtualizeCodeAttribute.
The next level of obfuscation is code virtualization. While control flow obfuscation hides the way the method decides what branch to execute, code virtualization tries to hide the logic of each instruction. To do that, ArmDot introduces a virtual machine with its stack and instruction set. Then the original code is converted to instructions for such a machine. These instructions are encoded by some format (unique each time) and placed to an assembly as a byte array. Finally, the virtual machine interprets those bytes.
This way may affect performance in some cases. Still, as most methods don't make an intensive calculation, you can apply VirtualizeCodeAttribute to an entire assembly so that ArmDot will virtualize all methods. In case if some methods make an intensive calculation, just exclude them from obfuscation.
ArmDot can't virtualize some kind of methods, e.g., open generic methods; ArmDot outputs the warning in such a case.
Strings encryption: HideStringsAttribute.
String literals are stored in open format inside an assembly. ArmDot provides a way to encode strings. To encrypt strings, apply the attribute HideStringsAttribute to a type or an entire assembly.
Embedded resources protection: ProtectEmbeddedResourcesAttribute.
Embedded resources are metadata; they are placed as-is and can be easily extracted and replaced. It is a good idea to make them invisible. To hide embedded resources, use ProtectEmbeddedResourcesAttribute.
This attribute can be applied to an assembly only because assemblies have embedded resources, not types or methods, as shown below:
[assembly: ArmDot.Client.ProtectEmbeddedResourcesAttribute()]
You can add this line to any source file of your project.
Files embedding: EmbedFileAttribute.
Sometimes applications require additional files: images, text files, managed or unmanaged DLLs. With ArmDot, you can bind files to an assembly.
The attribute EmbedFileAttribute specifies the file to embed (the property SourcePath) and its runtime path (the property RuntimePath).
When ArmDot processes the assembly, it opens the file using the path specified by the SourcePath. If the path is relative and ArmDot builds a project file (**.armdotproj*), then the base path is the ArmDot project file's directory. If MSBuild calls ArmDot (using the ArmDot.Engine.MSBuildTasks), the base path is the Visual Studio project file's directory.
RuntimePath determines a virtual path of an embedded file at runtime. It can be a full path, but usually, it consists of a predefined directory (like AssemblyDirectory) and a relative path as shown below:
[assembly: EmbedFile(SourcePath: "virtual_file_content2.txt", RuntimePath: @"$(AssemblyDirectory)\embedded_file2.txt")]
Conclusion
Declarative obfuscation is an efficient way to obfuscate only some parts of your project. Instead, you don't need to switch to the ArmDot UI; better use the ArmDot attributes to instruct the obfuscator. The properties Enable and Inherit help include and exclude items from obfuscation that is applied recursively - being used together with the MSBuild obfuscation task ArmDot.Engine.MSBuildTasks, the attributes allow developers to obfuscate projects without leaving Visual Studio.