PreEmptive Protection - Dotfuscator 4.31
User Guide

Understanding Checks

Checks are runtime validations that detect and react to unauthorized uses of an application. For instance, a Debugging Check detects if the application is being run under a debugger.

Dotfuscator can add Checks to your application through code injection. There's no need to write any code yourself; Dotfuscator automatically injects the appropriate validation logic, and can also inject a pre-built response for when the Check detects unauthorized use. You can also configure the Check to notify your application of the validation result, and to send telemetry to track incidents.

Important: By default, a Check does not do anything when an unauthorized state is detected. When configuring a Check, you must specify, using the Check's properties and the telemetry settings, how the Check should react to an unauthorized state.

In contrast to obfuscation which protects the application "at rest" (i.e., as assembly files), Checks protect the application while it runs. Using obfuscation and Checks together provides a layered approach to protecting your application, defending both your application code and your business data from attack.

Configuring Checks

To have Dotfuscator inject Checks, first enable injection.

To specify which Checks Dotfuscator will inject, use the Checks screen in the Dotfuscator user interface. If you prefer, you can also specify Checks by annotating your source code with Check Attributes. Both of these methods allow you to specify various properties that determine how the Check operates; for a full listing, see the Check Attributes page.

After Dotfuscator processes the assemblies, the resulting application will automatically perform Checks as specified methods are called at runtime.

Check Types

Each Check has a type. Each Check type performs a different set of validations, and may have slightly different properties to configure. A single application can have multiple Checks of the same type, provided they target distinct locations.

The Check types are:

  • Tamper Check, which detects if the application code has been modified
  • Debugging Check, which detects if a debugger is attached to the application
  • Shelf Life Check, which detects if the application is being run after an expiration date

You can find details on each Check type on the associated pages linked above. The rest of this page covers aspects that apply to multiple types of Checks.

Locations

Each Check targets one or more methods within the application code. These methods are known as a Check's locations.

Whenever a location is called at runtime, the associated Check runs, validating the state of the application at that instant per the Check's type and responding per the Check's properties.

A location may be associated with multiple Checks, provided each Check is of a different type; when such a location is called, each of its Checks run in sequence.

Note: If a Check has an Application Notification or Check Action that interrupts normal control flow (such as by throwing an exception), the other Checks on the same location may not run.

A Check may target multiple locations; if so, then the Check runs each time any of those locations is called.

Note: Only Checks defined in the user interface can have multiple locations. If a Check is defined by a Check attribute, then it has only one location, which is the method that the attribute annotates.

After a Check runs, it will not run again until another location is called. For instance, say a Debugging Check targets a single location, LoadFile(String path), which is called whenever the user loads a file in the application. If an attacker runs the application, loads a file, and then attaches a debugger, the debugger will not be detected until the attacker loads a file again.

Properties

Each Check can be configured with various properties, which determine how the Check operates, including how it reacts to unauthorized states. The properties available depend on the type of Check, so see the Check Attributes page for a full list.

Different Checks of the same type may have different property values. For instance, consider an application which has three methods of note: UserLogin(), AdminLogin(), and SupportLogin(). We want to prevent normal users and local administrators from attaching a debugger to the software, but in some scenarios it may be permissible for support personnel to be running the software in a debugger. We can configure two Debugging Checks:

  • The first Debugging Check targets both UserLogin() and AdminLogin(). We set the Check's Action property to Exit, causing the application to exit outright when a user or administrator runs the application under a debugger.

  • The second Debugging Check targets SupportLogin(). We set the Check's Action property to None, allowing the application to continue when run under a debugger by a support member. (We may want to enable Debugging Check Telemetry for the application, so that we can monitor if it looks like a non-support user is using a support account).

Check Telemetry

Checks can send telemetry when an unauthorized state is detected. For more information, see the Check Telemetry page.

Application Notification

Checks can notify the application code of the Check's result. In this way, the application can react to an unauthorized state in a customized way, such as by disabling application features or by sending third-party telemetry.

This notification happens before the specified Check Action.

Note: Application Notification for Shelf Life Checks differs from other types of Checks; see the Shelf Life Check page's Application Notification section for details.

The following properties of a Check determine how that Check notifies the application:

  • ApplicationNotificationSinkElement
  • ApplicationNotificationSinkName
  • ApplicationNotificationSinkOwner

These properties specify a sink in the application code that receives a bool value: true if the unauthorized state was detected, and false otherwise. That is, if a sink is specified, the Check always calls it, even for negative results. For details on specifying a sink, see the relevant properties on the Check Attributes page.

As an 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's injection, when other application code calls Run():

  1. Run() writes to the console and then calls TamperCheckLocation().

  2. 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 argument true if tampering was detected, and false otherwise.

    • TamperCheckSink(bool) writes information about the Tamper Check to the console, sets the myFlag field 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.

  3. TamperCheckLocation() executes, then returns control to Run().

  4. Run() writes additional information to the console, including a use of the myFlag field.

  5. Run() returns control to its caller.

Check Actions

Checks can themselves react to unauthorized application states in a number of built-in ways, such as by exiting the application or throwing a random exception. These reactions are known as Check Actions.

The Check Action occurs after the Application Notification behavior for the Check.

Each Check may have up to one Check Action. To define more complicated behavior, use application notification and have the application perform the behavior.

Note: Shelf Life Checks do not support Actions. However, you can still configure them to exit the application if it is expired.

Action Kinds

The kind of Check Action that a Check will use is determined by the Check's Action property.

The kinds of Check Action are:

  • None: The Check will return control to the application after running, even if the Check detected an unauthorized application state.

  • Exit: If the Check detected an unauthorized application state, the application will exit immediately with exit code 0.

  • Exception: If the Check detected an unauthorized application state, an exception is thrown.

    • If any Checks have this Check Action, Dotfuscator will add an exception type to the application that will be thrown by all instances of this Check Action. The exception will mimic a common .NET exception, such as System.NullReferenceException.

    • The thrown exception will attempt to hide its stack trace, to prevent further reverse-engineering. However, the stack trace will be visible in some places, such as in the default .NET unhandled exception handler. You may want to catch and handle exceptions thrown by this Check Action, which will appear to have no stack trace when handled by user code.

  • Hang: If the Check detected an unauthorized application state, the current thread hangs indefinitely.

Action Probability

You can configure a Check Action to occur randomly. This makes the behavior of the application more frustrating and less predictable for an attacker; only sometimes will the application "misbehave", and other times it will operate normally.

The probability of a Check Action occurring when the Check runs is determined by the Check's ActionProbability property.

A setting of 1.00 means the Check Action will always occur, and a setting of 0.00 means the Check Action will never occur. Choosing a setting between these values will produce random behavior; for instance, a setting of 0.25 means the Check Action will occur only 25% (one-quarter) of the times the Check runs.

Dotfuscator Version 4.31.0.6091. Copyright © 2017 PreEmptive Solutions, LLC