What is obfuscation?
Almost all VB.Net developers know that compiled .Net code is well readable and easy to understand. Most names, that a developer uses for classes, methods, and fields, are opened, and everyone can modify a .Net application without difficulty. All strings, used in methods, and all embedded resources are stored as-is and can be extracted in a moment. It costs almost nothing to convert a compiled VB.Net application to source code, to change copyrights and a logo, and then rebuild it. Those developers who remember days of VB6 might be surprised (VB6 compiler produces unmanaged code that is not easy to decode and recompile), but this is how .Net is designed: the reflection requires metadata that stores names of everything, embedded resources are stored without any kind of encryption.
Of course, if a company sells a commercial product, it doesn’t want to turn their program into an open-source project. The obfuscation to the rescue!
The obfuscation is a process of:
- Changing the original byte code to a form that is hard to decode;
- Renaming types, methods, fields, events, and properties;
- Hiding embedded resources and providing them at runtime on demand;
In this article, we will show how to utilize ArmDot to make your VB.Net code protected from prying eyes.
Set up a project
In this sample, we will create an application that checks the entered password by comparing its hash with the correct hash value. To hide the logic of the algorithm we will virtualize code by ArmDot.
The correct password in this sample is ArmDot
You can find complete source code on GitHub: https://github.com/Softanics/armdot-vb.net-sample
Create a new Windows Forms application in Visual Studio, add a textbox and a button. Add a handler that calculates a hash of the entered password, compares it with the correct value, and then displays a message with the information whether the entered password is correct or not:
Private Sub ButtonCheckPassword_Click(sender As Object, e As EventArgs) Handles ButtonCheckPassword.Click Dim hash As Byte() = (New SHA256Managed()).ComputeHash(Encoding.Unicode.GetBytes(TextBoxPassword.Text)) If hash(0) = 81 And hash(1) = 96 And hash(2) = 9 And hash(3) = 150 And hash(4) = 45 And hash(5) = 146 And hash(6) = 51 And hash(7) = 201 And hash(8) = 238 And hash(9) = 22 And hash(10) = 103 And hash(11) = 233 And hash(12) = 209 And hash(13) = 135 And hash(14) = 107 And hash(15) = 39 And hash(16) = 228 And hash(17) = 171 And hash(18) = 22 And hash(19) = 78 And hash(20) = 218 And hash(21) = 213 And hash(22) = 11 And hash(23) = 131 And hash(24) = 71 And hash(25) = 17 And hash(26) = 241 And hash(27) = 180 And hash(28) = 118 And hash(29) = 9 And hash(30) = 163 And hash(31) = 249 Then MessageBox.Show("The password is correct") Else MessageBox.Show("The password is wrong") End If End Sub
The comparison is made byte by byte. It is a good idea to obfuscate such kind of code because compiled byte code is straightforward: https://gist.github.com/Softanics/4c5c2df43d78caf582ccb2ddfbb8a3be
We will show different obfuscation options below, but before that, you need to add ArmDot to the project. In Visual Studio, right-click References, choose Manage NuGet Packages…, then switch to Browse, find and install “ArmDot.Client” and “ArmDot.Engine.MSBuildTasks”.
ArmDot.Client contains attributes that enable different obfuscation types.
ArmDot.Engine.MSBuildTasks provides a task that processes the assembly by the ArmDot engine.
After adding those NuGet packages, close the solution in Visual Studio and modify the project manually. Add the following declaration right before </Project>:
<Target Name="Protect" AfterTargets="Build"> <ItemGroup> <Assemblies Include="$(TargetDir)$(TargetFileName)" /> </ItemGroup> <ArmDot.Engine.MSBuildTasks.ObfuscateTask Inputs="@(Assemblies)" /> </Target>
This declaration adds ArmDot.Engine.MSBuildTasks.ObfuscateTask to the build process.
To ensure that ArmDot.Engine.MSBuildTasks.ObfuscateTask is added successfully, load the project and rebuild the project. You will see the output like the following:
1>------ Build started: Project: ArmDotSample, Configuration: Debug Any CPU ------ 1> ArmDotSample -> V:\Projects\armdot-vb.net-sample\ArmDotSample\bin\Debug\ArmDotSample.exe 1> [ArmDot] ArmDot [Engine Version 2020.15.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): ArmDotSample.exe (V:\Projects\armdot-vb.net-sample\ArmDotSample\bin\Debug\ArmDotSample.exe) ------ 1>V:\Projects\armdot-vb.net-sample\ArmDotSample\ArmDotSample.vbproj(135,5): warning : [ArmDot] No methods to protect in the assembly V:\Projects\armdot-vb.net-sample\ArmDotSample\bin\Debug\ArmDotSample.exe 1> [ArmDot] Writing protected assembly to V:\Projects\armdot-vb.net-sample\ArmDotSample\bin\Debug\ArmDotSample.exe... 1>V:\Projects\armdot-vb.net-sample\ArmDotSample\ArmDotSample.vbproj(135,5): warning : [ArmDot] The assembly ArmDotSample.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 ==========
Names obfuscation is a process of renaming types, methods, events, and properties to nonsense names. It is useful to hide the logic of an application in general.
In order to enable names obfuscation, add the following code to any source file:
Then rebuild the project. Method names are confusing now. In particular, the handler’s name is GetFileLineNumberAsymmetricAlgorithm now: https://gist.github.com/Softanics/c56f3fe72c58891e818a8e897e23484e
Unfortunately, strings are still placed in the code as-is.
String literals are widely used. In our sample, texts for MessageBox are easy to read and change by humans. To hide strings in entire assembly, add the following attribute:
If you want to encrypt strings in a particular method only, add this attribute to this method.
Strings are hidden now: https://gist.github.com/Softanics/6af0aa56293e1ce827105a4b3ed1b377
However, anyone who is reviewing the code can still understand that the code makes some comparisons, and finally displays a message box with one text or another one, depending on the comparison result. Fortunately, there is a way to hide such details.
Control flow obfuscation
The idea of control flow obfuscation is to extract sequences of instructions that are executed consequentially, and to change original code to a large loop that executes those sequences. At the end of the loop, an index of the next sequence is calculated at runtime. Thus the original logic of control flow is converted to a unified loop.
To activate control flow obfuscation use the attribute ObfuscateControlFlow:
You can find the result of control flow obfuscation here: https://gist.github.com/Softanics/1b11c7e3d3dc282b320fc948bc8e661a
Despite the control flow obfuscation is an efficient way to hide the original logic of a method, the data flow is still not encrypted: anyone has access to information about local variables, arguments, and which values are stored and loaded from them.
The evolution of the control flow obfuscation idea is code virtualization.
Code virtualization hides not only control flow but also data one. It represents the original code as a set of instructions for some virtual machine that maintains internal execution stack, local variables, and arguments. As with control flow obfuscation, a virtualized method is a big loop, but instead of executing an entire branch, it executes a single instruction in each iteration.
Original instructions are converted to another format, unique for each virtual machine. The uniqueness guarantees that a hacker has to decode each virtual machine from scratch,
Of course, there is a price to pay: a virtualized method is executing slower than the original code, so it a bad idea to use virtualization for computation-intensive methods.
To apply virtualization, use the attribute VirtualizeCode:
<ArmDot.Client.VirtualizeCode> Private Sub ButtonCheckPassword_Click(sender As Object, e As EventArgs) Handles ButtonCheckPassword.Click Dim hash As Byte() = (New SHA256Managed()).ComputeHash(Encoding.Unicode.GetBytes(TextBoxPassword.Text)) If hash(0) = 81 And hash(1) = 96 And hash(2) = 9 And hash(3) = 150 And hash(4) = 45 And hash(5) = 146 And hash(6) = 51 And hash(7) = 201 And hash(8) = 238 And hash(9) = 22 And hash(10) = 103 And hash(11) = 233 And hash(12) = 209 And hash(13) = 135 And hash(14) = 107 And hash(15) = 39 And hash(16) = 228 And hash(17) = 171 And hash(18) = 22 And hash(19) = 78 And hash(20) = 218 And hash(21) = 213 And hash(22) = 11 And hash(23) = 131 And hash(24) = 71 And hash(25) = 17 And hash(26) = 241 And hash(27) = 180 And hash(28) = 118 And hash(29) = 9 And hash(30) = 163 And hash(31) = 249 Then MessageBox.Show("The password is correct") Else MessageBox.Show("The password is wrong") End If End Sub
Rebuild the project, and review the result here: https://gist.github.com/Softanics/149e22daba0dc5f2b2a3015b78532ff5
The logic is reliably hidden now!
How to protect embedded resources in VB.Net?
ArmDot can hide and encrypt embedded resources. The attribute ProtectEmbeddedResources instructs the tool to protect embedded resources:
There are several standard obfuscation techniques: names obfuscation, control flow obfuscation, and code virtualization. Names obfuscation is the very first step to confuse anyone who wants to explore your application. To better hide the logic of a method, use control flow obfuscation: it hides the original control flow by a large loop that executes original branches. The most advanced obfuscation way is code virtualization: it converts an original method to a unique virtual machine that interprets original instructions (that are also converted to an internal unique format known for the virtual machine). Both data and control flow become hidden from others. While names obfuscation and control flow obfuscation doesn’t affect performance, code virtualization makes the code slower, but there is almost no difference if the code doesn’t make a lot of computation.
ArmDot supports all these obfuscation types. To enable them, use attributes from ArmDot.Client available on NuGet. To automate obfuscation, use ArmDot.Engine.MSBuildTasks.ObfuscateTask. This task is executed after an assembly is built, it obfuscates the assembly by the ArmDot engine.