Advanced Protection Topics
- P/Invoke Methods
- Managed C++ and IJW (It Just Works) Thunking
- Obfuscating Assemblies with Managed Resources
- Obfuscating Assemblies with Satellite DLLs
- Obfuscating Multi-module Assemblies
- Obfuscating 64-Bit Assemblies
- Reflection and Dynamic Class Loading
- Declarative Obfuscation using Custom Attributes
- Friend Assemblies
- XAML Considerations
P/Invoke Methods
P/Invoke methods (i.e. native platform methods) are automatically not renamed if Dotfuscator detects that the name is used to find the corresponding native function. P/Invoke methods that are mapped to native functions by an alias or by ordinal can be renamed.
Managed C++ and IJW (It Just Works) Thunking
Dotfuscator can process assemblies containing managed and unmanaged (native) code, such as those created by the Managed C++ compiler. Dotfuscator performs renaming and metadata removal on mixed code modules; however, string encryption, control flow obfuscation, and removal are automatically disabled. These features are still enabled on other "pure managed" input modules included in the run.
Obfuscating Assemblies with Managed Resources
Managed resources may be embedded inside a module (internal) or may be in files external to the module. Often, part of the name of the managed resource is a type name (see the .NET Framework documentation for more information about the “hub and spoke model” for lookup of managed resources.).
When the type name is renamed, Dotfuscator attempts to locate and rename the corresponding managed resource. If the resource is internal to the assembly, this is automatic. If the resource is embedded inside an external file, then the file must be in the same directory as the referencing module. If the resource is embedded inside another assembly, then that assembly must be one of the input assemblies.
Obfuscating Assemblies with Satellite DLLs
Localized applications can be seamlessly obfuscated along with their satellite resource DLLs. Dotfuscator automatically discovers these DLLs using the same rules that the runtime uses and automatically adds them as inputs to the obfuscation process. You do not need to explicitly specify them as inputs.
Your localized resources contained in the satellite DLLs will be renamed in sync with your culture neutral resources in the main assembly.
Obfuscating Multi-module Assemblies
A .NET assembly may be made up of multiple modules (i.e. files on disk). Usually an assembly is made up of one module, and this is the scenario that most tools, such as Visual Studio, support. Occasionally it is desirable to create assemblies made up of more than one module. Dotfuscator supports this scenario. Note that obfuscating a multi-module assembly is not the same as obfuscating multiple input assemblies.
To obfuscate a multi-module assembly, only the prime module needs to be listed as an input assembly. The non-prime modules are searched for in the same directory as the prime module.
In the prime module’s assembly manifest, Dotfuscator automatically updates the hash values of the other modules.
Obfuscating 64-Bit Assemblies
Dotfuscator can transparently obfuscate managed assemblies written explicitly for specific CPU architectures, including 64-bit architectures.
Dotfuscator itself is a managed application and can run on 32-bit and 64-bit versions of Windows.
Reflection and Dynamic Class Loading
Reflection and dynamic class loading are extremely powerful tools in the .NET architecture. However, this level of runtime program customization prevents Dotfuscator from infallibly determining whether it is safe to rename all types loaded into a given program.
Consider the following (C#) code fragment:
C# Code Fragment:
public object GetNewType() {
Type type = Type.GetType( GetUserInputString(), true );
object newInstance = Activator.CreateInstance( type );
return newInstance;
}
This code loads a type by name and dynamically instantiates it. In addition, the name is coming from a string input by the user!
There is no way for Dotfuscator to predict which type names the user will enter. The solution is to configure Dotfuscator to exclude the names of all potentially loadable types. Note that method and field renaming can still be performed. This is where manual user configuration plays an important role.
Often the situation is less serious. Consider a slight variation:
C# Code Fragment Variation:
public MyInterface GetNewType() {
Type type = Type.GetType( GetUserInputString(), true );
object newInstance = Activator.CreateInstance( type );
return newInstance as MyInterface;
}
Now it is immediately obvious that only a subset of types need to be excluded: those implementing MyInterface
.
Declarative Obfuscation using Custom Attributes
The .NET Framework provides two custom attributes designed to make it easy to automatically obfuscate assemblies without having to set up config files. This section outlines how you can use these attributes with Dotfuscator. It is assumed that you are familiar with custom attributes and how to apply them in your development language.
If you are using an earlier version of the .NET Framework that does not have these custom attributes, Dotfuscator ships with a DLL containing compatible attributes.
System.Reflection.ObfuscateAssemblyAttribute
The ObfuscateAssemblyAttribute
is used at the assembly level to tell Dotfuscator how to obfuscate the assembly as a whole.
This attribute's properties specify whether the assembly is in library mode and whether obfuscation attribute stripping is enabled for the assembly.
Full configuration details for the ObfuscateAssemblyAttribute
can be found here.
System.Reflection.ObfuscationAttribute
The ObfuscationAttribute
is used on types and their members and has a Feature property that specifies what action Dotfuscator should perform on the annotated code element.
Dotfuscator supports configuring the following features via ObfuscationAttributes
:
- Renaming exclusions
- Control Flow exclusions
- String Encryption inclusions
- Removal entry points and conditional inclusions
Multiple features may be configured independently by annotating a single code element with multiple ObfuscationAttribute
s, each with a different feature property value.
Dotfuscator ignores attributes with feature strings that it does not understand.
Full configuration details for the ObfuscationAttribute
can be found here.
Enabling or Disabling Declarative Obfuscation
Dotfuscator allows you to switch Declarative Obfuscation on or off for any or all input assemblies. If not enabled, Dotfuscator completely ignores obfuscation-related custom attributes.
Configure the Honor Obfuscation Attributes setting in the Dotfuscator Config Editor.
Stripping Declarative Obfuscation Attributes
Dotfuscator can remove the obfuscation attributes when processing is complete, so output assemblies will not contain clues about how they were obfuscated.
Configure the Strip Obfuscation Attributes setting in the Dotfuscator Config Editor or configure attribute stripping via the StripAfterObfuscation property of the obfuscation-related custom attributes.
Using Feature Map Strings
Dotfuscator allows you to map values contained in the Feature property of an ObfuscationAttribute
to feature strings that Dotfuscator understands.
For example, you can annotate your application with ObfuscationAttributes
that reference a feature called "testmode"
.
Dotfuscator, by default, does not understand this feature string; therefore, it ignores the attributes.
Later, if you want Dotfuscator to use these attributes to configure Renaming and Control Flow obfuscation, then map the feature string "testmode"
to Dotfuscator's built-in "renaming"
and "controlflow"
strings.
Configure the Feature Map Strings on the Settings Tab in the Dotfuscator Config Editor.
Friend Assemblies
The .NET Framework has the concept of friend assemblies, where an assembly may declare that its internal type definitions are visible to specified other assemblies.
This is done using the System.Runtime.CompilerServices.InternalsVisibleToAttribute
.
Dotfuscator detects the use of this attribute and modifies its renaming and removal rules as described below.
Assume A and B are two assemblies where assembly B references A, and A is marked with InternalsVisibleTo ( B )
.
There are a few cases of interest:
- A and B are both input assemblies in library mode. If A has no other external, non-input assembly friends, Dotfuscator safely mangles internal names and fixes up the references in assembly B. If A does have other external friends, then internal names are preserved.
- A and B are both input assemblies not in library mode. Internal names in A are, by default, mangled and references in B are fixed up, regardless of the existence of other external friend assemblies. As usual in Dotfuscator, names and groups of names can be preserved via manual configuration.
- A is an input assembly in library mode, and B is not an input assembly at all. Internal names in A are not mangled in order to not break potential references in B.
- A is an input assembly not in library mode, and B is not an input assembly at all. Internal names are mangled, potential references in B are not fixed up since it is not an input assembly. As usual in Dotfuscator, names and groups of names can be preserved via manual configuration. This case would require manual configuration if B actually does reference A's internals.
XAML Considerations
XAML Rewriting
Dotfuscator parses and updates:
- Binding Markup Extensions, if the base property in the expression belongs to a class in the XML hierarchy above that node.
- Example:
<Textbox Text="{Binding Path='Dealership.SalesTeam'}">
- Example:
- Element names, if we determine it is backed by a class we can find in the input (based on the namespace).
- If we can find a match, then we also rewrite attributes/children nodes appropriately.
XAML Rewriting Limitations
Dotfuscator does not handle:
- Actual XML namespaces
- We try to update things in templates/styles, but we may do so incorrectly. It is typically better to just exclude properties and types referenced from templates/styles.
- Attached properties/events
- We do not update the backing field, methods, and registered name for XAML references.
Best Practices
Manually exclude:
- All attached properties and events.
- All backing fields and registered methods of attached properties and events.
- Look for fields of type
System.Windows.DependencyProperty
. - Look for methods that have any parameter of type
System.Windows.DependencyObject
.- Unfortunately, a custom rule cannot be made to support this in the current version, so all instances must be found manually.
- Look for fields of type
- All properties referenced from templates/styles (and potentially all other properties in the input with the same literal name).
XAML Build Warnings
In most situations when Dotfuscator analyzes a piece of XAML and cannot match it up with a CLR object, it will issue a warning to notify users they will need to make a manual change. It is important that warnings be checked when trying to determine the cause of runtime failures.