Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PowerShell

Configuring Visual Studio for Obfuscation

4.96/5 (84 votes)
29 Oct 2016CPOL30 min read 252.2K   4.4K  
An introductory article that illustrates how to configure Visual Studio for Obfuscation

Table of Contents

  1. Introduction
  2. What gets obfuscated?
  3. How does an assembly get obfuscated?
  4. When does a project’s assembly get obfuscated?
  5. What type of assemblies are obfuscated?
  6. When and when not to obfuscate?
  7. The ObfuscatedRelease solution configuration
  8. Overview of the obfuscation workflow
  9. Projects in the sample solution
  10. Strong name key files
  11. Project settings
  12. Solution platforms
  13. Post-build events
  14. ConfuserEx
  15. How to test that your assembly has been obfuscated?
  16. Obfuscation related problems
  17. Requirements for building the sample solution
  18. History

Introduction

This is an introductory article about obfuscation in Visual Studio. It explains how to configure Visual Studio for obfuscation. It does not discuss the theory behind obfuscation or the effectiveness of obfuscation in protecting your intellectual property.

What Gets Obfuscated?

One of the questions I found posted in several forums asked what gets obfuscated, the source code or the assembly created when a project is built. The answer is the assembly created when a project is built. For example, one of the projects in the sample solution included with this article is named PrivateAssembly. When this project is built, then the output is an assembly named PrivateAssembly.dll. It is this assembly PrivateAssembly.dll which gets obfuscated.

How Does an Assembly Get Obfuscated?

An assembly is obfuscated using a program called an obfuscator. The obfuscator included with Visual Studio is the community edition of Dotfuscator. The sample solution included with this article does not use Dotfuscator but instead uses the obfuscator ConfuserEx, an open source project.

Assemblies can be obfuscated using a GUI program or a command line program - each has its use. I found the GUI program is useful as a learning tool whereas the command line program is useful in automating the build process.

Development on and support of the ConfuserEx obfuscator stopped as of July, 2016. However, most of the information in this article is applicable to other obfuscators that include a command line program.

When Does a Project’s Assembly Get Obfuscated?

In the sample solution, an assembly is obfuscated immediately after a project’s assembly has been built. More specifically, an assembly is obfuscated when a project’s post-build event is executed. A post-build event is a user supplied script that executes a sequence of commands. In the current context, the script executes commands that obfuscate a project’s assembly. When the requirements are very simple, then the script can be written using batch commands and batch files, otherwise my recommendation is to write PowerShell scripts. The sample solution uses PowerShell scripts.

What Types of Assemblies are Obfuscated?

The sample solution included with this article illustrates how to obfuscate private assemblies, strong named assemblies and delay signed assemblies. This article assumes you understand the difference between these three types of assemblies. In short, strong named assemblies are signed with the private token key from a cryptographic strong name key file, delay signed assemblies are signed with the public token key from a cryptographic strong name key file and private assemblies are not signed. Those of you who are new to obfuscation should read the article "Giving a NET Assembly a Strong Name" as it describes the difference between obfuscating a strong named assembly and obfuscating a delay signed assembly. Other articles worth reading are this and this.

When and When Not to Obfuscate?

By default, projects in a Visual Studio solution are configured with two solution configurations – the Debug solution configuration and the Release solution configuration. The question is, which of these configurations do you apply obfuscation to?

I have not come across any reason for obfuscating assemblies built under the Debug solution configuration. So when the Debug solution configuration is selected, then my tests (e.g., unit, integration, specification) are only run against the non-obfuscated assemblies.

On the other hand, under the Release solution configuration, I want to be able to build both non-obfuscated assemblies and obfuscated assemblies. The reason for this is if a problem occurs when using the obfuscated assemblies then I want to be able to test if the same problem occurs in the non-obfuscated assemblies. In other words, is the problem related to the process of obfuscation? So under the release configuration, my tests (e.g., unit, integration, specification) can be run against the non-obfuscated assemblies and the obfuscated assemblies. To do this means my Visual Studio solution must be configured so that I am able to selectively build non-obfuscated assemblies or obfuscated assemblies. The question is, how do you configure Visual Studio to do this, to selectively build non-obfuscated assemblies in one run and then build obfuscated assemblies in another run? The answer is to create a new solution configuration which I name ObfuscatedRelease.

The ObfuscatedRelease Solution Configuration

When a new project is created in Visual Studio, then Debug and Release solution configurations are automatically added to the project. For obfuscation purposes, what is needed is a new solution configuration that you must add to your Visual Studio solution. In the sample solution, the name of this new solution configuration is ObfuscatedRelease (see next figure). The Debug and Release solution configurations are selected when building non-obfuscated assemblies and the ObfuscatedRelease solution configuration is selected when building obfuscated assemblies.

Image 1

Figure 1

The decision regarding where and when to obfuscate an assembly actually occurs in a project’s post-build event. For example, in the sample solution, a post-build event obfuscates an assembly only if it determines that the ObfuscatedRelease solution configuration has been selected (see next listing). If the Debug or Release solution configurations are selected, then the project’s assembly is not obfuscated.

Listing 1
PowerShell
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate

rem commands to obfuscate the assembly go here

:lDoNotObfuscate

So the key to being able to selectively build obfuscated and non-obfuscated assemblies is to add a new solution configuration to your Visual Studio solution and then have a project’s post-build event obfuscate the project’s assembly only if this new solution configuration is selected.

A description of how to add a new solution configuration to your Visual Studio solution can be found here. When editing a new solution configuration, make certain that a project’s Configuration setting matches the Active solution configuration setting. For example, the next figure illustrates that the configuration for the PrivateAssembly project has incorrectly been set to Release instead of ObfuscatedRelease. This means that the assembly for the PrivateAssembly project will never be obfuscated.

Image 2

Figure 2

The next figure illustrates the configuration setting for the PrivateAssembly project is correctly set to ObfuscatedRelease.

Image 3

Figure 3

Overview of the Obfuscation Workflow

The next set of figures illustrates the basic workflow for obfuscating assemblies in the sample solution. Each figure shows the post build directory structure for the PrivateAssembly sample project when the solution configuration is set to ObfuscatedRelease and the solution platform is set to AnyCPU. The obfuscation workflows for the different sample projects are similar but be aware they are not exactly the same. For example, the workflow for the StrongNamedAssembly project installs its obfuscated assembly in the Global Assembly Cache (GAC) while this step does not occur for the PrivateAssembly and DelaySignedAssembly projects.

Step 1

Visual Studio builds the project PrivateAssembly and stores the NON-obfuscated PrivateAssembly.dll assembly in the ObfuscatedRelease directory (see the next figure).

Image 4

Figure 4

Step 2

Visual Studio runs the post-build event for the PrivateAssembly project. This creates an obfuscated copy of the PrivateAssembly.dll assembly in the Confused directory (see next figure). There are now two copies of the PrivateAssembly.dll assembly, a non-obfuscated copy in the ObfuscatedRelease directory and an obfuscated copy in Confused directory.

Image 5

Figure 5

Step 3

The post-build event copies the non-obfuscated PrivateAssembly.dll assembly in the ObfuscatedRelease directory to the NonObfuscatedAssemblyBackup directory (see next figure). There are now three copies of the PrivateAssembly.dll assembly, a non-obfuscated copy in the ObfuscatedRelease directory, a non-obfuscated copy in the NonObfuscatedAssemblyBackup directory and an obfuscated copy in the Confused directory.

Image 6

Figure 6

Step 4

The post-build event copies the obfuscated PrivateAssembly.dll assembly in the Confused directory to the ObfuscatedRelease directory (see next figure). There are now two copies of the obfuscated PrivateAssembly.dll assembly, one in the ObfuscatedRelease directory and one in the Confused directory. There is now only one copy of the non-obfuscated PrivateAssembly.dll assembly which is in the NonObfuscatedAssemblyBackup directory.

Image 7

Figure 7

Given there are now three copies of PrivateAssembly.dll, the question is which copy is referenced by other projects? The answer is the obfuscated copy of PrivateAssembly.dll in the ObfuscatedRelease directory. Visual Studio will automatically reference this copy so there is no additional configuration required on your part. Copies of PrivateAssembly.dll in the Confused and the NonObfuscatedAssemblyBackup directory are no longer used and can be deleted.

Projects in the Sample Solution

The sample solution contains three C# class libraries named PrivateAssembly, DelaySignedAssembly and StrongNamedAssembly and a C# console application named MyAppCS. The names of these projects indicate the type of assembly they build. The project StrongNamedAssembly builds a strong named assembly, the project DelaySignedAssembly builds a delay signed assembly and the project PrivateAssembly builds a private assembly. The next figure shows the dependencies between these projects. There are no special requirements for having these dependencies and they simply reflect how I chose to implement the projects. When the ObfuscatedRelease solution configuration is selected, then assemblies for these four projects are obfuscated. When the Debug and Release solution configurations are selected, then none of these assemblies are obfuscated.

Image 8

Figure 8

The application I am working on uses C++/CLI to call into C# assemblies. The sample solution therefore includes a C++/CLI console application named MyAppCPP to illustrate how C++/CLI code can call into obfuscated assemblies. The MyAppCPP project has the same dependencies as the C# console application that is illustrated in the above figure. When the ObfuscatedRelease solution configuration is selected, then the C# assemblies are obfuscated but the MyAppCPP assembly is not obfuscated. When the Debug and Release solution configurations are selected, then none of the assemblies are obfuscated.

Unit tests are provided for the C# class libraries to demonstrate that unit tests can also be run against obfuscated assemblies. When the ObfuscatedRelease solution configuration is selected, then the unit tests are run against the obfuscated assemblies. When the Debug and Release solution configurations are selected, then the unit tests are run against non-obfuscated assemblies.

To build and run the sample solution, Visual Studio must be started with administrative privileges. This is because the StrongNamedAssembly project installs its assembly in the GAC and doing so requires administrative privileges. A batch file is included with the sample solution that uninstalls the StrongNamedAssembly assembly from the GAC. This batch file should be run after you have finished working with the sample solution. The name of this batch file is UninstallAssembliesFromGAC.bat and it too must be run with administrative privileges. The file path of this batch file is:

Obfuscation\BatchFiles\UninstallAssembliesFromGAC.bat

Strong Name Key Files

Projects that build strong named assemblies and delay signed assemblies make use of cryptographic keys stored in what is referred to as strong name key files. The article "Giving a NET Assembly a Strong Name" describes how to generate strong name key files. Two strong name key files are included with the sample solution, one includes both the public and private key tokens (i.e., PublicPrivateKeys.snk) while the other includes only the public key token (i.e. PublicKey.snk). The file paths of these two key files are:

Obfuscation\StrongNameKeyFiles\PublicPrivateKeys.snk
Obfuscation\StrongNameKeyFiles\PublicKey.snk

A sample batch file is provided which illustrates how to create the strong name key files. The file path of this batch file is:

Obfuscation\StrongNameKeyFiles\createStrongNameKeyFiles.bat

As mentioned earlier, strong named assemblies are signed with the private key token, delay signed assemblies are signed with the public key token and private assemblies are not signed at all.

Project Settings

There are three particular settings you will want to pay attention to in the sample solution:

  1. a project’s Signing settings,
  2. the Copy Local setting for a referenced project and
  3. the Windows SDK directory path in the sample PowerShell script

A Project’s Signing Settings

The Signing tab for a project’s property settings is used to specify settings for signing an assembly. To display a project’s signing settings, on the main menu in Visual Studio, click [Project] [ProjectName Properties] and then click the Signing tab. The next figure illustrates the signing settings for the StrongNamedAssembly sample project. This figure shows that the assembly will be signed with the private key file PublicPrivateKeys.snk and the Delay sign only option is not checked. This means the project builds a strong named assembly.

Image 9

Figure 9

The Copy Local Setting

The second setting, Copy Local, is defined when a class library is referenced by another project. This setting is set to either True or False. For example, the next figure shows the class library DelaySignedAssembly is referenced by the class library PrivateAssembly. The DelaySignedAssembly assembly is NOT installed in the GAC and must be copied to the output directory of the PrivateAssembly project. So the Copy Local setting for the referenced DelaySignedAssembly project is set to True.

Image 10

Figure 10

The following tables show the Signing and Copy Local settings for the three types of assemblies.

Table 1 - Settings for a Private Assembly

Setting Value
Sign the assembly un-checked
Choose a strong name key file undefined
Delay sign only un-checked
Copy Local True

Table 2 - Settings for a Strong Named Assembly

Setting Value
Sign the assembly checked
Choose a strong name key file select a strong name key file containing a private key
Delay sign only un-checked
Copy Local False

Table 3 - Settings for a Delay Signed Assembly

Setting Value
Sign the assembly checked
Choose a strong name key file select a strong name key file containing ONLY the public key
Delay sign only checked
Copy Local True

The Windows SDK Directory Path

The third and last setting to define is the Windows SDK directory path in the sample PowerShell module Utility.psm1. This directory path specifies where the GAC utility program gacutil.exe and the strong name utility program sn.exe are located. The file path of the Utility.psm1 module is:

Obfuscation\PowerShellScripts\Utility\Utility.psm1

This module defines the variable $_windowsSdkDirectory (see line 270) as follows:

PowerShell
$_windowsSdkDirectory = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools\"

You will only need to change this directory path if the Windows SDK for VS2015 is installed in a different directory on your computer.

Solution Platforms

The sample solution is configured for four platforms: Any CPU, Win32, x86 and x64. The C# console application and unit tests should be run under the Any CPU, x86 or x64 platform configurations. The C++ console application should be run under the Win32 or x64 platform configurations.

Post-Build Events

The sequence of operations performed in the post-build event for the sample projects varies according to the type of assembly (private, strong named or delay signed) being obfuscated. The following sections review the post-build events for each of the three types of assemblies and the PowerShell scripts they call.

PowerShell Scripts

When I first started working with obfuscation, I wrote the post-build events using batch commands and batch files. But as is described in the section The ConfuserEx project file template, I subsequently decided to incorporate a template file into the post-build events. The problem is that batch commands and batch files are not well suited to performing the operations involved in using this template file - it can be done but the implementation is awkward, error prone and extremely difficult to debug. On the other hand, this is something that can be readily accomplished using PowerShell. As a result, you will find all post-build events in the sample projects call PowerShell scripts. The next figure shows the PowerShell scripts and module included in the sample solution.

Image 11

Figure 11

The next table describes these PowerShell scripts and module.

Table 4 - Powershell Scripts/modules in the Sample Solution

GACAssembly script - used to install and uninstall a project’s assembly in the GAC
ObfuscateAssembly script - used to obfuscate a project’s assembly
SetAssemblyStrongNameVerificationState script - used to disable strong name verification for a delay signed assembly
SignAssembly script - used to re-sign an obfuscated strong name assembly
Utility module - a set of PowerShell functions used by the above PowerShell scripts

The PowerShell scripts included with the sample solution perform minimal error checking so as to make them easier for the reader to understand them.

Post-Build Event for a Private Assembly

The next listing shows the post-build event in the PrivateAssembly sample project.

Listing 2
PowerShell
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe

if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate

rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" "$(SolutionPath)" "$(TargetPath)" 
if errorlevel 1 exit 1

:lDoNotObfuscate

The IF statement checks which solution configuration has been selected in Visual Studio. If the solution configuration is not set to ObfuscatedRelease, then the project’s assembly is not obfuscated, otherwise the project’s assembly is obfuscated. The assembly is obfuscated by calling the PowerShell script ObfuscateAssembly.ps1.

The main steps in the obfuscation workflow are:

  1. Create a ConfuserEx project file using the ConfuserEx template file.
  2. Obfuscate the project’s assembly and store the obfuscated assembly in the Confused directory.
  3. Copy the non-obfuscated assembly to the NonObfuscatedAssemblyBackup directory.
  4. Copy the obfuscated assembly to the project’s output directory.

If you open ObfuscatePrivateAssembly.ps1, then you will see it calls four functions (see line numbers 160 to 163) that perform the above four steps (see next listing).

Listing 3
CreateConfuserExProjectFileFromTemplateFile
ObfuscateProjectAssembly
BackupProjectOutputDirectory
CopyObfuscatedAssemblyToProjectOutputDirectory

Post-Build Event for a Strong Named Assembly

The following listing shows the post-build event in the StrongNamedAssembly sample project.

Listing 4
PowerShell
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe
 
if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate

rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" 
"$(SolutionPath)" "$(TargetPath)"
if errorlevel 1 exit 1

rem re-sign the obfuscated assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\SignAssembly\SignAssembly.ps1
set keyFilePath=$(ProjectDir)PublicPrivateKeys.snk
"%powerShellFilePath%" -File "%powerShellScriptPath%" 
"$(TargetPath)" "%keyFilePath%" "$(PlatformName)"
if errorlevel 1 exit 1

:lDoNotObfuscate

rem remove previously installed versions of the assembly in the GAC
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\GACAssembly\GACAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" 
"$(TargetName)"  "$(PlatformName)"

rem now install current assembly in GAC
"%powerShellFilePath%" -File "%powerShellScriptPath%" 
"$(TargetPath)"  "$(PlatformName)" -InstallAssemblyInGAC
if errorlevel 1 exit 1

First, this script only obfuscates the project’s assembly if the solution configuration is set to ObfuscatedRelease. Second, if the assembly is obfuscated, then it is resigned with the private key token from the strong name key file PublicPrivateKeys.snk. Third, if this project’s assembly was previously installed in the GAC, then this previous version is uninstalled from the GAC. Finally, the current version of the project’s assembly is installed in the GAC.

Post-Build Event for a Delay Signed Assembly

The following listing shows the post-build event in the DelaySignedAssembly sample project.

Listing 5
PowerShell
set powerShellFilePath=%systemroot%\sysnative\windowspowershell\v1.0\powershell.exe

rem Disable strong name verification of the project’s delay signed assembly.
rem Note: make certain to enable strong name verification of the project assembly once this assembly is ready for deployment.
set powerShellScriptPath=$(SolutionDir)
PowerShellScripts\SetAssemblyStrongNameVerificationState\SetAssemblyStrongNameVerificationState.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" 
"$(TargetPath)" "$(PlatformName)" -DisableStrongNameVerification 
if errorlevel 1 exit 1

if /I $(ConfigurationName) neq ObfuscatedRelease goto lDoNotObfuscate

rem obfuscate the assembly
set powerShellScriptPath=$(SolutionDir)PowerShellScripts\ObfuscateAssembly\ObfuscateAssembly.ps1
"%powerShellFilePath%" -File "%powerShellScriptPath%" "$(SolutionPath)" "$(TargetPath)" 
if errorlevel 1 exit 1

:lDoNotObfuscate

First, this post-build event calls SetAssemblyStrongNameVerificationState.ps1 to instruct the runtime to disable strong name verification for this delay signed assembly. Second, it obfuscates the project’s assembly if the solution configuration is set to ObfuscatedRelease. Note, the sample project does not install the obfuscated, delay signed assembly in the GAC. My attempts at doing so generated an error that I was unable to resolve. I did not investigate this matter any further as I do not use delay-signed assemblies in my own work.

Once a delay signed assembly has been fully tested, then it will be given to someone who has access to the private key token, signed with the private key token and then added to the deployment project so it can be installed in the GAC at deployment time.

ConfuserEx

The sample solution uses the obfuscator named ConfuserEx. Most readers will not know anything about ConfuserEx so I thought it would be helpful to address a few points about how it is used in the sample solution.

The ConfuserEx Project File

In Visual Studio, various settings for each project are stored in file that has an extension such as CSPROJ for C# and VCXPROJ for C++. Similarly, ConfuserEx stores settings in a project file that has the extension CRPROJ. The ConfuserEx project file specifies what assemblies are to be obfuscated, how to obfuscate the assemblies and where the obfuscated assemblies are to be stored.

The easiest way to create a ConfuserEx project file is using the ConfuserEx GUI program. The ConfuserEx binaries are not included in the sample solution but will be automatically installed the first time that the sample solution is built. After the sample solution has been built, then the file path to the GUI will be:

Obfuscation\packages\ConfuserEx.Final.1.0.0\tools\ConfuserEx.exe

The ConfuserEx project file is passed as an argument to the ConfuserEx command line program. The file path to the command line program is:

Obfuscation\packages\ConfuserEx.Final.1.0.0\tools\Confuser.CLI.exe

The ConfuserEx project file can specify one or more of your assemblies that are to be obfuscated. The sample solution adopts the approach where each ConfuserEx project file specifies a single assembly that is to be obfuscated. This means the sample solution has to work many different ConfuserEx project files. The next section will make this point clear.

The ConfuserEx Project File Template

This section explains the motivation behind using a template of a ConfuserEx project file. The use of this template is specific to the sample solution provided with the article. You will not find any reference to this template file on the ConfuserEx GitHub site.

The sample solution is configured with three different solution configurations (i.e., Debug, Release, ObfuscatedRelease) and four different solution platforms (i.e., any CPU, Win32, x86, x64). When a project in the sample solution is built, then the project output directory is determined in part by a combination of the selected solution configuration and the solution platform. More specifically, the output from each C# project can be stored in one of 12 (i.e., 3 configurations x 4 platforms) different directories, depending on which solution configuration and solution platform have been selected. For example, the next listing shows some of the output directories for the sample project PrivateAssembly.

Listing 6
Obfuscation\Projects\PrivateAssembly\bin\Debug 
Obfuscation\Projects\PrivateAssembly\bin\Release
Obfuscation\Projects\PrivateAssembly\bin\ObfuscatedRelease
Obfuscation\Projects\PrivateAssembly\bin\x86\Debug
Obfuscation\Projects\PrivateAssembly\bin\x86\Release
Obfuscation\Projects\PrivateAssembly\bin\x86\ObfuscatedRelease
Obfuscation\Projects\PrivateAssembly\bin\x64\Debug
Obfuscation\Projects\PrivateAssembly\bin\x64\Release
Obfuscation\Projects\PrivateAssembly\bin\x64\ObfuscatedRelease

A ConfuserEx project needs to know which one of these 12 directories contains the assembly that will be obfuscated. One way of accomplishing this is to use the GUI program to create 12 different ConfuserEx project files where each ConfuserEx project files identifies one of the 12 different project output directories. Indeed, this is what I did when I first started working with ConfuserEx. But this can quickly become a tedious and error prone process. I needed an easier way to create the many ConfuserEx project files. So I compared the contents of each of the 12 different ConfuserEx project files and found all files had the same content except for a few specific setting which varied between them. For example, the next listing shows the content in the first set of ConfuserEx project files I was working with. The text identified by “XXX” varied between the project files, but all other content was the same.

Listing 7
XML
<project outputDir="XXX" baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false" />
<module path="XXX" />
</project>

It was immediately clear that instead of working with 12 different ConfuserEx project files (and then multiplying this by the number of C# projects) that I could create a template file which contained the content common to all ConfuserEx project files. Then, in a project’s post-build event, my PowerShell script could generate a ConfuserEx project file from the template file and supply the settings that were specific to the project being built.

The file path of the single template file that is shared by all sample C# projects is:

Obfuscation\PowerShellScripts\ObfuscateAssembly\ConfuserEx.Template.crproj

A ConfuserEx project file is created from the ConfuserEx.Template.crproj template file when a post-build event runs the PowerShell script ObfuscateAssembly.ps1. The ConfuserEx project file is saved in the C# project’s output directory (i.e., in the same directory where the C# project’s assembly has been built).

Each C# project should create its own ConfuserEx project file and a ConfuserEx project file should not be shared between C# projects. This rule helps to avoid a potential resource conflict which can occur if multiple C# projects were to share the same ConfuserEx project file. By shared, I mean that each C# project uses the same file path (i.e., same directory and same filename) to create a ConfuserEx project file. Now, a shared or common ConfuserEx project could be used as long as multiple C# projects do not attempt to create this ConfuserEx project file at the same time. But if multiple C# projects create this ConfuserEx project file at the same time, then this means one C# project overwrites the contents of the ConfuserEx project file that are required by another C# project. Visual Studio can build projects in parallel so this resource conflict is almost inevitable. This resource conflict is avoided by having each C# project creates its own ConfuserEx project file.

Those of you with a keen eye will note that the sample solution could successfully use a single, shared ConfuserEx project file and that the resource conflict described in the previous paragraph will not occur. This is because the dependencies in the sample projects forces Visual Studio to build the projects in sequence or one at a time. So at any point in time, a single, shared ConfuserEx project file would only be written to by one sample C# project. But the sample projects do not to this. Just to be clear, the sample projects do not share a ConfuserEx project file. Instead, each sample project creates its own ConfuserEx project file.

A single, shared TEMPLATE file is used to create the ConfuserEx project files. This template file is a read-only file so it can be shared by all C# sample projects without concern for a resource conflict. The use of a single template file makes the assumption that the same obfuscation settings are applied to all assemblies. If this is not true for your requirements, then you will need to come up with another approach to creating the ConfuserEx project files.

How to Test that Your Assembly Has Been Obfuscated?

Now that you have configured Visual Studio for obfuscation, how can you confirm that your assembly is being obfuscated?  First, when the obfuscator is executed in a post-build event, then it should log information in the Visual Studio output window. So if you do not see this information in the Visual Studio output window then the obfuscator has not been executed and the assembly has not been obfuscated. Second, you can use the program ILDASM.EXE to view human-readable information about an assembly. If ILDASM displays such information then the assembly has not been obfuscated. On the other hand, if ILDASM is unable to display such information, then the assembly has been obfuscated.

Obfuscation Information in the Visual Studio Output Window

When the obfuscator is executed in a project’s post-build event, then it should log information to the Visual Studio output window. For example, the next listing illustrates the first few lines of information that is logged in the Visual Studio output window when ConfuserEx begins to obfuscate the PrivateAssembly.dll assembly.

Listing 8
2>------ Build started: Project: PrivateAssembly, Configuration: ObfuscatedRelease x86 ------
2>  PrivateAssembly -> F:\Visual Studio 2015\Projects\Obfuscation\Projects\PrivateAssembly\bin\x86\ObfuscatedRelease\PrivateAssembly.dll
2>   [INFO] ConfuserEx v1.0.0 Copyright (C) Ki 2014
2>   [INFO] Running on Microsoft Windows NT 6.1.7601 Service Pack 1, 
     .NET Framework v4.0.30319.42000, 64 bits
2>  [DEBUG] Discovering plugins...
2>   [INFO] Discovered 10 protections, 1 packers.
2>  [DEBUG] Resolving component dependency...

As a reminder, obfuscation is only performed when the solution configuration is set to ObfuscatedRelease. So setting the solution configuration to either Debug or Release means the obfuscator information illustrated in the above listing will not appear in the Visual Studio output window.

Using ILDASM.EXE to Disassemble an Assembly

ILDASM.exe is a program that is installed along with Visual Studio. This program is used to decompile (i.e., reverse engineer) an assembly and show information about the assembly in a format that you can read. ILDASM can be used to quickly check whether or not your assembly has been obfuscated. As a general statement, if ILDASM is able to display information about your assembly then your assembly has not been obfuscated. Conversely, if ILDASM is not able to display information about your assembly, then your assembly has been obfuscated.

This old blog entry describes where you can find the ILDASM program on your computer and this old tutorial illustrates the information that it displays.

The next figure illustrates the information ILDASM displays for the non-obfuscated PrivateAssembly.dll assembly.

Image 12

Figure 12

The next figure illustrates what happens when ILDASM attempts to read the obfuscated PrivateAssembly.dll assembly. ILDASM is unable to display information from the obfuscated assembly and will instead display one or more error messages such as that illustrated in the next figure.

Image 13

Figure 13

You should expect to encounter problems related to obfuscation and a general approach I use to resolving them is as follows:

  • Step 1: Set Visual Studio to the Debug solution configuration (i.e., where obfuscation is completely disabled) and test if the problem can be replicated. If the problem can be replicated, then it is not related to obfuscation. On the other hand, if the problem cannot be replicated when obfuscation has been disabled, then this is a good indication the problem is related to obfuscation. In this case, proceed to Step 2.
  • Step 2: Set Visual Studio back to the obfuscation solution configuration (e.g., to ObfuscatedRelease in the sample solution). Next, disable all obfuscation settings in the obfuscator project file and attempt to replicate the problem. If all obfuscation settings have truly been disabled, then you should not be able to replicate the problem. In this case, proceed to the next step.
  • Step 3: Enable a first obfuscation setting and attempt to replicate the problem. If the problem cannot be replicated, then proceed to the next step.
  • Step 4: Enable a second obfuscation setting and attempt to replicate the problem. If the problem cannot be replicated, then proceed to the next step.
  • Step 5: Continue enabling obfuscation settings, one at a time, until you are able to replicate the problem. When you are finally able to replicate the problem, then the last setting you enabled is the one that is causing the problem.

The next section provides an example of how the trouble shooting steps just described were used to resolve a particular compiler error.

I received an obscure fatal error C1001 (see next listing) when I first attempted to build the sample C++/CLI console application under the ObfuscatedRelease solution configuration.

Listing 9
3>MyAppCPP.cpp(38): fatal error C1001: An internal error has occurred in the compiler. 
3> (compiler file ’f:\dd\vctools\compiler\cxxfe\sl\p1\c\cpimport.cpp’, line 17137)
3> To work around this problem, try simplifying or changing the program 
   near the locations listed above. 
3> Please choose the Technical Support command on the Visual C++ 
3> Help menu, or open the Technical Support help file for more information

The problem did not occur under either the Debug or Release solution configurations. The problem only occurred when building under the ObfuscatedRelease solution configuration. This indicated the problem was related to the process of obfuscation.

Next, I used the ConfuserEx GUI program to learn how to disable specific settings in the ConfuserEx project file. The next listing shows an example of the sample ConfuserEx template file where all obfuscation settings have been disabled. A ConfuserEx setting (more correctly referred to as a rule protection) is disabled by adding the attribute action="remove" to the setting.

Listing 10
XML
<project outputDir="XXX " baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false">
	<protection id="anti ildasm" action="remove"/>
	<protection id="anti tamper" action="remove"/>
	<protection id="constants" action="remove"/>
	<protection id="ctrl flow" action="remove"/>
	<protection id="anti dump" action="remove"/>
	<protection id="anti debug" action="remove"/>
	<protection id="invalid metadata" action="remove"/>
	<protection id="ref proxy" action="remove"/>
	<protection id="resources" action="remove"/>
	<protection id="rename" action="remove"/>
</rule>
<module path="XXX" />
</project>

I was unable to reproduce the C1001 fatal error when all obfuscation settings were disabled. So I enabled the first setting "anti ildasm" by changing the value for the action attribute from remove to add (see next listing). I was still not able to replicate the compiler error.

Listing 11
XML
<project outputDir="XXX " baseDir="XXX" xmlns="http://confuser.codeplex.com">
<rule pattern="true" preset="normal" inherit="false">
	<protection id="anti ildasm" action="add" />
	<protection id="anti tamper" action="remove" />
	<protection id="constants" action="remove" />
	<protection id="ctrl flow" action="remove" />
	<protection id="anti dump" action="remove" />
	<protection id="anti debug" action="remove" />
	<protection id="invalid metadata" action="remove" />
	<protection id="ref proxy" action="remove" />
	<protection id="resources" action="remove" />
	<protection id="rename" action="remove" />
</rule>
<module path="XXX" />
</project>

I continued enabling the other settings, one at a time, until enabling the ref proxy setting reproduced the C1001 error. For this reason, if you examine the ConfuserEx template file in the sample solution, you will find the ref proxy rule protection has been disabled. You will also find several other settings have been disabled in the ConfuserEx template file and this was required in order to successfully build all the sample projects. I used a trial-and-error approach to determine which ConfuserEx settings could be enabled and which settings had to be disabled. If you are working with a commercial obfuscator, then a better approach might be to contact customer support to determine if there is another solution other than simply disabling obfuscation settings.

Reflection

When you start reading about obfuscation, then you will find forum posts that state reflection and obfuscation do not play well together. The sample console application Reflection illustrates a few of the problems that can occur when obfuscating your assemblies. This console application uses reflection to obtain information about the types, classes and methods found in the assembly PrivateAssembly.dll. The next listing shows the program output generated for the non-obfuscated assembly. The output contains the name of the IMyClass interface, the MyClass class and its method names such as ConvertStringToUpperCase.

Listing 12
============================================================
Assembly name: PrivateAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken 
=null
type name: PrivateAssembly.IMyClass
method name: ConvertStringToUpperCase

type name: PrivateAssembly.MyClass
method name: get_MyString
method name: set_MyString
method name: get_MyInt
method name: set_MyInt
method name: get_MyDouble
method name: set_MyDouble
method name: ConvertStringToUpperCase
method name: ToString
method name: Equals
method name: GetHashCode
method name: GetType

The next listing shows the output generated for the obfuscated assembly. The output contains nothing more than few method names and therein lies the problem. Specifically, had I written the Reflection console application to search for types with the literal names of ‘IMyClass’ and ‘MyClass’, then the application would have failed when attempting to read from the obfuscated assembly. This is because types with these literal names simply do not exist in the obfuscated assembly. The lesson to be learned is that obfuscation can introduce bugs into your applications that use reflection. I have little experience with either and have no advice to offer other than to say you need to exercise caution when using reflection and obfuscation together.

Listing 13
Assembly name: PrivateAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken
=null
type name: ????????????????????????????????????????+????????????????????????????????????????
method name: Equals
method name: GetHashCode
method name: ToString
method name: GetType


type name: ????????????????????????????????????????+???????????????????????????????????????
method name: Equals
method name: GetHashCode
method name: ToString
method name: GetType

Debugging the Delay Signed Assembly

My attempts at installing the obfuscated DelaySignedAssembly.dll assembly in the GAC generated the error message, "Failure adding assembly to the cache: Strong name signature could not be verified. Was the assembly built delay-signed?" I do not use delay-signed assemblies in my own work. So after a short but unsuccessful effort at trouble shooting, I did not invest any more time in attempting to resolve this error.

Obfuscated, delay-signed assemblies can still be tested and debugged by treating them just as you would treat a private assembly. Specifically, make certain the obfuscated, delay signed assembly is copied to the same directory as your application. So when a delay signed assembly is referenced by another project then confirm its Copy Local setting is set to True. For example, when the sample console application MyAppCS is built, then the DelaySignedAssembly.dll assembly project is copied to the output directory for the MyAppCS project. It is that simple.

Requirements for Building the Sample Solution

  • Visual Studio 2015
    Visual Studio must be run/started with administrative privileges. This is because the StrongNamedAssembly project installs the project’s assembly in the GAC and administrative privileges are required to do this.

    Read this article if you get the following error message the first time you attempt to build the sample solution:
    PowerShell
    GACAssembly.ps1 cannot be loaded. 
    The file ...\Obfuscation\PowerShellScripts\GACAssembly\GACAssembly.ps1 
    is not digitally signed. 
    You cannot run this script on the current system.		
  • ConfuserEx
    ConfuserEx is an open source obfuscator. ConfuserEx will automatically be installed the first time you build the sample solution. As was mentioned, development and support for ConfuserEx stopped in July, 2016.

  • NUnit
    The sample solution includes NUnit unit tests. NUnit will automatically be installed the first time you build the sample solution.

  • NUnit3TestAdapter
    The sample solution includes the NUnit3TestAdapter v3.4.1 test runner. NUnit3TestAdapter will automatically be installed the first time you build the sample solution.

    The unit test sample projects must first be built before NUnit3TestAdapter can discover and list the sample unit tests. For an explanation of how to run the unit tests, read this document. You can also find other explanations by doing a Google search with the key words "Visual Studio Test Explorer" or looking for a YouTube video using the same search key words.

    If you upgrade to NUnit3TestAdapter v3.5.0 and encounter the problem described here, then the work around solution I used was to downgrade back to v3.4.1.

  • PowerShell V4
    The sample PowerShell scripts were written and tested against PowerShell V4. PowerShell V4 is installed by installing the Windows Management Framework 4.0. Make certain PowerShell V4 or greater is installed on your computer.

  • PowerShell Tools for VS2015
    I recommend you install the PowerShell Tools for Visual Studio as it allows you to write and debug PowerShell scripts in Visual Studio itself. Alternatively, you can use any PowerShell editor (e.g., PowerShell ISE, PowerShell Plus Professional and PowerGUI) for writing and debugging your PowerShell scripts.

  • An internet connection
    The sample solution does not include binaries for ConfuserEx, NUnit and NUnit3TestAdapter. These binaries will automatically be installed the first time you build the sample solution (this is a feature of NuGet) and is why you will need the internet connection. So if you do not have an internet connection, then these binaries will not be installed and you will not be able to successfully build the sample solution.

History

  • October, 2015
    • Published the original article illustrating how to obfuscate private assemblies using ConfuserEx v0.5.0
  • November, 2015
    • Updated the article to illustrate how to obfuscate private, strong named and delay signed assemblies.
  • January, 2016
    • Updated the post-build event in the StrongNamedAssembly project to correct compile/runtime GAC related errors
    • Updated the sample solution from VS2013 to VS2015
  • October, 2016
    • Updated the sample solution to use ConfuserEx v1.0.0 and NUnit v3.5.0
    • Added the test runner NUnit3TestAdapter v3.4.1.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)