ArmDot Tutorial
Introduction
In this tutorial, we will demonstrate how to obfuscate an application with the help of ArmDot. We will show basic obfuscation options, and how to use ArmDot API.
You can find the sample project on GitHub: https://github.com/Softanics/armdot-tutorial-sample
Prepare the Project
In this tutorial let's use Visual Studio just for instance. Let's create a new Windows Forms project.
In order to use ArmDot add the ArmDot.Client NuGet package (below you will see how to add obfuscation to the build process with the help of the ArmDot.Engine.MSBuildTasks NuGet package; you can add it now) to the project:
Imagine, an application verifies a password, comparing its hash with a correct one. It is a good idea to hide the logic, a correct hash value, and to prevent the code from modifications.
Let's add some controls to the form: a text area to enter a password, and a button to verify:
Then double-click the button to go to the handler. Write the verification code that calculates a hash of entered text and compares the value with the correct one:
private void buttonCheckPassword_Click(object sender, EventArgs e)
{
byte[] hash = (new SHA256Managed()).ComputeHash(Encoding.Unicode.GetBytes(textBoxPassword.Text));
if (hash[0] == 81 && hash[1] == 96 && hash[2] == 9 && hash[3] == 150 &&
hash[4] == 45 && hash[5] == 146 && hash[6] == 51 && hash[7] == 201 &&
hash[8] == 238 && hash[9] == 22 && hash[10] == 103 && hash[11] == 233 &&
hash[12] == 209 && hash[13] == 135 && hash[14] == 107 && hash[15] == 39 &&
hash[16] == 228 && hash[17] == 171 && hash[18] == 22 && hash[19] == 78 &&
hash[20] == 218 && hash[21] == 213 && hash[22] == 11 && hash[23] == 131 &&
hash[24] == 71 && hash[25] == 17 && hash[26] == 241 && hash[27] == 180 &&
hash[28] == 118 && hash[29] == 9 && hash[30] == 163 && hash[31] == 249)
{
MessageBox.Show("The password is correct");
}
else
{
MessageBox.Show("The password is wrong");
}
}
Build the application and look at the generated code. To review byte code we highly recommend dotPeek, but you can also use the standard tool Ildasm.
⚠️ Warning: dotPeek cache issue
dotPeek caches results, so you have to clear its cache to review updated IL code (see more: https://stackoverflow.com/questions/30071777/where-does-dotpeek-store-its-cache).
Otherwise, even after obfuscation, you will see outdated (i.e. not obfuscated) code.
After loading the executable file into dotPeek, find and select buttonCheckPassword_Click
, and you will see that the code is easy to understand:
ArmDot virtualizes IL code by transforming .NET byte code into an internal form that is executed by the virtual machine. Each time ArmDot generates a unique virtual machine with a unique set of internal instructions.
Enable Obfuscation
Add the attribute VirtualizeCode to the method that should be virtualized:
[ArmDot.Client.VirtualizeCode]
private void buttonCheckPassword_Click(object sender, EventArgs e)
{
byte[] hash = (new SHA256Managed()).ComputeHash(Encoding.Unicode.GetBytes(textBoxPassword.Text));
if (hash[0] == 81 && hash[1] == 96 && hash[2] == 9 && hash[3] == 150 &&
hash[4] == 45 && hash[5] == 146 && hash[6] == 51 && hash[7] == 201 &&
hash[8] == 238 && hash[9] == 22 && hash[10] == 103 && hash[11] == 233 &&
hash[12] == 209 && hash[13] == 135 && hash[14] == 107 && hash[15] == 39 &&
hash[16] == 228 && hash[17] == 171 && hash[18] == 22 && hash[19] == 78 &&
hash[20] == 218 && hash[21] == 213 && hash[22] == 11 && hash[23] == 131 &&
hash[24] == 71 && hash[25] == 17 && hash[26] == 241 && hash[27] == 180 &&
hash[28] == 118 && hash[29] == 9 && hash[30] == 163 && hash[31] == 249)
{
MessageBox.Show("The password is correct");
}
else
{
MessageBox.Show("The password is wrong");
}
}
Build the project. The built assembly still has a reference to ArmDot.Client. But the assembly is not yet protected. The method has VirtualizeCodeAttribute
attribute, which tells ArmDot that the method should be virtualized, but ArmDot hasn't obfuscate the assembly yet: we need an additional step.
⚠ Notice about ArmDot.Client
A developer uses attributes from
ArmDot.Client.dll
to instruct ArmDot what obfuscation options should be applied. After obfuscation, a reference toArmDot.Client.dll
is removed, so you don't need to distribute it with your application.
It is easy to add obfuscation to the build process. Ensure that the ArmDot.Engine.MSBuildTasks NuGet package is added to the project.
This package provides an MSBuild task that obfuscates assemblies by ArmDot after they have been built.
Close the solution in Visual Studio, and add the task to the end of the project file right before </Project>
:
<Target Name="Protect" AfterTargets="AfterCompile" BeforeTargets="BeforePublish">
<ItemGroup>
<Assemblies Include="$(ProjectDir)$(IntermediateOutputPath)$(TargetFileName)" />
</ItemGroup>
<ArmDot.Engine.MSBuildTasks.ObfuscateTask
Inputs="@(Assemblies)"
ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
SkipAlreadyObfuscatedAssemblies="true"
/>
</Target>
Target
adds the ArmDot obfuscation task that is executed after all assemblies are built. Assemblies
stores list of assemblies to protect. In this sample, there is only one assembly presented.
Load the project and rebuild the solution. You will get some ArmDot output during the build process:
1>------ Rebuild All started: Project: armdot-tutorial-sample, Configuration: Debug Any CPU ------
1> armdot-tutorial-sample -> V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe
1> [ArmDot] ArmDot [Engine Version 2020.12.0.0] (c) Softanics. All Rights Reserved
1> [ArmDot] No license key specified, or it is empty. ArmDot is working in demo mode.
1> [ArmDot] THIS PROGRAM IN UNREGISTERED. Buy a license at https://www.armdot.com/order.html
1> [ArmDot] ------ Build started: Assembly (1 of 1): armdot-tutorial-sample.exe (V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe) ------
1> [ArmDot] Conversion started for method System.Void armdot_tutorial_sample.Form1::buttonCheckPassword_Click(System.Object,System.EventArgs)
1> [ArmDot] Conversion finished for method System.Void armdot_tutorial_sample.Form1::buttonCheckPassword_Click(System.Object,System.EventArgs)
1> [ArmDot] Writing protected assembly to V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe...
1>V:\Projects\armdot-tutorial-sample\armdot-tutorial-sample.csproj(96,3): warning : [ArmDot] The assembly armdot-tutorial-sample.exe will stop working in 7 days because it is protected by the ArmDot demo version
1> [ArmDot] Finished
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
It means that everything has been set up correctly!
💡 How to obfuscate release builds only?
Usually, a release build is obfuscated only, as debug builds are used to debug.
To obfuscate release builds only, add a condition:
<Target Name="Protect" AfterTargets="AfterCompile" BeforeTargets="BeforePublish" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <ItemGroup> <Assemblies Include="$(ProjectDir)$(IntermediateOutputPath)$(TargetFileName)" /> </ItemGroup> <ArmDot.Engine.MSBuildTasks.ObfuscateTask Inputs="@(Assemblies)" ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')" /> </Target>
Rebuild the project and run. The obfuscated code is working as expected:
Look at the obfuscated code in dotPeek (you might need to clear the cache). It is really hard to understand now what the code is doing (you can find complete IL code on GitHub: https://gist.github.com/Softanics/116502b1ab09e86a2be6f4985e6f2cca):
Enable Licensing API
Another ArmDot feature is the licensing API developed for commercial software vendors. With the help of the API, one can issue and check license keys, and extract information stored in license keys.
ArmDot uses asymmetric encryption to generate license keys. The cryptography algorithm requires a few constants that are unique for each project. ArmDot stores the algorithm constants in a project file. Having a project file, you can use ArmDot command line tool to create license keys, or open the project in the ArmDot UI and create keys there.
You can create such a project file using the ArmDot UI, but there is an easier way if you use Visual Studio for development like in this sample: ArmDot itself will create a project file. Let's have a look how to do that.
In order to instruct the ArmDot MSBuild task that you intend to use the licensing API, modify the project file as the following:
<Target Name="Protect" AfterTargets="AfterCompile" BeforeTargets="BeforePublish">
<ItemGroup>
<Assemblies Include="$(ProjectDir)$(IntermediateOutputPath)$(TargetFileName)" />
</ItemGroup>
<ArmDot.Engine.MSBuildTasks.ObfuscateTask
Inputs="@(Assemblies)"
ProjectPath="$(ProjectDir)armdot-tutorial-sample.armdotproj"
ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
SkipAlreadyObfuscatedAssemblies="true"
/>
</Target>
The ProjectPath
attribute specifies the path of an ArmDot project file to be used (it is a good idea to add armdotproj extension to the file name, as this is a known extension for the ArmDot). If the file doesn't exist, a new one will be created. You can add it to a version control system once it is ready (just rebuild the project).
Reload the project in Visual Studio and rebuild it to ensure that everything is going well. ArmDot shows that a new project file armdot-tutorial-sample.armdotproj
has been created:
1>------ Build started: Project: armdot-tutorial-sample, Configuration: Debug Any CPU ------
1> armdot-tutorial-sample -> V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe
1> [ArmDot] ArmDot [Engine Version 2020.13.0.0] (c) Softanics. All Rights Reserved
1> [ArmDot] No license key specified, or it is empty. ArmDot is working in demo mode.
1> [ArmDot] THIS PROGRAM IN UNREGISTERED. Buy a license at https://www.armdot.com/order.html
1> [ArmDot] V:\Projects\armdot-tutorial-sample\armdot-tutorial-sample.armdotproj does not exist. Creating new project...
1> [ArmDot] ------ Build started: Assembly (1 of 1): armdot-tutorial-sample.exe (V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe) ------
1> [ArmDot] Conversion started for method System.Void armdot_tutorial_sample.Form1::buttonCheckPassword_Click(System.Object,System.EventArgs)
1> [ArmDot] Conversion finished for method System.Void armdot_tutorial_sample.Form1::buttonCheckPassword_Click(System.Object,System.EventArgs)
1> [ArmDot] Writing protected assembly to V:\Projects\armdot-tutorial-sample\bin\Debug\armdot-tutorial-sample.exe...
1>V:\Projects\armdot-tutorial-sample\armdot-tutorial-sample.csproj(95,5): warning : [ArmDot] The assembly armdot-tutorial-sample.exe will stop working in 7 days because it is protected by the ArmDot demo version
1> [ArmDot] Finished
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Checking License Key
Now we can use methods that provide ArmDot API to check a license key state and to retrieve information included in it.
⚠ Call PutKey in each method before using the licensing API
PutKey must be called before calling methods that return information from a license key. It sets the current license key that is visible in the currently executed method only.
Let's add a new button to check entered license key, and displaying its state:
private void buttonCheckLicense_Click(object sender, EventArgs e)
{
ArmDot.Client.Api.PutKey(textBoxPassword.Text);
string state;
switch (ArmDot.Client.Api.GetLicenseState())
{
case ArmDot.Client.Api.LicenseKeyState.Valid:
{
state = "valid";
break;
}
case ArmDot.Client.Api.LicenseKeyState.Blocked:
{
state = "blocked";
break;
}
case ArmDot.Client.Api.LicenseKeyState.Expired:
{
state = "expired";
break;
}
case ArmDot.Client.Api.LicenseKeyState.MaximumBuildDateExpired:
{
state = "maximum build date expired";
break;
}
case ArmDot.Client.Api.LicenseKeyState.BadHardwareId:
{
state = "bad hardware id";
break;
}
case ArmDot.Client.Api.LicenseKeyState.Invalid:
default:
{
state = "invalid";
break;
}
}
MessageBox.Show($"The key state is {state}");
}
To test this code we need a valid license key. On Windows, run ArmDot, open the created project file armdot-tutorial-sample.armdotproj
, switch to Licenses, click to Add..., enter license key parameters and click to OK:
On Linux, use ArmDotConsole to generate new license key from the command line:
artem@ububtu-x64:/media/sf_Ubuntu-x64/armdot/armdot-tutorial-sample$ ArmDotConsole --project-path armdot-tutorial-sample.armdotproj --generate-license-key --license-user-name "Test" --license-user-email "test@test.com"
ArmDotConsole [Version 2020.14.0.0]
(c) 2004 - 2020 Softanics. All Rights Reserved
lcju0zYjLcZKnbwWlOKLfFTKMISuGRCRgk2JQa+gwU7PXvrZ8zMD6U0ffcPaW26tUNueMAVi6lmBmt0/OrcwIJYxWB5HyWXzDMbYzz27LeOIfuYI05AEXOJNe2x3ApC3MMxK2FCJC6sBakLX0yN+fZiKKV9+H6T79x20+8wae3AA
Build the project and enter the generated key. It is working well:
⚠ An obfuscated assembly never uses ArmDot.Client.dll
The licensing API consists of the methods exposed by ArmDot.Client.Api, so before obfuscation, the assembly has a reference to ArmDot.Client.dll.
When ArmDot processes an assembly, it replaces methods of ArmDot.Client with their actual instructions.
That is why an obfuscated assembly never requires ArmDot.Client.dll and does not have a reference to it.
Get Data From a Key
There is a set of methods to get information that is stored in a license key: GetUserName, GetUserEMail, GetUserData, GetExpirationDate, and GetMaximumBuildDate. Always call PutKey before any of them!
Modify the code to display a user name that is saved in a key, and also virtualize the code to confuse anyone who will research the code:
[ArmDot.Client.VirtualizeCode]
private void buttonCheckLicense_Click(object sender, EventArgs e)
{
ArmDot.Client.Api.PutKey(textBoxPassword.Text);
string state;
string userName = null;
switch (ArmDot.Client.Api.GetLicenseState())
{
case ArmDot.Client.Api.LicenseKeyState.Valid:
{
state = "valid";
userName = ArmDot.Client.Api.GetUserName();
break;
}
case ArmDot.Client.Api.LicenseKeyState.Blocked:
{
state = "blocked";
break;
}
case ArmDot.Client.Api.LicenseKeyState.Expired:
{
state = "expired";
break;
}
case ArmDot.Client.Api.LicenseKeyState.MaximumBuildDateExpired:
{
state = "maximum build date expired";
break;
}
case ArmDot.Client.Api.LicenseKeyState.BadHardwareId:
{
state = "bad hardware id";
break;
}
case ArmDot.Client.Api.LicenseKeyState.Invalid:
default:
{
state = "invalid";
break;
}
}
MessageBox.Show($"The key state is {state}, user name is {userName ?? "<unknown>"}");
}
Build the project and run. Check that it works as expected:
Wrapping up
You have just learned how to use attributes to tell ArmDot what kind of obfuscation to be applied. The ArmDot build task helps developers to add obfuscation to a build process. The licensing API checks whether a license key is valid, and extracts data stored in keys.