📜 ⬆️ ⬇️

Protecting .NET Applications with the Sentinel LDK Envelope

Utility Sentinel LDK Envelope, which will be discussed in this article, is designed to install an external protection on executable modules (EXE and DLL) for platforms Win32, Windows x64, .NET, as well as on Java applications (JAR and WAR). Protection is performed by “binding” the application code to the Sentinel protection key (new generation of HASP keys), and the key can be either hardware (HL) or software (SL). An executable module processed in this way will work only in the presence of the required key with all necessary licenses. In addition to checking for the presence of a key, the protection code embedded in the application will also provide active resistance to debugging and will make it difficult for the application to reverse-engineer, including static code analysis.

The purpose of this article is to consider the ways and features of protection of .NET applications, and with emphasis on maximizing the automation of the installation of protection. Therefore, further we will consider only the functionality of the Envelope, which concerns the protection of .NET-applications.

From the point of view of automating the process of protecting an application, the best option would be to implement protection at the source code level of the application using the Sentinel LDK API functions. With this implementation, the developer independently decides how this or that class or method will be protected, and when rebuilding the project, in case of changes to the source code, all the work done before implementing the protection is automatically taken into account naturally, without requiring rework. However, features of the .NET platform do not allow using many protection techniques that can be used in native code, for example, dynamic code encryption during application operation, code integrity monitoring, code recovery using noise-proof coding, etc. Rather, let's say, all this can be implemented, but it will most likely not be comfortable working in C #, but system programming in more "powerful" languages, low-level work with CIL-code, as well as the need to maintain and update used working methods in case of any changes inside .NET, for example, when a new version of this platform is released.
')
On the other hand, you can use hinged protection. It will take much less time to implement, the developer’s qualifications may not be so high (no knowledge of other programming languages, CIL, ... are needed). However, here too, not everything is cloudless, it is difficult to achieve complete automation of the installation of protection. First, we will consider in detail the use of hinged protection in normal use, and then we will think about how to automate all this. Suppose we create an initially insecure application without using the Sentinel LDK API, and then we will process the assembly using the Envelope. The technological cycle of creating a protected application in this case will look like this:
1) Building an unprotected assembly in a development environment, for example, in Visual Studio.
2) Protection of the constructed assembly in the process of interactive work with the Envelope.
Let us consider in more detail the second stage and see what methods of protection the Envelope can provide us. The protection process consists of the following operations:
1. Having loaded the first time Envelope, we will see an empty project. Immediately, we will indicate which series of keys our protected assembly will work with (see Figure 1):
image
Fig. 1 - Select key code series

2. Establish the general protection settings that will be applied to all the assemblies to be protected in the project (see Figure 2):
image
Fig. 2 - Setting General Security Settings

The parameter assignment is as follows:
• String encryption - encryption of all data stored in the #US heap, which contains in UNICODE standard all the strings that the developer has defined in his source code. So, for example, in .NET Reflector the code with unencrypted lines looks like:
image
And so - with encrypted:
image
• Periodic Background checks - background polling of the key at a specified interval.
• Apply compression — compression when classes and methods marked as protected are placed in the assembly. In reality, the protection durability does not affect, .NET Reflector, for example, does not notice this compression at rest, regardless of the parameters of this option.
• Obfuscate Symbols - obfuscation of data stored on the #Strings heap containing symbolic information, such as the names of classes, methods, fields, etc. Possible full obfuscation or partial, when the names of the resources are not changed.
Class Program before obfuscation:
image
After obfuscation:
image

3. Include one or more ready-made assemblies in the Envelope utility project (see Figure 3).
image
Fig. 3 - Adding target files to the project

4. For each assembly included in the project, specify the paths (where to get the source file and where to put the protected file), with which keys and how to work, which license to use (see Figure 4):
image
Fig. 4 - Set build options

5. If necessary, for each specific assembly, you can set your own protection parameters, which overlap common ones (see Fig. 5), and fine-tune the operation of the protective mechanisms (see Fig. 6):
image
Fig. five

image
Fig. 6

Parameters in fig. 5 have already been considered in paragraph 2. Of the parameters of interest (in terms of protection) in fig. 6, the following can be noted:
• LOCKING_TYPE - defines the types of keys with which the protection of this assembly will work. Possible options:
image
Where:
- HL - hardware key
- SL-UserMode - a software key that can work without a driver
- SL-AdminMode - a software key that works in conjunction with the driver
- SL - SL-UserMode + SL-AdminMode

• MIN_CODE_SIZE - the minimum size of the CIL code of the method in bytes, starting from which it will be protected by the Envelope in the case of a group operation, for example, when the entire class is marked for protection.

6. The most time-consuming part of the work remains to determine the classes and methods to be protected (Fig. 7-1), and for each selected class / method, set the parameters of the protective mechanisms (Fig. 7-2):
image
Fig. 7 - Security Management Methods and Classes

To select a class or method for protection, simply check the box next to its name in window 1. At the same time, window 2 displays the current protection settings for the selected object. Assign protection settings:

• Feature ID - license number from the key. For different classes / methods it may differ in case of their separate licensing.
• Frequency - how often the key for the protected class / method will be checked. Possible options:
image
Where:
- Once per program - once for the duration of the application
- Once per class instance - every time an instance is created
- Every time - with each pass of the control through the protected code

• Symbol obfuscation - obfuscation of symbolic information, can be applied to a method, even if it is not marked as subject to protection, since not related to the key. Possible options:
image
Where:
- Use global definition - the current value of the Obfuscate Symbols parameter is used, see p. 2 and p. 5.
- Enabled - obfuscation is always performed, regardless of global settings.
- Disabled — Obfuscation is never performed, regardless of global settings.

• Code obfuscation - control_flow-obfuscation of CIL-code, can be applied to the method, even if it is not marked as protected, because not related to the key. This is how the function code fragment looks in IDA after obfuscation:
image

And this is the control transfer graph in the function after obfuscation:
image

It is clearly seen that obfuscation is that the code is literally broken by one instruction, and all of them are mixed, and in order to preserve the previous order of their execution, after each instruction, a transition instruction (op-code br) is additionally inserted at the desired address. Obviously, the speed of the function protected in this way may drop dramatically, and the code will significantly increase in size. Therefore, this method of protection should be applied with caution.

• Code encryption - encrypting the CIL-code of the selected method through the key, using the AES algorithm. With this method of protection, the original CIL-code is extracted from the body of the method, encrypted through a key, and placed in the resources of the assembly in an encrypted form. A short adapter is inserted in its place, providing loading, decryption, dynamic compilation and transfer of control to the code of the protected method. After the completion of the method, its code is removed from memory. Here is the fragment of the body of the protected method in IDA:
image
The call class [mscorlib] System.Reflection.Emit.DynamicMethod statement defines and represents a dynamic method that will be compiled in memory, executed, and subsequently deleted. The instruction callvirt instance object [mscorlib] System.Reflection.MethodBase :: Invoke transfers control to the method if it is successfully compiled. This method of protection practically does not affect the speed of the protected code, providing quite decent durability.

7. After all the required classes / methods are marked as intended for protection, all that remains is to save the results of the work as a project file and click the Protect button. As a result, we get a protected assembly. The end of the work.

Now back to the issue of process automation. Obviously, the work we have just done is poorly automated. Yes, we saved everything we did, in the form of a project, and the next time you need to protect the assembly, you can use it by immediately loading it into the Envelope. However, the whole technological cycle of creating a protected application still consists of two weakly related stages that are performed in different programs. Let's try to connect them, making the protection transparent to the developer process. To do this, we use the console version of Envelope, which is called envelope.com (don't let the extension scare you, this is a normal exe-file, not a hello from the old ms-dos). So, when running with the –h key, we are informed that:
image

We will use the utility with the –p key at the “post build event” stage in Visual Studio. You can do, for example, like this:
image

All work with envelope.com has been moved to the batch file envelope.cmd, located in the project directory and containing the following commands:

..\..\LDK\VendorTools\VendorSuite\envelope.com -n --protect %1
move /Y %2.protect %2

In order for everything to work correctly, you need to specify the correct path to the directory with the envelope.com utility (you can use an absolute path, not a relative path, like this), and make a small change to the protection project in Envelope, by changing the output file extension, in order so that the move command can work normally:
image

The work of the utility can be checked by log in the Visual Studio output window:
image

After the end of the assembly creation process in the Visual Studio environment, we immediately get a protected application without unnecessary gestures.
It seems that everything turned out quite well, but what if the source code of the application makes significant changes? For example, if new classes / methods appear to be protected, old ones are removed or is it planned to change the way of protecting existing classes / methods? In the traditional approach, you will have to open the protection project in the Envelope and make all the changes that have been accumulated, as previously described in Section 6, which is sad, because it puts an end to the automation of the protection process in case of any major changes in the source code of the application.
However, it is possible to automate the actualization of such changes in the source code. This can be done by using custom attributes in the source code of the application. The object of protection using custom attributes can be a method, a class, or the entire assembly, depending on where a particular set of attributes is located. The list of available attributes to protect an object completely repeats the set of protection parameters for classes / methods described in section 6. Here is a list of available attributes:
• Protect - type BOOL, possible values ​​- TRUE / FALSE, indicates whether the object should be protected using the key.
• FeatureId - type int, possible values ​​- [0; 65535], indicates the license number (Feature ID), which will be used when protecting the object. It is accepted for execution only if Protect == TRUE.
• Encrypt - type BOOL, possible values ​​- TRUE / FALSE, indicates whether the CIL code of the object should be encrypted. It is accepted for execution only if Protect == TRUE.
• CodeObfuscation - type BOOL, possible values ​​- TRUE / FALSE, indicates whether the object's CIL code should be controlled_flow obfuscation. Accepted for execution regardless of the value of Protect. This method of protection leads to a decrease in the speed of the protected code.
• Frequency - the envelope type EnvelopeMethodProtectionFrequency, which indicates how often the license will be checked for the protected object. It is accepted for execution only if Protect == TRUE. Possible values:
- CheckOncePerApplicaton - one-time check in the process of the application.
- CheckOncePerInstance - one-time check for each object instance.
- CheckEveryTime - check with each pass of control through the object code.
• SymbolObfuscation - the enumerated type of the EnvelopeSymbolObfuscation, indicates the obfuscation method of the symbolic information in the protected object. Accepted for execution regardless of the value of Protect. Possible values:
- ObfuscateSkip - a complete ban on obfuscation of all symbolic information.
- ObfuscateForce - force obfuscation for all symbolic information.
- ObfuscateDefault — obfuscate for all symbolic information, except public names, as well as objects with the virtual and protected modifiers.

In order for the Envelope to get attributes from the source code, you must use the using directive to include the Aladdin.HASP.Envelope and Aladdin.HASP.EnvelopeRuntime namespaces in it. Naturally, before this you need to add the files Aladdin.HASP.Envelope.DLL and Aladdin.HASP.EnvelopeRuntime.DLL in the Visual Studio project links. You do not need to distribute these files with the protected application, they are required only during the installation of protection for the assembly. Below is an example of protection from source code using custom attributes:
image

Generally speaking, all changes related to the protection of the assembly can be divided into two categories:
1. Global changes that occur quite rarely. Described in paragraphs 1 - 5, they can not be controlled from the source code.
2. Changes in the source code associated with the emergence or deletion of classes / methods to be protected occur much more frequently. Described in Section 6. Now you can fully update such changes from the source code, without editing the protection project in the Envelope.

And now, summing up the above, we can say that the developer completely manages the protection from the source code, just as in the case of using the Sentinel LDK API. Downloading the protection project to the Envelope and correcting its parameters will be necessary only in the event of any global changes, for example, changing a series of keys, changing the name of the assembly or setting a different value for the background key polling interval, which happens incomparably less often.

Source: https://habr.com/ru/post/228241/


All Articles