Building and Debugging
Dotfuscator can combine multiple input assemblies into one or more output assemblies. Assembly linking can be used to make your application even smaller, especially when used with Renaming and Removal, and can simplify deployment scenarios.
For example, if you have input assemblies A, B, C, D, and E, you can link assemblies A, B, and C and name the result F. At the same time, you can also link D and E and name the result G. The only rule is that you can't link the same input assembly into multiple output assemblies.
The linking feature is fully integrated with the rest of Dotfuscator, so in one step, you can obfuscate, remove unused types, methods, and fields, and link the result into one assembly.
If you have other input assemblies that reference the assemblies you are linking together, Dotfuscator will transparently update all the assembly and type references so the output assemblies will correctly work together.
Linking is not supported for Managed C++ assemblies.
Linking is not supported in Automatic Input Management mode.
Dotfuscator does not update the assembly name in Pack URIs in XAML. Therefore, linking of applications with any XAML should generally be avoided.
Linking is only supported for the classic .NET Framework.
Incremental obfuscation allows you to keep a consistent naming scheme across Dotfuscator runs for your application. By referencing a name mapping file, types and members can be consistently renamed over time. Consistent renaming is desirable in multiple scenarios, including the redistribution of a subset of files that constitute a dependent group, and sequential obfuscation of assemblies in a resource constrained environment. Appropriate utilization of this feature offers the additional benefit of more rapid obfuscation, when only some of the assemblies in an application need to be redistributed.
Consider a scenario where you have used Dotfuscator on your application and distributed that application to your customers. Now you would like to make changes to one of the assemblies and provide it as an update. A naive re-execution of Dotfuscator upon your application would likely rename your legacy classes and methods in a different way, forcing you to redistribute the entire application to your customers. Dotfuscator's incremental obfuscation allows you to keep the same names so you can release the changed assembly.
Incremental obfuscation is useful in sequential build scenarios by allowing the obfuscation of large applications to be broken down into smaller, more manageable groups of assemblies.
A hypothetical application consisting of three files,
C could be built as follows:
C.dll could be obfuscated initially, then
B.dll could be incrementally obfuscated using the map file from
C.dll, and finally
A.exe could be obfuscated using the map file from
Incremental obfuscation requires an input mapping file containing the names that need to be reused. The format is the same as the output mapping file that Dotfuscator produces after every run. A best practice is to save a copy of the output mapping file in a safe place (e.g. in version control) for every released build of your application. The file can then be used as an input mapping file if an incremental update should ever be necessary.
When performing a run using incremental obfuscation, Dotfuscator must have access to all the application's assemblies, although it is not required that all the assemblies be included in the config. They only need to be discoverable by the same probing rules used by Dotfuscator to locate referenced assemblies.
Debugging Obfuscated Code
One major drawback of obfuscation is that the task of maintaining and troubleshooting an obfuscated application becomes more difficult. In particular, Control Flow obfuscation significantly hinders debugging, because the ordering of instructions in the assembly no longer matches the ordering of the source language's commands. Renaming obfuscation complicates debugging further and also causes stack traces - such as those reported by customers of the released app - to use the obfuscated names.
Thankfully, Dotfuscator has features that allow you to debug your obfuscated app and interpret its stack traces.
Dotfuscator has the ability to emit debugging symbols - e.g., PDB files - for the protected assemblies. Using these symbols, you can use a debugger to step through an obfuscated assembly and hit breakpoints against the original source code.
This feature is enabled by default for new Dotfuscator config files, allowing you to integrate Dotfuscator into a Visual Studio project and keep debugging your app in Visual Studio, even when building in an obfuscated configuration (e.g. Release).
Translating Stack Traces
Stack traces emitted by obfuscated assemblies will use obfuscated names, rather than the original source names, which can make it difficult to diagnose issues reported by customers. The Dotfuscator Config Editor includes a tool for decoding obfuscated stack traces back into equivalent stack traces using the original names. For developers who do not have Dotfuscator installed, PreEmptive Solutions provides a standalone tool, Lucidator, which can also perform this translation.
Both tools require the renaming map file produced by Dotfuscator during obfuscation, so you will need to privately archive this file with every release of your app in order to later translate obfuscated stack traces.
Obfuscating Strong Named Assemblies
Strong named assemblies are digitally signed. This allows the runtime to determine if an assembly has been altered after signing. The signature is an SHA1 hash signed with the private key of an RSA public/private key pair. Both the signature and the public key are embedded in the assembly's metadata.
Since Dotfuscator modifies the assembly, it is essential that signing occur after running the assembly through Dotfuscator.
Dotfuscator handles this step as part of the obfuscation process.
Automatically Re-signing after Obfuscation
Dotfuscator automatically re-signs strongly named assemblies after obfuscation, eliminating the need for manual steps after obfuscation. Dotfuscator both re-signs your already signed assemblies, and completes the signing process on delay signed assemblies.
Re-signing Strongly Named Assemblies
As part of the build process, Dotfuscator re-signs assemblies that are already strongly named. You can tell Dotfuscator explicitly where to find the public/private key pair, or you can rely on a location specified by custom attributes on the input assembly (e.g. System.Reflection.AssemblyKeyFileAttribute).
The following example shows an XML config file fragment that sets up resigning with an explicit key file. Any key file specified via custom attribute is not used.
Re-signing with Explicit Key File:
<signing> <resign> <option>dontuseattributes</option> <key> <file dir="c:\temp" name="key.snk" /> </key> </resign> ... </signing>
Finishing Signing Delay Signed Assemblies
If an input assembly is delay signed, Dotfuscator can finish the signing process. Tell Dotfuscator where to locate the private key required to complete the signing.
The following example shows an XML config file fragment that sets up delay signing with an explicit key file.
<signing> ... <delaysign> <key> <file dir="c:\temp" name="key.snk" /> </key> </delaysign> </signing>
Dotfuscator allows you to specify programs that run before and after its build sequence.
Pre Build Event
In its build process, Dotfuscator executes the program specified by the pre build event before it does anything else with your input assemblies.
Post Build Event
Dotfuscator executes the program specified by the post build event at the very end of its build process. You can tell Dotfuscator to execute the program only when the build succeeds, only when the build fails, or all the time. In addition, you can tell Dotfuscator to run the program once for each output module.
Build Event Properties
The Dotfuscator build engine exposes several properties that you can use when configuring build events:
|dotf.destination||Path to the destination directory.|
|dotf.inputmap.xml||Full path and filename to the input map file if specified.|
|dotf.outputmap.xml||Full path and filename to the output map file if specified.|
|dotf.removal.xml||Full path and filename to the XML removal file if specified.|
|dotf.config.file||Full path to the current config file.|
|dotf.current.out.module||Full path to the current output module. Used in the post build event when the program is called for each output module.|
|dotf.current.in.module||Full path to the current input module. Used in the post build event when the program is called for each output module.|
You can also reference external properties (environment variables or properties passed on the command line using the
-p option) and user defined config properties in your build events.
Finding External Tools
Dotfuscator uses ILdasm and ILasm to process assemblies. ILdasm is the .NET intermediate language (IL) disassembler, and ILasm is the IL assembler. Dotfuscator can obtain these tools in two ways:
Via .NET Framework. If Dotfuscator is running on Windows, ILdasm can be provided by an installed .NET Framework SDK, and ILasm can be provided by an installed .NET Framework runtime. These tools are found automatically if present, using well-known install locations and registry keys.
✅ These tools support certain additional features.
❌ These tools do not run on macOS or Linux; they only run on Windows.
❌ These tools do not support assemblies targeting newer frameworks, such as .NET Core 3.x and .NET 5.
Via NuGet. Dotfuscator can use NuGet to obtain both ILdasm and ILasm. The retrieval and use of these packages is handled automatically as part of the build process.
There are environment variables available for configuring the NuGet source and credentials if the default NuGet source is unavailable.
DOT_ILTOOLS_CORE_NUGETFEEDsets the NuGet source feed.
DOT_ILTOOLS_CORE_NUGET_PASSWORDset the credentials for the NuGet source feed.
❌ These tools do not support certain additional features.
✅ These tools run on macOS and Linux, as well as Windows.
✅ These tools support assemblies targeting newer frameworks, such as .NET Core 3.x and .NET 5.
Normally, Dotfuscator chooses the best tools for each assembly automatically, based on the assembly's target framework. However, you may override this behavior with specially-named properties that instruct Dotfuscator to use specific ILdasm and ILasm executables on the filesystem.
The full algorithm that Dotfuscator uses to find ILdasm and ILasm for each input assembly is as follows:
Dotfuscator determines the metadata version of the assembly.
In most cases, this is
However, assemblies targeting older versions of the Windows-only .NET Framework may have a lower version number. For example, an assembly targeting .NET Framework 3.5 could have a metadata version of
For diagnostic purposes, you can determine the metadata version of an assembly by using ILdasm yourself. Look for the
// Metadata version:comment (if you use the graphical user interface, this comment appears in the "MANIFEST" section).
Dotfuscator looks at user-specified properties related to the metadata version found in step 1. If any relevant properties are defined, Dotfuscator will use the specified executables instead of the tools it would normally use based on the rules in the following steps.
If Dotfuscator is running on macOS or Linux, it will get the tools from NuGet and stop the algorithm at this step. Steps 4 and later of this algorithm only apply when Dotfuscator is running on Windows.
(Rationale: The .NET Framework tools can only run on Windows, so only the tools found via NuGet are compatible with macOS and Linux.)
If the assembly targets a runtime which supports default interface methods, Dotfuscator will get the tools from NuGet. Target frameworks that fall into this category include:
- .NET 5.0 and later
- .NET Core 3.0 and later
- .NET Standard 2.1
- Xamarin Android
- Xamarin iOS
(Rationale: Default interface methods are a breaking runtime change introduced in .NET Core 3.0. The .NET Framework tools consider such methods to be invalid and will not process assemblies using this feature correctly. Thus, Dotfuscator must use the newer tools available via NuGet.)
If the assembly targets a runtime which is Windows-exclusive, Dotfuscator will use the tools from .NET Framework. Target frameworks that fall into this category include:
- .NET Framework 4.x and earlier
- Universal Windows Platform (UWP)
(Rationale: Building assemblies for these frameworks typically require the .NET Framework SDK. Dotfuscator's requiring of these tools also prevents an unexpected loss of features for customers using these frameworks.)
Otherwise, Dotfuscator will use the tools from .NET Framework if they are present, or get the tools from NuGet if the .NET Framework tools are not present. Target frameworks that fall into this category include:
- .NET Core 2.x and earlier
- .NET Standard 2.0 and earlier
- Portable Class Libraries (PCLs)
(Rationale: Either set of tools can be used for these frameworks, but the .NET Framework tools support more features and thus are preferred.)
For help with the errors that occur when Dotfuscator cannot find one of these tools, see Troubleshooting.
Features Requiring .NET Framework SDK
Because Dotfuscator relies on the ILdasm and ILasm tools to process assemblies, certain features may be limited depending on the versions of these tools available. See the section above for how Dotfuscator chooses these versions.
The following features are only available when Dotfuscator is using the Windows-exclusive .NET Framework SDK:
- Debugging Symbols (e.g.,
.pdbfiles), regardless of the debugging option.
- Native Win32 resource files embedded in assemblies (e.g., Windows file versions, application icons).
Managed resources (e.g.,
.resx files) are supported in all scenarios.
The table below summarizes the support for these features based on the operating system Dotfuscator is running on and the target app type.
|OS||Target App Type||Debugging Symbols||Native resources||Managed resources|
|Windows||.NET 5.0 and later||❌||❌||✅|
|Windows||.NET Core 3.0 and later||❌||❌||✅|
|Windows||.NET Core 2.x and earlier||✅*||✅*||✅|
|Windows||.NET Framework 4.x and earlier||✅||✅||✅|
|Windows||.NET Standard 2.1||❌||❌||✅|
|Windows||.NET Standard 2.0 and earlier||✅*||✅*||✅|
|Windows||Portable Class Libraries||✅*||✅*||✅|
|Windows||Universal Windows Platform (UWP)||✅||✅||✅|
* If you installed Dotfuscator with the NuGet package, a full (Windows) .NET Framework SDK must also be installed to use this feature.
Explicitly Specifying the Tool Locations
You can use properties to specify a specific ILdasm or ILasm executable for assemblies built on certain versions of .NET.
Note that .NET Core and .NET Standard assemblies are always considered to be built on version
4.0, due to that being the metadata version embedded in these assemblies.
The following table summarizes by example:
|Property Name||Property Value||Meaning|
||Dotfuscator will disassemble input assemblies that target .NET Framework v2.0.50215 with this version of the IL disassembler.|
||Dotfuscator will disassemble input assemblies that target .NET Framework v1.1.x with this version of the IL disassembler.|
||Dotfuscator will assemble output assemblies that target .NET Framework v1.0.3705 with this version of the IL assembler.|
||Dotfuscator will assemble output assemblies that target .NET Framework v4.x, .NET Core, or .NET Standard with this version of the IL assembler.|
Because the supported command line arguments for these tools differ between the versions provided by the .NET Framework and those provided by NuGet, Dotfuscator heuristically determines the kind of user-supplied tool by checking its path.
If the path contains
Microsoft.NETCore, Dotfuscator will treat the specified tool as coming from NuGet; otherwise, it will treat the tool as coming from the .NET Framework.