Building and Debugging
Linking
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.
Limitations
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.
Incremental Obfuscation
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, A.exe
, B.dll
, and C.dll
where A
references B
, and B
references 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 B.dll
.
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.
Debugging Symbols
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.
Delay Signing:
<signing>
...
<delaysign>
<key>
<file dir="c:\temp" name="key.snk" />
</key>
</delaysign>
</signing>
Build Events
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:
Property Name | Description |
---|---|
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.