Beginning with Dotfuscator Professional Edition version 4.25, you’ve been able to add anti-debug protections to your .NET applications, and now that Visual Studio 2017 has shipped, Dotfuscator Community Edition (CE) users have access to those protections as well.
If you are reading this, you likely already know that a debugger is a powerful tool for watching and interacting with an application as it runs, and like any powerful tool, it can be used for good or ill. For example, an application owner would likely consider any of the following debugging scenarios “ill use” when carried out by an unauthorized party:
Static protections, like obfuscation, can help mitigate the threat—it is significantly more difficult to debug a well-obfuscated application—but, as always in security, a layered approach is best. Runtime detections and defenses (such as anti-debugging), in conjunction with static protections, make for an even more effective deterrent.
In Dotfuscator, these runtime detections and defenses are commonly referred to as “Checks”. Anti-debug is one kind of Check. Dotfuscator includes other Checks as well, such as anti-tamper and expiration (a.k.a. “Shelf-Life”). In each case, Dotfuscator injects the detection code into your application at the location you specify (using a custom or extended attribute). At runtime, when the condition is detected, the application can then defend itself, based on how the Check was configured.
The response options include:
These are simple to add and require no coding. In addition, you can specify a probability of the action occurring in order to get non-deterministic behavior. The preconfigured actions include:
These allow you to modify the state and behavior of your application in any way you wish, in response to a detection. A Check can:
This allows you to see who or what triggered the check.
To see some specifics and get an idea of what’s possible, let’s dive into a concrete example.
For demonstration purposes, we’ve created a simple WPF application: a front end to a remote expense approval service. You can find the source and documentation on GitHub.
This is a simplified version of the application that PreEmptive’s Sebastian Holst showed in this video. Among other things, the video shows what a debugging attack might look like and how a debugger can be used to exploit programming flaws in order to escalate privileges. You will get more out of this article if you go watch it right now and come back.
The sample includes configuration files for both Dotfuscator Pro and Dotfuscator CE. We will begin with the Pro configuration, then talk about CE and the differences between the two. If you want to follow along, you should pull the source, build the Release configuration in Visual Studio 2015 or 2017, then open the Dotfuscator configuration file in the Dotfuscator UI using the configuration that corresponds to your Dotfuscator SKU. If you don’t yet have Dotfuscator, you can find instructions for getting it on the Dotfuscator Downloads page. If you are using CE, you will need the latest version that comes with Visual Studio 2017.
Once open in Dotfuscator, build the project. The build will protect the input EXE and write the protected EXE to the Dotfuscator output directory. To make the protected application runnable from that directory, you will need to manually copy the original dependencies if you are using CE (the Pro configuration has a post-build event that does this for you). To do so, copy the DLLs from the project’s binRelease directory into the Dotfuscator output directory. When run, the application should look like this:

After opening the Pro configuration file with the Dotfuscator Professional standalone UI, you can find the extended attributes used to configure the Checks on the Instrumentation tab.

We will go through each one, describing what it does, how it is implemented, and why it is useful.
Starting at the beginning, the App.Application_Startup() method has a DebuggingCheckAttribute that forces the application to exit 100% of the time if a debugger is attached. This is accomplished by setting the “Action” to “Exit” and leaving the other settings at their default values. Exiting at startup prevents launching the app via a debugger; an attacker must instead attach to the process after it starts.
Also at startup—this time in the App.Main() method—a Shelf-Life Check runs, and disables (i.e., “bricks”) the app if debugging was detected in a previous run.

The details of the Shelf-Life Check are outside the scope of this article, but essentially, the injected code determines whether the application has expired and then sets the application-defined field (_isExpired) appropriately. The developer has written code in the application that disables the UI based on the value of the _isExpired field.
An application that can “remember” that it has been attacked can change its behavior in subsequent runs, throwing new roadblocks in front of attackers. In this case, the entire application is disabled after debugging is detected, and the attacker must figure out why before proceeding.
When a user changes tabs in the application, the MainWindows.tabCtrl_SelectionChanged() event handler fires. That method in turn calls the MainWindows.InjectADebugCheckHere() method. The DebugCheckAttribute on this method is configured to call yet another method, MainWindow.DebugNotification(), if a debugger is detected.
It shows the “Debugging Detected” dialog, which forces the attacker to select a “consequence”.

Consequences include:
Moving on to the “Expense Request” tab, the event handler for the Submit button is MainWindow.Submit_Click(), has a DebugCheckAttribute that is configured to hang the application one third of the time while submitting an expense request. Again, behavior that varies from run to run frustrates and deters attackers and their tools. In addition to the Action, the DebugCheckAttribute is also configured to call the MainWindows.DisableSubmit() method. So if the application doesn’t hang during submission, the “Submit” button is always disabled afterward, preventing further use of the feature in that session. Locking down sensitive features during debugging hinders attempts to exploit or corrupt them.
Last, Debug Check messages are always sent to a PreEmptive Analytics endpoint when debugging is detected. The application’s synthesized “License Key” is included in the message. This is done entirely through code injection and Dotfuscator configuration:
On Dotfuscator’s Settings tab, the “Send Debug Check Messages” property is set to “Yes”.

On the Instrumentation tab, there are Application and Business Attributes at the assembly level, with the BusinessAttribute using the Company Key for PreEmptive’s free Community Workbench.

The App.Main method has a SetupAttribute, configured to use the endpoint for the Community Workbench. It also tells the injected code to retrieve the application’s license key from the _licenseKey field.

This is another example of alerting, providing notification, and auditing of debugging attempts. In this case, the application’s debugging notifications are sent to the free Community Workbench [depreciated], and you can see a dashboard showing Debugging notifications here [depreciated].
Since the Professional Edition offers more functionality than the Community Edition, the CE configuration differs slightly. To summarize the differences in functionality:
These limitations somewhat restrict what can be done in the CE configuration for the sample application, but the good news is that there is still a set of reasonably effective defenses:
After building the application and running it through Dotfuscator, verify that the injected defenses are working. You can test them using the Visual Studio debugger.
First, we will test the startup behavior when the application is launched by the debugger. One quick way to get the Visual Studio debugger to launch an external application is via a C# project’s Debug properties: simply set the Start action as shown:

Once configured this way, pressing F5 or “Start Debugging” from the Debug menu will launch the protected version of the application under the VS Debugger. If the injected “Exit” action is working correctly, you won’t see much. The debugger will start, then stop again, and you will see a notice that the “program has exited” in the Debug Output window.
You can test the rest of the defenses by starting the application outside the debugger, then attaching the debugger to it via Visual Studio’s “Attach to Process” dialog. Try some of these tests with the debugger attached:
If your applications are worth protecting and you consider unauthorized debugging a threat, you should use Dotfuscator’s anti-debugging capabilities. When you do, keep in mind some of the general principles we’ve highlighted in this article:
To learn more: