Checks are runtime validations that detect and react to unauthorized use of an application. For example, a Debugging Check detects whether the application is being run under a debugger.
Dotfuscator adds Checks to your application through code injection. You do not need to write the validation logic yourself. Dotfuscator injects the required Check logic and can also inject a built-in response for when the Check detects unauthorized use. You can also configure the Check to notify your application of the validation result.
Checks protect the application while it runs. This complements obfuscation, which protects the application at rest, such as assembly files. Using obfuscation and Checks together provides a layered approach to application protection.
Configure Checks
To have Dotfuscator inject Checks, first enable Checks in your Dotfuscator configuration.
Then, specify which Checks Dotfuscator should inject. You can configure Checks in one of the following ways:
- Use the Checks screen in the Dotfuscator Config Editor.
- Annotate your source code with Check attributes.
Both methods allow you to configure properties that determine how the Check operates. For the full list of available properties, see the Check Attributes article.
After Dotfuscator processes the assemblies, the resulting application automatically performs the configured Checks when the specified methods are called at runtime.
Check types
Each Check has a type. Each Check type performs different validation and may have different configuration properties.
A single application can have multiple Checks of the same type, as long as they target distinct locations.
Dotfuscator provides the following Check types:
| Check type | Description |
| Debugging Check | Detects whether a debugger is attached to the application. |
| Tamper Check | Detects whether the application code has been modified. |
| Shelf Life Check | Detects whether the application is running after its expiration date. |
| Root Check | Detects whether the application is being run on a rooted Android device. |
Locations
Each Check targets one or more methods in the application code. These methods are called locations.
When a location is called at runtime, the associated Check runs. The Check validates the state of the application according to its type and reacts according to its configured properties.
A location can be associated with multiple Checks, as long as each Check is a different type. When that location is called, each Check runs in sequence.
A Check can target multiple locations. In that case, the Check runs each time any of those locations is called.
After a Check runs, it does not run again until another configured location is called. For example, if a Debugging Check targets only the LoadFile(String path) method, the Check runs when that method is called. If a debugger is attached after that method runs, the debugger is not detected until LoadFile(String path) is called again.
Properties
Each Check can be configured with properties that determine how the Check operates, including how it reacts to unauthorized states.
The available properties depend on the Check type. For the full list of properties, see the Check Attributes article.
Different Checks of the same type can use different property values. For example, one Debugging Check can be configured to exit the application when a debugger is detected, while another Debugging Check can be configured to notify the application and continue running.
Application Notification
Checks can notify the application code of the Check result. This allows the application to react to an unauthorized state in a custom way, such as by disabling application features or sending telemetry.
Application Notification happens before the configured Check Action.
The following Check properties determine how a Check notifies the application:
ApplicationNotificationSinkElementApplicationNotificationSinkNameApplicationNotificationSinkOwner
These properties specify a sink in the application code that receives a bool value:
-
trueif the unauthorized state was detected. -
falseif the unauthorized state was not detected.
If a sink is specified, the Check always calls it, even when the Check does not detect an unauthorized state.
Example
Consider the following Tamper Check and sample code for the ApplicationNotificationExample class:
internal class ApplicationNotificationExample
{
private bool? myFlag;
public void Run()
{
Console.WriteLine("Application logic...");
TamperCheckLocation();
Console.WriteLine("More Application logic...");
Console.WriteLine($"The CheckResult was '{myFlag}'.");
}
private void TamperCheckLocation()
{
Console.WriteLine("This method is a location of a Tamper Check, which will run before this text.");
}
private void TamperCheckSink(bool tamperingDetected)
{
Console.WriteLine("This method is a sink for the Tamper Check.");
if (tamperingDetected)
{
Console.WriteLine("This application has been tampered!");
}
else
{
Console.WriteLine("This application has not been tampered.");
}
myFlag = tamperingDetected;
}
}After Dotfuscator injects the Check code, when other application code calls Run():
-
Run()writes to the console and then callsTamperCheckLocation(). - Before
TamperCheckLocation()executes, the Tamper Check runs:- The Check determines if the application has been modified after it was processed by Dotfuscator.
- As its application notification, the Check calls the
TamperCheckSink(bool)method, supplying as the argumenttrueif tampering was detected, andfalseotherwise. -
TamperCheckSink(bool)writes information about the Tamper Check to the console, sets themyFlagfield to the Check's result for later use, and returns control to the Check. - The Check finishes running and returns control to the
TamperCheckLocation()method.
-
TamperCheckLocation()executes, then returns control toRun(). -
Run()writes additional information to the console, including a use of themyFlagfield. -
Run()returns control to its caller.
Check Actions
Checks can react to unauthorized application states by using built-in responses called Check Actions.
- A Check Action happens after Application Notification.
- Each Check can have up to one Check Action. To define more complex behavior, use Application Notification and have the application perform the behavior.
The Check Action is determined by the Check’s Action property.
The available Check Actions are:
| Check Action | Description |
| None | The Check returns control to the application after running, even if the Check detected an unauthorized state. |
| Exit | If the Check detects an unauthorized state, the application exits immediately with exit code 0. |
| Exception | If the Check detects an unauthorized state, an exception is thrown. |
| Hang | If the Check detects an unauthorized state, the current thread hangs indefinitely. |
Action Probability
You can configure a Check Action to occur randomly. This makes the application behavior less predictable when an unauthorized state is detected.
The probability of a Check Action occurring is determined by the ActionProbability property.
A value of 1.00 means the Check Action always occurs. A value of 0.00 means the Check Action never occurs. A value between these numbers makes the action occur randomly. For example, a value of 0.25 means the Check Action occurs 25% of the times the Check runs.
Configure the Check library code location
Dotfuscator injects library code into one of the input assemblies for each kind of Check included in the application.
Dotfuscator performs a dependency analysis of the input assemblies to choose the best assembly to receive this code injection. It chooses the assembly in a way that minimizes new dependencies among input assemblies. However, adding new dependencies is unavoidable in some cases.
You can override the assembly where this code is injected by adding a Config Property named accesspoint and setting its value to the name of the assembly where the code should be inserted. You can add this Config Property in the Config Editor.