Attributes
Dotfuscator is a post-build tool, meaning it operates on compiled .NET assemblies, not source code. Because of this, most options for how Dotfuscator protects an application are stored in a Dotfuscator config file, separate from the source code.
However, if you do have access to the source code, you can also configure some Dotfuscator features with .NET attributes.
Dotfuscator recognizes certain attributes when it processes assemblies, which configure how Dotfuscator will process the code elements the attributes annotate. By default, Dotfuscator also strips (removes) these attributes during processing, so that the shipped application does not contain sensitive configuration information.
Compatible Attributes
Dotfuscator recognizes certain attributes and ignores all others. The attributes that Dotfuscator recognizes are:
- Obfuscation Attributes which configure Declarative Obfuscation
- Check Attributes, which tell Dotfuscator how to inject Checks
For details on each set of attributes, see the linked pages. The rest of this page will cover aspects common to many attributes.
When to Use Attributes
Using attributes makes it easier to see, when looking at the source code, how code elements will be affected by Dotfuscator.
For instance, the following C# snippet uses the ObfuscationAttribute
to indicate a class (but not its members) should be excluded from renaming:
using System.Reflection;
[Obfuscation(Feature = "renaming", Exclude = true, ApplyToMembers = false)]
private class MyClass { /* ... */ }
However, there are some scenarios that are better served by using the Dotfuscator user interface to modify the Dotfuscator config file, rather than by using attributes. These scenarios include:
- When working with assemblies whose source code you cannot edit.
- When recompiling the code every time you want to change the protection settings is too much overhead for your development process.
- When using rules to exclude code items from obfuscation is more convenient or efficient than annotating each individual code item with an attribute.
- When configuring a Check that targets multiple locations.
You can combine attributes with Dotfuscator config file settings.
For instance, the snippet above indicates to Dotfuscator that MyClass
should be excluded from renaming, but you can exclude other types using the Dotfuscator user interface and save those exclusions to the config file.
When Dotfuscator processes the assemblies per the config file, it will not rename code items that were excluded either by attributes or the config file.
Adding Attributes to Code
To add attributes to your code and have Dotfuscator recognize them:
Add the appropriate references to your project. For details, see:
Apply compatible attributes to your code as you would any attribute.
The namespace (and thus the necessary
using
statement) varies depending on the attribute. See each attribute's section for details.As with other attributes, you may omit the suffix
Attribute
in the usage. For instance, this is a usage ofTamperCheckAttribute
:[TamperCheck(Action = CheckAction.Exit)] public void MyMethod() { // method body }
Build your application.
If using Obfuscation Attributes, use the Dotfuscator user interface to configure the following settings:
For each assembly that contains such attributes, ensure the Honor obfuscation attributes and Strip obfuscation attributes options are enabled.
Ensure the relevant protection features (such as renaming) are enabled.
If using Check Attributes, use the Dotfuscator user interface to configure the following settings:
For each assembly that contains such attributes, ensure the Honor check attributes and Strip check attributes options are enabled.
Ensure that Checks are enabled.
Build the config file. Dotfuscator acts according to the configured attributes, then removes the attributes (and the related assembly references) from your application.
Interacting with Application Code
Existing application code can receive information from code injected by Dotfuscator. Such application code is known as a sink.
Each Check can define one or more sinks for different contexts. The sinks of a Check are defined by three of its properties:
An element property, which specifies the kind of code item that sink is, such as Field or Method. For more information, see the Sink Element Kinds subsection.
A name property, which specifies the name of the code item (if applicable).
An optional owner property, which specifies the type that defines the code item (if applicable). If not specified, this defaults to the type that defines the location of the Check. For more information, see the Owners subsection.
For instance, when a Check runs, it can inform the application of its result via an application notification sink. The sink can be configured through the Check's ApplicationNotificationSink properties:
ApplicationNotificationSinkElement
ApplicationNotificationSinkName
ApplicationNotificationSinkOwner
Details on the sinks available for a given Check are listed on the relevant sections of the Check Attributes page.
Sink Element Kinds
This section lists the kinds of code elements that can be used as a sink for injected code.
Note that the supported elements, signatures of the elements, and meaning of the values provided to the sink vary by Check. See the Check Attributes page for details.
For instance, a Check's Application Notification Sink requires a bool
field or property; or a method, delegate field, or delegate method argument with signature void(bool)
.
The Boolean value is true
if the Check detected the unauthorized condition (such as an attached debugger or tampering) and false
otherwise.
What follows is a list of possible values for the element property of a Check (for example, ApplicationNotificationSinkElement for a Tamper Check). The possible sink element kinds are:
None: The sink is disabled and no information is provided from the injected code to the application.
This is usually the default value for the element property.
The name and owner properties are ignored with this setting.
Method: The injected code calls a method, providing the information through the method's parameters. The method must be accessible to all locations of the Check.
The name property specifies the simple name of the method (for example,
MyMethod
, notvoid MyMethod(bool)
).The owner property specifies the type that declares the method.
Field: The injected code provides the information by setting a field. The field must be accessible and settable to all locations of the Check.
The name property specifies the simple name of the field.
The owner property specifies the type that declares the field.
Property: The injected code provides the information by setting a property. The property and its setter must be accessible to all locations of the Check.
The name property specifies the simple name of the property.
The owner property specifies the type that declares the property.
MethodArgument: The injected code calls a delegate specified by a method argument, providing the information through the delegate's parameters. The method argument must be an argument to all locations of the Check.
The name property specifies the name of the method argument.
The owner property is ignored with this setting.
Delegate: The injected code calls a delegate specified by a field, providing the information through the delegate's parameters. The field must be accessible to all locations of the Check.
The name property specifies the simple name of the field.
The owner property specifies the type that declares the field.
DefaultAction: The injected code performs some default behavior based on the information. The exact behavior varies depending on the particular sink being configured, which can include this setting being an invalid configuration.
Despite its name, this is not always the default value for the element property.
The name and owner properties are ignored with this setting.
Owners
A sink's owner is the type that declares the sink.
The value of the owner property is a type name is expressed including the namespace, but not the assembly name.
For instance, consider a field named wasTampered
on the class MyType
which is declared in the namespace Outer.Inner
.
The properties defining a sink to this field would be as follows:
The element property: Field
The name property:
wasTampered
The owner property:
Outer.Inner.MyType
A sink may be a static member or an instance member of the owner. This distinction can affect how the owner property is used. Additionally, when using a Check with multiple locations, it is possible to have each location refer to a different owner.
For a Static Member
If the sink is a static member, then it may be defined on any type, provided it is accessible to all locations of the Check. The owner property specifies which type this is. If the property's value is blank, the type is assumed to be the type that defines the location of the Check.
For an Instance Member
If the sink is an instance member, then the following must be true:
The location of the Check must also be an instance member.
The location of the Check must be defined by the same type that declares the sink.
The owner property must be the name of this type, or be blank.
At runtime, the instance that was used when calling the location will also be used for the sink.
Multiple Owners
When using a Check with multiple locations, each location is evaluated individually with respect to sinks. This means that each location may have a different sink, provided the following are true:
Each type that defines a location for the Check must have the specified sink member.
If a particular location is a static member, then the corresponding sink member must also be a static member.
If a particular location is an instance member, then the corresponding sink member may be an instance member or a static member.
The owner property must be left blank.