A few weeks ago I got an email from a customer who was trying to use my tool for migrating Source Safe to Subversion on a Windows Server 2003. We managed to do the migration by running the tool on a newer version of Windows, but that made me add the problem to my backlog, since 63% of companies are still using Windows Server 2003.
It took me only a few minutes of testing to conclude that the obfuscation step was causing the problem in Windows Server 2003.
I’ve been using EazFuscator for many years now. It was free up to a few years ago, and by that time it was maybe the most modern obfuscator available.
The last free version is 3.3 and you can get it here. The last one that was working fine in my WPF projects was 3.2. However, if I wanted to have bugfixes (like the Windows 2003) I would have to upgrade.
The same link above (that provides EazFuscator 3.3) led me to ConfuserEx, which is an open-source and very modern C# obfuscator.
Like in most other opensource projects, documentation could be a little better. But you can find some nice summary at this StackOverflow thread.
First thing you will notice is that you can define obfuscation rules both in the
.crproj file (which is the project that describes the obfuscation) or using declarative obfuscation (which is just using regular .NET
ObfuscationAttribute in your code). I tried using the interface for configuring the
.crproj file, but since most of my projects already contain obfuscation attributes I decided to go with the attributes path. Another reason is that I couldn’t really understand the obfuscator behavior when using both attributes and rules in
.crproj (it was not clear which setting was overring the other..)
As you will probably notice, the syntax that ConfuserEx uses is somewhat confusing at first (no pun intended). As you gain better understanding on this obfuscator you’ll notice that in fact it’s much more strict than other obfuscators, but make much more sense:
- In most other obfuscators (including EazFuscator) you can disable members renaming using
[Obfuscation(Exclude=true, Feature = "renaming")].
Exclude=truewas encouraged, but usually is completely useless.
- ConfuserEx on the other hand is much more strict and follows the documentation for the Obfuscation attribute, which states that Exclude should be used if you want to completely exclude some member from the obfuscation. So if you use something like
[Obfuscation(Exclude = true, Feature = "ctrl flow")]you will get a nice descriptive error that says
System.ArgumentException: Feature property cannot be set when Exclude is true.
In summary, if you want to exclude a class from being renamed, use
[ObfuscationAttribute(Exclude = false, Feature = "-rename")].
General Rules, Exclusion Rules, and where the hell should you describe your rules
Obfuscators usually work by defining the general rules, that should be applied to all your project, and exclusion rules which override that general behavior for a specific class or member. The exclusion rules are like the ones I’ve shown above: they are usually applied directly to the source-code. And that makes a lot of sense, since if you refactor your classes or members (renaming or moving across namespaces) their overriding rules will follow them. The other alternative is defining those rules inside
.crproj (using a very powerful [altough badly documented] syntax for selecting by namespace, class name, types, etc).
Since the exclusion rules fit very well in the source code, why would you leave the general rules in the
.crproj? It makes more sense to keep all the rules together inside the source code. And general rules fit very well into assembly-level attributes (which by convention you should add to
[assembly: Obfuscation(Exclude = false, Feature = "random seed: MySeed123")] [assembly: Obfuscation(Exclude = false, Feature = "packer:compressor")] [assembly: Obfuscation(Exclude = false, Feature = "preset(normal)")]
- The random seed is just a way of seeding the random-name-generator with a known value, so that all classes and members will be renamed to the same names on every obfuscation. It can help your customer support to understand stacktraces.
- The packer is a feature that most modern obfuscators use that load your code dynamically in memory. It’s completely independent of the other obfuscation features, so it’s a good idea to inspect your assemblies without the packer to make sure that code is really being obfuscated.
You can also use compatibility mode (
[assembly: Obfuscation(Exclude = false, Feature = "packer:compressor(compat=true)")]) which could solve some bugs (but could introduce others)
The preset is just a default set of features to be used during obfuscation.
Available options are:
You can fine-tune the options like this, adding some features not defined in the preset while removing others:
[assembly: Obfuscation(Exclude = false, Feature = "preset(minimum);+ctrl flow;-anti debug;+rename(mode=letters,flatten=false);")].
You could also pick each feature that you want while using the
.crprojallow you to add multiple rules, using that powerful syntax I mentioned before. Some examples here, here and here.
You can integrate the ConfuserEx process into your automated builds like this:
REM "C:\Program Files (x86)\Eazfuscator.NET\Eazfuscator.NET.exe" .\Obfuscated\%$(TargetFileName)% -k %$(SolutionDir)%..\Abstrakti.snk "C:\Program Files (x86)\ConfuserEx\Confuser.CLI.exe" %$(SolutionDir)%\Krepost.WPF\Confuser-%$(SolutionName)%-%$(ConfigurationName)%.crproj @if %errorlevel% neq 0 GOTO Error
In case you are curious about the environment variables above, I always add all environment variables to my post-build scripts like this:
CALL "$(ProjectDir)..\PostBuild.bat" "$(OutDir)" "$(ConfigurationName)" "$(ProjectName)" "$(TargetName)" "$(TargetPath)" "$(ProjectPath)" "$(ProjectFileName)" "$(TargetExt)" "$(TargetFileName)" "$(TargetDir)" "$(ProjectDir)" "$(SolutionFileName)" "$(SolutionPath)" "$(SolutionDir)" "$(SolutionName)" "$(PlatformName)" "$(ProjectExt)" "$(SolutionExt)". And then in PostBuild.bat I read them like this:
@set $(OutDir)=%~1 @set $(ConfigurationName)=%~2 @set $(ProjectName)=%~3 @set $(TargetName)=%~4 @set $(TargetPath)=%~5 @set $(ProjectPath)=%~6 @set $(ProjectFileName)=%~7 @set $(TargetExt)=%~8 @set $(TargetFileName)=%~9 @shift @shift @shift @shift @shift @shift @shift @shift @shift @set $(TargetDir)=%~1 @set $(ProjectDir)=%~2 @set $(SolutionFileName)=%~3 @set $(SolutionPath)=%~4 @set $(SolutionDir)=%~5 @set $(SolutionName)=%~6 @set $(PlatformName)=%~7 @set $(ProjectExt)=%~8 @set $(SolutionExt)=%~9
That’s just a standard I like to use in my projects so that I could use any environment variable that I ever need. :-)
Encrypted by an Open Source project?
Encryption through obscurity was something from the past century. Open cryptography is considered safer because it is open to reviews and auditing from security experts all over the world.
Many people believe that ConfuserEx is one of the hardest obfuscators available. And although any executable on the local machine is crackable (even when using obfuscators or packers), there are some tricks you can do to avoid script kiddies and 1-click tools. And remember to think about the tradeoffs of adding too much security.