Skip to main content
Version: 2023.4

4.3. Conditional Obfuscation

Conditional obfuscation is an extension feature to indirect declarative obfuscation.

4.3.1. Types

Conditional obfuscation allows to process the types in bulk according to their natural properties such as visibility (public, internal, protected, private), subtype (class, struct, enum, delegate), and others.

This functionality is achieved by when conditional clause, which can be specified in an obfuscation attribute. So the full conditional attribute notation has the following form:

[assembly: Obfuscation(Feature = "apply to type [name mask] when [condition]: [feature]")]

where [name mask] is a glob mask for the type name and [condition] is a string defining the condition for a match.

Condition is defined as a boolean predicate with Pascal syntax. A quick example of a conditional attribute is shown below.

Example 4.8. Indirectly disable renaming of all internal enums and their members

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when internal and enum: renaming", Exclude = true, ApplyToMembers = true)]

Please take a closer look at internal and enum predicate in the sample above. What it says is this: the result of predicate is true when internal variable equals to true and enum variable equals to true too; otherwise the result of predicate is false. The values of internal and enum variables are calculated for every type in the assembly, describing the natural properties of a CLR type in boolean form.

The list of available variables is presented in the table below.

Table 4.1. The list of available variables for conditional obfuscation of types

Variable

Description

abstract

true if the type is abstract; otherwise, false

anonymous

true if the type is anonymous; otherwise, false

class

true if the type is a class or a record; otherwise, false

closure

true if the type is a container synthesized to hold captured variables for closures; otherwise, false

compiler-generated

true if the type is generated by a compiler; otherwise, false

delegate

true if the type is a delegate; otherwise, false

enum

true if the type is an enumeration; otherwise, false

generic

true if the type is generic; otherwise, false

interface

true if the type is an interface; otherwise, false

internal

true if the type is internal; otherwise, false

nested

true if the type is nested; otherwise, false

obfuscator-generated

true if the type is generated by an obfuscator; otherwise, false

private

true if the type is private; otherwise, false. Applies to nested types only; false if the type is not nested

protected

true if the type is protected; otherwise, false. Applies to nested types only; false if the type is not nested

public

true if the type is public; otherwise, false

record

true if the type is a record; otherwise, false

sealed

true if the type is sealed; otherwise, false

serializable

true if the type is serializable; otherwise, false

static

true if the type is static; otherwise, false

struct

true if the type is a structure; otherwise, false

Predefined constants can be used in expressions as well. The list of available constants is presented in the table below.

Table 4.2. The list of available constants for conditional obfuscation

Constant

Description

false

false value

true

true value

Built-in functions can be used in expressions too. The list of available functions is presented in the table below.

Table 4.3. Built-in functions for conditional obfuscation

Function

Description

inherits('type-name')

Returns true if the type inherits another type specified by type-name parameter; otherwise, false. Inherits function considers base and all inherited types including interfaces. Type name parameter can contain either the full type name or a mask for a bulk match

extends('type-name')

Returns true if the type extends another type specified by type-name parameter; otherwise, false. Extends function only checks the base type, but all implemented and inherited interfaces are considered. Type name parameter can contain either the full type name or a mask for a bulk match

has-attribute('type-name')

Returns true if the type or member has a custom attribute specified by type-name parameter; otherwise, false. Type name parameter can contain either the full type name or a mask for a bulk match.
Example:

has-attribute('System.ComponentModel.DisplayNameAttribute')
matches('name-mask')

Returns true if the type or member name matches a mask specified by the parameter; otherwise, false.

Variables, functions and constants can be combined by operators. They have the standard Pascal precedence. The list of available operators is presented in the table below.

Table 4.4. The list of available operators for conditional obfuscation

Operator

Description

Priority

not

Unary operator for boolean negation

Highest

and

Binary operator for boolean and operation

Medium

or

Binary operator for boolean or operation

Lower

=

Binary operator for boolean equal operation

Lowest

<>

Binary operator for boolean not equal operation

Lowest

The precedence of operations can be changed by parentheses.

Let's take a look at examples.

Example 4.9. Disable renaming of all types except enums

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when not enum: renaming", Exclude = true)]

Example 4.10. Disable renaming of all internal nested and serializable types together with their members

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when internal and (nested or serializable): renaming", Exclude = true, ApplyToMembers = true)]

Example 4.11. Disable renaming of all types except internal nested and serializable types

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when not (internal and (nested or serializable)): renaming", Exclude = true)]

Example 4.12. Disable renaming of all interfaces in Contoso.Core.Services namespace

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type Contoso.Core.Services.* when interface: renaming", Exclude = true)]

Example 4.13.  Disable renaming of all classes derived from System.IDisposable interface. Renaming of members for matched classes is disabled too

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when class and extends('System.IDisposable'): renaming", Exclude = true, ApplyToMembers = true)]

As you can see, the conditions can have any complexity and can be freely defined to achieve your specific goals.

4.3.2. Type Members

It was described how to tune the obfuscation of types in a bulk way in the section above. But what about type members such as methods, fields, properties, and others? Sometimes it may be beneficial to process them in a bulk way too.

The declarative attribute for conditional obfuscation of type members has the following form:

using System.Reflection;

[Obfuscation(Feature = "apply to member [name mask] when [condition]: [feature]")]
class Sample1
{

}

[name mask] is a glob mask which selects the members according to their names. [condition] allows to specify a boolean predicate to select members according to their properties. The rules are all the same as for types; the only difference is a set of variables which can be used in a boolean predicate. The list of available variables is presented below.

Table 4.5. The list of available variables for conditional obfuscation of type members

Variable

Description

abstract

true if the member is abstract; otherwise, false

compiler-generated

true if the member is generated by a compiler; otherwise, false

const

true if the member defines a literal constant; otherwise, false. Applies to fields only; false if the member is not a field

constructor

true if the member is a constructor; otherwise, false

event

true if the member is an event; otherwise, false

field

true if the member is a field; otherwise, false

generic

true if the member is generic; otherwise, false. Applies to methods only; false if the member is not a method

internal

true if the member is internal; otherwise, false

method

true if the member is a method; otherwise, false

obfuscator-generated

true if the member is generated by an obfuscator; otherwise, false

private

true if the member is private; otherwise, false

property

true if the member is a property; otherwise, false

protected

true if the member is protected; otherwise, false

public

true if the member is public; otherwise, false

readonly

true if the member is read-only; otherwise, false. Applies to fields and properties only; false if the member is neither a field nor a property

static

true if the member is static; otherwise, false

virtual

true if the member is virtual; otherwise, false

The information on this topic is extremely bare, so let's take a relaxed look at some real-life samples.

Example 4.14. Disable renaming of public properties

using System.Reflection;

[Obfuscation(Feature = "apply to member * when property and public: renaming", Exclude = true)]
class ImageQualityService
{

}

Example 4.15. Disable renaming of all methods

using System.Reflection;

[Obfuscation(Feature = "apply to member * when method: renaming", Exclude = true)]
class CellCallEngine
{

}

Example 4.16. Disable renaming of internal fields

using System.Reflection;

[Obfuscation(Feature = "apply to member * when field and internal: renaming", Exclude = true)]
class ContosoHeadquarters
{

}

4.3.3. Options are Combinable

Conditional obfuscation of types and type members can be easily combined to achieve specific goals in an elegant and powerful way.

Just take a look at the samples below.

Example 4.17.  Disable renaming of property Contoso in type Acme.Services

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type Acme.Services: apply to member Contoso when property: renaming", Exclude = true)]

Example 4.18. Disable renaming of all public properties in all types

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type *: apply to member * when public and property: renaming", Exclude = true)]

Example 4.19.  Disable renaming of all public properties in types defined in MyNamespace

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type MyNamespace.*: apply to member * when public and property: renaming", Exclude = true)]

Example 4.20. Disable renaming of all internal events in all public types

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when public: apply to member * when event and internal: renaming", Exclude = true)]

4.3.4. Diagnostics

How to know which types and members conditional obfuscation applies to?

Sometimes it may be useful to get the full list of types and members targeted by a specific conditional obfuscation directive. To achieve that, you can use the log feature as shown below:

Example 4.21. Log all members targeted by a conditional obfuscation directive

using System.Reflection;

[assembly: Obfuscation(Feature = "apply to type * when public: apply to member *: log")]
Note

Logging does not affect obfuscation in any way. It just dumps the list of all targeted items to the Eazfuscator.NET's log.