How exclusions work
Exclusions are used to prevent certain parts of our application from being protected with Shield.
They are simple instructions given to Shield to define what action should be taken on certain parts of the code. Normally, code is excluded from obfuscation, but it can also be used to alter the behavior of Shield, i.e., to indicate, for example, a custom configuration for only certain parts of the code.
Exclusion using attributes
The easiest way to exclude some part of our application is through attributes, as we can directly indicate from the code itself what should be excluded and how.
Shield for .NET bases its exclusion system on the official .NET methodology using the ObfuscationAttribute
Read here about ObfuscationAttribute Class .NET
Discover the official Microsoft documentation on the ObfuscationAttribute class for .NET
This option also allows you to keep exclusions in your version control (GIT) to know who, when, and where part of the application has been excluded for Shield obfuscation.
Exclude a specific protection
Well, imagine you want to exclude the rename protection
You must use the attribute as follows:
[Obfuscation(Exclude=false, Feature = "-rename")]
Now, understand how it works:
First, never mark
Exclude
as trueThis is because when you mark it as true , which is the default value, you completely exclude the member from obfuscation when what you really want is to indicate parameters to Shield and not just omit this.
Second, in the
Feature
parameter, you must pass an action operator followed by theID
of the protection you want to apply it to.For example, in this case, we use - to remove protection, followed by the
ID
of the protection: rename
Basic action operators for Shield
In Shield, we always like to keep it simple, so there are only three basic operators:
- - (minus), to remove protection from the part of the code where the attribute is added.
- + (plus), to add protection to the part of the code where the attribute is added.
- ; (semicolon), to separate different actions within the same
Feature
field of the attribute.
Managing multiple exclusions at the same time
Now, to manage different actions within the same attribute, we can use the ; operator to separate the actions.
The format will be as follows:
Feature="[action1];[action2]..."
for example:
[Obfuscation(Exclude=false, Feature = "-rename;+control_flow_advance")]
where we would be removing the rename protection and adding control flow protection.
How and where to apply the obfuscation attribute
The obfuscation attribute can be applied to the following members:
- Assembly
- Class
- Delegate
- Enum
- Event
- Field
- Interface
- Method
- Parameter
- Property
- Struct
For example:
using System;
using System.Reflection;
[ObfuscationAttribute(Exclude=false, Feature = "-rename;")]
public class Type1
{
[ObfuscationAttribute(Exclude=false, Feature = "+control_flow_advance")]
public void MethodA() {}
[ObfuscationAttribute(Exclude=false, Feature = "+control_flow_advance;-constants_mutation")]
public void MethodB() {}
}
[ObfuscationAttribute(Exclude=false, Feature = "-rename")]
public class Type2
{
public void MethodA() {}
public void MethodB() {}
}
Hierarchies when applying the attribute
The hierarchy will follow the logical hierarchy of object-oriented programming, just like .NET
Module
└── Namespace
└── Type
├── Nested Types
│ └── ...
├── Methods
├── Fields
├── Properties
└── Events
Where you can use the ApplyToMembers option of the ObfuscationAttribute to define whether to apply the rules to lower members hierarchically default: yes
or to apply the rules only to the specific member but not subsequent ones.
- Use ApplyToMembers in false to avoid applying the rules to the members of a type.
using System;
using System.Reflection;
[ObfuscationAttribute(Exclude=false, Feature = "-rename;", ApplyToMembers=false)]
public class Type1
{
//MethodA and MethodB will not be excluded from renaming, Type1 will.
public void MethodA() {}
public void MethodB() {}
}
Advanced action operators
You've seen the action operators for Shield, but there are more rules for advanced actions in the protection management.
Rules on protections
- Rules: protection_id(option=value) to change specific configurations of a protection, for example:
Feature = "rename(force=true)"
which will apply the parameterforce
in the rename protection, if it contains more parameters they are separated by a comma, for example:Feature = "rename(force=true, mode=default)"
Filters
Namespace: namespace('value'): to apply rules specifically to a specific member, for example:
Feature = "namespace('Payments'):-rename"
that will exclude the rename protection in the namespacePayments
orFeature = "namespace('Payments.PaypalManager'):-rename"
that will exclude the rename protection in the typePaypalManager
Name: name('value'): to apply rules specifically to a specific member according to its name, for example:
Feature = "name('Main'):-rename"
that will exclude the rename protection to any member calledMain
.Based on the same dynamics as namespace and name , the following rules can be applied:
- decl-type : To filter according to the fullname of the DeclaringType of a member
- module : To filter according to the name of the module of a member
- full-name : To filter according to the fullname of a member
- member-type : To filter according to the type of a member, i.e:
Feature = "member-type('type'):-rename"
- is-type : To filter according to the type of a member or in its defect its DeclaringType, i.e:
Feature = "is-type('enum'):-rename"
- is-public : To filter according to the visibility of the member, in this case, only public ones, i.e:
Feature = "is-public:-rename"
- has-attr : To filter members containing an attribute with a specific name, i.e:
Feature = "has-attr('DllImport'):-rename"
Dynamic filters (regex)
Match: match('regex'): applies a regex type filter that checks on the full name of the member, for example:
Feature = "match('^ShieldPayments.Paypal.Manager[0-9]'):-rename"
that will exclude the rename protection for any member whose fullname isShieldPayments.Paypal.Manager
followed by a number from0 to 9
.Match by name: match-name('regex'): like
match
but will not filter by the full name, instead, by the unique name of the member, for example:Feature = "match-name('[a-zA-Z]*Shield[a-zA-Z]*'):-rename"
that will exclude the rename protection to any member whose name containsShield
likeTryShieldNow
orInitShield
.Match by declaring type: match-type-name('regex'): like
match
but will filter by the name of the declaring type only, for example:Feature = "match-type-name('[a-zA-Z]*Shield[a-zA-Z]*'):-rename"
that will exclude the rename protection for any member of the type whose name containsShield
likeLoadShield.Now()
orShieldPayments.UpdateCard()
.
Logical operators
Once all the filters and previous rules have been applied, it is time to concatenate rules and conditions in a single exclusion attribute. For this, we will use logical operators:
And and to join two conditions that must be met, such as
Feature = "is-type('enum') and name('Cards'):-rename"
that will exclude the rename protection for all types that are Enum and are named Cards.Or or to check two conditions where one of them must be met, such as
Feature = "is-type('enum') or is-public:-rename"
that will exclude the rename protection for all types that are public or are Enum.Not not to negate a condition, such as
Feature = "not is-type('enum'):-rename"
that will exclude the rename protection for all types that are NOT Enum.
Real code examples
You can copy these attributes into your code to generate your own rules.
Basic Shield operators:
using System;
using System.Reflection;
// This type will be obfuscated because Shield by default will not exclude anything.
public class Type1
{
//Exclude MethodA from being renamed and add control flow on it.
[ObfuscationAttribute(Exclude=false, Feature = "-rename;+control_flow_advance")]
public void MethodA() {}
//The member will be protected according to Shield's configuration
public void MethodB() {}
}
// Exclude this type from being renamed by Shield, but does not apply the exclusion to its members, so it will only be applied to Type2
[ObfuscationAttribute(Exclude=false, Feature = "-rename", ApplyToMembers=false)]
public class Type2
{
// As the members of Type2 are not applied to Type2 exclusion, we must force that MethodA is also not renamed, for example.
[ObfuscationAttribute(Exclude=false, Feature="-rename")]
public void MethodA() {}
// This member will be renamed because ApplyToMembers in false prevents the Type2 rule from applying to its members
public void MethodB() {}
}
// The attributes can be grouped, but the order must be specified according to the desired priority.
// For example, with 1. we make Typ3 not to be renamed, but its members will.
// With rule 2. we will make Type3 and its members not to have control flow.
[ObfuscationAttribute(Exclude=false, Feature = "1. -rename", ApplyToMembers=false)]
[ObfuscationAttribute(Exclude=false, Feature = "2. -control_flow_advance")]
public class Type3
{
//This member will be renamed because ApplyToMembers is false, but it will not include control flow.
public void MethodA() {}
//This member will be renamed because ApplyToMembers is false, but it will not include control flow.
public void MethodB() {}
}
Advanced Shield operators:
using System;
using System.Reflection;
[ObfuscationAttribute(Exclude=false, Feature = "not is-type('enum') and not has-attr('DllImport') and is-public:+rename;-constants_mutation")]
public class Type1
{
//This method will have rename protection and no constants mutation because it is not an enum type, does not contain the DllImport attribute, and is also public.
public void MethodA() {}
}
[ObfuscationAttribute(Exclude=false, Feature = "1. -constants_mutation;+rename", ApplyToMembers=false)]
[ObfuscationAttribute(Exclude=false, Feature = "2. not is-type('enum') and not has-attr('DllImport') and is-public:+constants_mutation")]
[ObfuscationAttribute(Exclude=false, Feature = "3. match-name('[a-zA-Z]*Shield[a-zA-Z]*') or has-attr('External'):-rename")]
//The type Type2 will be renamed and constants_mutation will be excluded based on the first rule
public class Type2
{
//Based on the first rule this method is not affected because ApplyToMembers is false.
//Since the second rule matches that this method is public and not enum nor contains the DllImport attribute, constants_mutation will be applied
//As the third rule neither the name matches nor contains the External attribute, MethodA will be renamed.
public void MethodA() {}
//The first rule does not apply
//The second matches, so constants_mutation will be applied
//As in the third the name ShieldReload matches the Regex rule, it will be excluded from being renamed.
public void ShieldReload() {}
//The first rule does not apply
//The second matches, so constants_mutation will be applied
//As in the third it matches that MethodB contains the External attribute, it will be excluded from being renamed.
[External(".exe")]
public void MethodB() {}
//The first rule does not apply
//The second does NOT match because it contains the DllImport attribute, so constants_mutation will NOT be applied
//As in the third it does NOT match because it does not contain Shield in the name, and does not have the External attribute, so rename will not be excluded.
[DllImport("kernel32")]
public void MethodC() {}
}
Now you can easily manage the exclusions of your application!