34
Obfuscation, Application Monitoring, and Management

THE IL DISASSEMBLER

Before looking at how you can protect your code from other people and monitor its behavior “in the wild,” it is important to consider how you can build better applications in the first place. A useful tool for this is the Microsoft .NET Framework IL Disassembler, or ILDasm. You can execute ILDasm by launching the Developer command prompt. If you are running Windows 8 or 10, enter command prompt into the Search text box (for Windows 8, you need to use the Search charm to display the text box). In Windows 7, you can find the developer command prompt at All Programs ➪ Microsoft Visual Studio 2017 ➪ Visual Studio Tools ➪ Visual Studio Command Prompt. Once the command prompt is running, enter ILDasm to launch the Disassembler. In Figure 34-1, a small class library has been opened using this tool, and you can immediately see the namespace and class information contained within this assembly.

Screenshot of a small class library.

FIGURE 34-1

To compare the IL that is generated, the original source code for the MathematicalGenius class is as follows:

C#

namespace ObfuscationSample
{
    public class MathematicalGenius
    {
        public static Int32 GenerateMagicNumber(Int32 age, Int32 height)
        {
            return (age * height) + DateTime.Now.DayOfYear;
        }
    }
}

VB

Namespace ObfuscationSample
    Public Class MathematicalGenius
        Public Shared Function GenerateMagicNumber(ByVal age As Integer, _
                                                ByVal height As Integer) As Integer
            Return (age * height) + Today.DayOfWeek
        End Function
    End Class
End Namespace

Double-clicking the GenerateMagicNumber method in ILDasm opens up an additional window that shows the IL for that method. Figure 34-2 shows the IL for the GenerateMagicNumber method, which represents your super-secret, patent-pending algorithm. In actual fact, anyone who is prepared to spend a couple of hours learning how to interpret MSIL could quickly work out that the method simply multiplies the two int32 parameters, age and height, and then adds the current day of the year to the result.

Illustration of IL for the GenerateMagicNumber method.

FIGURE 34-2

If you haven’t spent any time understanding how to read MSIL, a decompiler can convert this IL back into one or more .NET languages.

DECOMPILERS

One of the most widely used decompilers is JustDecompile from Telerik (available for download at http://www.telerik.com/products/decompiler.aspx). JustDecompile can be used to decompile any .NET assembly into C# or Visual Basic. In Figure 34-3, the same assembly that you just accessed using ILDasm is opened in JustDecompile.

Illustration of ILDasm opened in JustDecompile.

FIGURE 34-3

In the pane on the left of Figure 34-3, you can see the namespaces, type, and method information in a layout similar to ILDasm. Double-clicking a method opens the Disassembler pane on the right, which displays the contents of that method in the language specified in the toolbar. In this case, you can see the C# code that generates the magic number, which is almost identical to the original code.

If the generation of the magic number were a real secret on which your organization depended in order to make money, the ability to decompile this application would pose a significant risk. This capability should affect not only how you deliver your code, but also how you might design your application. Obfuscation, discussed in the next section, is one possible approach to mitigating (but not completely eliminating) this risk.

OBFUSCATING YOUR CODE

So far, this chapter has highlighted the need for better protection for the logic embedded in your applications. Obfuscation is the art of renaming symbols and modifying code paths in an assembly so that the logic is unintelligible and can’t be easily understood if decompiled. Numerous products can obfuscate your code, each using its own tricks to make the output less likely to be understood. Visual Studio 2017 ships with the Community Edition of Dotfuscator and Analytics from PreEmptive Solutions, which this chapter uses as an example of how you can apply obfuscation to your code.

Dotfuscator and Analytics

Although Dotfuscator can be launched from the Tools menu within Visual Studio 2017, it is a separate product with its own licensing. The Community Edition (CE) contains only a subset of the functionality of the commercial edition of the product, the Dotfuscator Suite. If you are serious about trying to hide the functionality embedded in your application, you should consider upgrading. You can find more information on the commercial version of Dotfuscator at http://www.preemptive.com/products/dotfuscator/compare-editions.

Dotfuscator CE uses its own project format to keep track of which assemblies you are obfuscating and any options that you specify. After starting Dotfuscator from the Tools menu, it opens with a new unsaved project. Select the Inputs node in the navigation tree, and then click the button with the plus sign under the Inputs listing to add the .NET assemblies that you want to obfuscate. Figure 34-4 shows a new Dotfuscator project into which has been added the assembly for the application from earlier in this chapter.

Illustration of new Dotfuscator project.

FIGURE 34-4

On the right side of the interface, make sure that Library mode is unchecked. Then you can select Build Project from the Build menu, or click the Build button (fourth from the left) on the toolbar, to obfuscate this application. If you have saved the Dotfuscator project, the obfuscated assemblies will be added to a Dotfuscated folder under the folder where the project was saved. If the project has not been saved, the output is written to c:Dotfuscated.

If you open the generated assembly using JustDecompile, as shown in Figure 34-5, you can see that the GenerateMagicNumber method has been renamed, along with the input parameters. In addition, the namespace hierarchy has been removed, and classes have been renamed. Although this is a rather simple example, you can see how numerous methods with similar, nonintuitive names could cause confusion and make the source code difficult to understand when decompiled.

Illustration of JustDecompile dialog.

FIGURE 34-5

The previous example obfuscated the public method of a class, which is fine if the method will be called only from assemblies obfuscated along with the one containing the class definition. However, if this were a class library or API that will be referenced by other unobfuscated applications, you would see a list of classes that have no apparent structure, relationship, or even naming convention. This would make working with this assembly difficult. Luckily, Dotfuscator enables you to control what is renamed during obfuscation. Before going ahead, you need to refactor the code slightly to pull the functionality out of the public method. If you didn’t do this and you excluded this method from being renamed, your secret algorithm would not be obfuscated. By separating the logic into another method, you can obfuscate that while keeping the public interface unchanged. The refactored code would look like the following:

C#

namespace ObfuscationSample
{
    public class MathematicalGenius
    {
        public static Int32 GenerateMagicNumber(Int32 age, Int32 height)
        {
            return SecretGenerateMagicNumber(age, height);
        }

        private static Int32 SecretGenerateMagicNumber(Int32 age, Int32 height)
        {
            return (age * height) + DateTime.Now.DayOfYear;
        }
    }
}

VB

Namespace ObfuscationSample
    Public Class MathematicalGenius
        Public Shared Function GenerateMagicNumber(ByVal age As Integer, _
                                                ByVal height As Integer) As Integer
            Return SecretGenerateMagicNumber(age, height)
        End Function

        Private Shared Function SecretGenerateMagicNumber(ByVal age As Integer, _
                                                ByVal height As Integer) As Integer
            Return (age * height) + Today.DayOfWeek
        End Function
    End Class
End Namespace

After rebuilding the application, you need to reopen the Dotfuscator project by selecting it from the Recent Projects list. You have several different ways to selectively apply obfuscation to an assembly. First, you can enable Library mode on specific assemblies by selecting the appropriate check box on the Inputs screen (see Figure 34-4). This has the effect of keeping the namespace, class name, and all public properties and methods intact, while renaming all private methods and variables. Second, you can manually select which elements should not be renamed from within Dotfuscator. To do this, open the Renaming item from the navigation tree, as shown in Figure 34-6.

Illustration of Renaming item from the navigation tree.

FIGURE 34-6

The Renaming dialog opens on the Exclusions tab where you can see the familiar tree view of your assembly with the attributes, namespaces, types, and methods listed. As the name of the tab suggests, this tree enables you to exclude certain elements from being renamed. The GenerateMagicNumber method (refer to Figure 34-6), as well as the class that it is contained in, is excluded. (Otherwise, you would have ended up with something like b.GenerateMagicNumber, where b is the renamed class.) In addition to explicitly choosing which elements will be excluded, you can also define custom rules that can include regular expressions.

After you build the Dotfuscator project, click the Results item in the navigation tree. This screen shows the actions that Dotfuscator performed during obfuscation. The new name of each class, property, and method displays as a subnode under each renamed element in the tree. You can see that the MathematicalGenius class and the GenerateMagicNumber method have not been renamed, as shown in Figure 34-7.

Illustration of MathematicalGenius class and the GenerateMagicNumber method.

FIGURE 34-7

The SecretGenerateMagicNumber method has been renamed to a, as indicated by the subnode with the Dotfuscator icon.

Obfuscation Attributes

In the previous example you saw how to choose which types and methods to obfuscate within Dotfuscator. Of course, if you were to start using a different obfuscating product, you must configure it to exclude the public members. It would be more convenient to annotate your code with attributes indicating whether a symbol should be obfuscated. You can do this by using the Obfuscation and ObfuscationAssemblyAttribute attributes from the System.Reflection namespace.

The default behavior in Dotfuscator is to override exclusions specified in the project with the settings specified by any obfuscation attributes. Refer to Figure 34-4 to see a series of check boxes for each assembly added to the project, of which one is Honor Obfuscation Attributes. You can change the default behavior so that any exclusions set within the project take precedence by unchecking the Honor Obfuscation Attributes option on a per-assembly basis.

ObfuscationAssemblyAttribute

The ObfuscationAssemblyAttribute attribute can be applied to an assembly to control whether it should be treated as a class library or as a private assembly. The distinction is that with a class library it is expected that other assemblies will be referencing the public types and methods it exposes. As such, the obfuscation tool needs to ensure that these symbols are not renamed. Alternatively, as a private assembly, every symbol can be potentially renamed. The following is the syntax for ObfuscationAssemblyAttribute:

C#

[assembly: ObfuscateAssemblyAttribute(false, StripAfterObfuscation=true)]

VB

<Assembly: ObfuscateAssemblyAttribute(False, StripAfterObfuscation:=True)>

The two arguments that this attribute takes indicate whether it is a private assembly and whether to strip the attribute off after obfuscation. The preceding snippet indicates that this is not a private assembly and that public symbols should not be renamed. In addition, the snippet indicates that the obfuscation attribute should be stripped off after obfuscation — after all, the less information available to anyone wanting to decompile the assembly, the better.

Adding this attribute to the AssemblyInfo.cs or AssemblyInfo.vb file automatically preserves the names of all public symbols in the ObfuscationSample application. This means that you can remove the exclusion you created earlier for the GenerateMagicNumber method.

ObfuscationAttribute

The downside of the ObfuscationAssemblyAttribute attribute is that it exposes all the public types and methods regardless of whether they existed for internal use only. On the other hand, the ObfuscationAttribute attribute can be applied to individual types and methods, so it provides a much finer level of control over what is obfuscated. To illustrate the use of this attribute, refactor the example to include an additional public method, EvaluatePerson, and place the logic into another class, HiddenGenius:

C#

namespace ObfuscationSample
{

    [System.Reflection.ObfuscationAttribute(ApplyToMembers=true, Exclude=true)]
    public class MathematicalGenius
    {
        public static Int32 GenerateMagicNumber(Int32 age, Int32 height)
        {
            return HiddenGenius.GenerateMagicNumber(age, height);
        }

        public static Boolean EvaluatePerson(Int32 age, Int32 height)
        {
            return HiddenGenius.EvaluatePerson(age, height);
        }
    }

    [System.Reflection.ObfuscationAttribute(ApplyToMembers=false, Exclude=true)]
    public class HiddenGenius
    {
        public static Int32 GenerateMagicNumber(Int32 age, Int32 height)
        {
            return (age * height) + DateTime.Now.DayOfYear;
        }

        [System.Reflection.ObfuscationAttribute(Exclude=true)]
        public static Boolean EvaluatePerson(Int32 age, Int32 height)
        {
            return GenerateMagicNumber(age, height) > 6000;
        }
    }
}

VB

Namespace ObfuscationSample
    <System.Reflection.ObfuscationAttribute(ApplyToMembers:=True,Exclude:=True)> _
    Public Class MathematicalGenius
        Public Shared Function GenerateMagicNumber(ByVal age As Integer, _
                                                ByVal height As Integer) As Integer
            Return HiddenGenius.GenerateMagicNumber(age, height)
        End Function

        Public Shared Function EvaluatePerson(ByVal age As Integer, _
                                                ByVal height As Integer) As Boolean
            Return HiddenGenius.EvaluatePerson(age, height)
        End Function
    End Class

    <System.Reflection.ObfuscationAttribute(ApplyToMembers:=False,Exclude:=True)> _
    Public Class HiddenGenius
        Public Shared Function GenerateMagicNumber(ByVal age As Integer, _
                                                ByVal height As Integer) As Integer
            Return (age * height) + Today.DayOfWeek
        End Function

        <System.Reflection.ObfuscationAttribute(Exclude:=True)> _
        Public Shared Function EvaluatePerson(ByVal age As Integer, _
                                                ByVal height As Integer) As Boolean
            Return GenerateMagicNumber(age, height) > 6000
        End Function
    End Class
End Namespace

In this example, the MathematicalGenius class is the class that you want to expose outside of this library. As such, you want to exclude this class and all its methods from being obfuscated. You do this by applying the ObfuscationAttribute attribute with both the Exclude and ApplyToMembers parameters set to True.

The second class, HiddenGenius, has mixed obfuscation. As a result of some squabbling among the developers who wrote this class, the EvaluatePerson method needs to be exposed, but all other methods in this class should be obfuscated. Again, the ObfuscationAttribute attribute is applied to the class so that the class does not get obfuscated. However, this time you want the default behavior to be such that symbols contained in the class are obfuscated, so the ApplyToMembers parameter is set to False. In addition, the Obfuscation attribute is applied to the EvaluatePerson method so that it will still be accessible.

Words of Caution

In a couple of places it is worth considering what can happen when obfuscation — or more precisely, renaming — occurs, and how it can affect the workings of the application.

Reflection

The .NET Framework provides a rich reflection model through which types can be queried and instantiated dynamically. Unfortunately, some of the reflection methods use string lookups for type and member names. Clearly, the use of renaming obfuscation prevents these lookups from working, and the only solution is not to mangle any symbols that may be invoked using reflection. Note that control flow obfuscation does not have this particular undesirable side-effect. Dotfuscator’s smart obfuscation feature attempts to automatically determine a limited set of symbols to exclude based on how the application uses reflection. For example, say that you use the field names of an enum type. Smart obfuscation can detect the reflection call used to retrieve the enum’s field name and then automatically exclude the enum fields from renaming.

Strongly Named Assemblies

One of the purposes behind giving an assembly a strong name is that it prevents the assembly from being tampered with. Unfortunately, obfuscating relies on taking an existing assembly and modifying the names and code flow before generating a new assembly. This would mean that the assembly no longer has a valid strong name. To allow obfuscation to occur, you need to delay signing of your assembly by checking the Delay Sign Only check box on the Signing tab of the Project Properties window, as shown in Figure 34-8.

Illustration of Signing tab of the Project Properties window.

FIGURE 34-8

After building the assembly, you can then obfuscate it in the normal way. The only difference is that after obfuscating you need to sign the obfuscated assembly, which you can do manually using the Strong Name utility, as shown in this example:

sn -R ObfuscationSample.exe ObfuscationKey.snk

Debugging with Delayed Signing

As displayed on the Project Properties window, checking the Delay Sign Only box prevents the application from being able to be run or debugged. This is because the assembly will fail the strong-name verification process. To enable debugging for an application with delayed signing, you can register the appropriate assemblies for verification skipping. This is also done using the Strong Name utility. For example, the following code skips verification for the ObfuscationSample.exe application:

sn -Vr ObfuscationSample.exe

Similarly, the following reactivates verification for this application:

sn -Vu ObfuscationSample.exe

This is a pain for you to do every time you build an application, so you can add the following lines to the post-build events for the application:

"$(DevEnvDir)......Microsoft SDKsWindowsv7.0AinNETFX 4.0 Toolssn.exe" -Vr
"$(TargetPath)"
"$(DevEnvDir)......Microsoft SDKsWindowsv7.0AinNETFX 4.0 Toolssn.exe" -Vr
"$(TargetDir)$(TargetName).vshost$(TargetExt)"

The first line skips verification for the compiled application. However, Visual Studio uses an additional vshost file to bootstrap the application when it executes. This also needs to be registered to skip verification when launching a debugging session.

APPLICATION MONITORING AND MANAGEMENT

The version of Dotfuscator that ships with Visual Studio 2017 has a lot of functionality for adding run-time monitoring and management functionality to your applications. As with obfuscation, these capabilities are injected into your application as a post-build step, which means you typically don’t need to modify your source code in any way to take advantage of them.

The application monitoring and management capabilities include

  • Tamper Defense: Exits your application and optionally notifies you if it has been modified in an unauthorized manner.
  • Application Expiry: Configure an expiration date for your application, after which it will no longer run.
  • Application Usage Tracking: Instrument your code to track usage, including specific features within your application.

Only the Tamper Defense functionality is discussed in this book (in the “Tamper Defense” section later in this chapter). A different technique for tracking application feature usage is discussed in the “Application Instrumentation and Analytics” section, also later in this chapter.

Although you can use the Honor Instrumentation Attributes check box to turn on and off the injection of the instrumentation code (visible in Figure 34-4), the default behavior is to have instrumentation enabled.

Specifying the functionality to be injected into your application is accomplished by adding Dotfuscator attributes — either as a custom attribute within your source code or through the Dotfuscator UI.

Tamper Defense

Tamper defense provides a way for you to detect when your applications have been modified in an unauthorized manner. Whereas obfuscation is a preventative control designed to reduce the risks that stem from unauthorized reverse engineering, tamper defense is a detective control designed to reduce the risks that stem from unauthorized modification of your managed assemblies. The pairing of preventative and detective controls is a widely accepted risk management pattern, for example, fire prevention and detection.

Tamper defense is applied on a per-method basis, and tamper detection is performed at run time when a protected method is invoked.

To add tamper defense to your application, select the Analytics node under the Configuration Options portion of the navigation menu and then select the Attributes tab. You see a tree that contains the assemblies you have added to the Dotfuscator project with a hierarchy of the classes and methods that each assembly contains. Navigate to the HiddenGenius.GenerateMagicNumber function, right-click it, and select Add Attribute. This displays the list of available Dotfuscator attributes, as shown in Figure 34-9.

Illustration of list of available Dotfuscator attributes.

FIGURE 34-9

Select the InsertTamperCheckAttribute attribute, and click OK. The attribute is added to the selected method. You can now build the Dotfuscator project to inject the tamper defense functionality into your application.

To help you test the tamper defense functionality, Dotfuscator ships with a simple utility that simulates tampering of an assembly. Called TamperTester, you can find this utility in the same directory in which Dotfuscator is installed (by default at C:Program FilesMicrosoft Visual Studio 15.0PreEmptive SolutionsDotfuscator and Analytics Community Edition). This should be run from the command line with the name of the assembly and the output folder as arguments:

tampertester ObfuscationSample.exe c:	amperedapps

By default, your application immediately exits if the method has been tampered with. You can optionally configure Dotfuscator to generate a notification message to an endpoint of your choosing. The commercial edition of Dotfuscator includes two primary extensions to the CE version; it enables you to add a custom handler to be executed when tampering is detected, supporting a custom real-time tamper defense in lieu of the default exit behavior; and PreEmptive Solutions offers a notification service that accepts tamper alerts and automatically notifies your organization as an incident response.

Application Instrumentation and Analytics

As a developer, the goal is to build an application that meets your users’ needs while reducing any issues that might be encountered. To meet this aim, it is important to be able to gain an understanding of what your users are experiencing, both good and bad, in your application. That’s where analytics come into play. Analytics are capable of providing a full view of your application. This includes not only any exceptions or other unexpected behavior, but also which parts of the application are being used.

For analytics to be useful, there needs to be a mechanism to both capture and report on them. As part of the Azure platform, Microsoft provides the Application Insights platform. Application Insights is not new; it was part of Visual Studio Online. Now it has been integrated into Azure and is available through the Azure portal.

For your application to participate, you need to instrument your application appropriately. Fortunately, Visual Studio 2017 includes a couple of tools to make this easier.

Depending on the type of project that you created, the Application Insights SDK is automatically included in your reference list. For existing projects (and, in general, Application Insights is anticipated to be used with web or UWP applications), you can add the Application Insights SDK by selecting Add Application Insights from the context menu for the project from within Solution Explorer. Once the SDK is available, you’ll need to configure Application Insights. Again, through the context menu in the project, select Configure Application Insights. This shows a screen similar to what you see in Figure 34-10.

Illustration of Configure Application Insights.

FIGURE 34-10

One you have logged in to your Azure subscription, there are two additional options available to you. If your account is associated with multiple Azure subscriptions, select the subscription you want used for this project. Also, you can specify the resource to which the Application Insights telemetry should be sent. If you are creating a new resource, clicking the Configure Settings link reveals the dialog that appears in Figure 34-11. Through this, you can specify the Resource Group (which corresponds to the region in which the telemetry will be gathered), the name of the resource, and the region in which the service will be hosted.

Illustration of Configure Settings link.

FIGURE 34-11

If you look at the difference that adding Application Insights to your project made, you’ll find that it’s not significant. There is a configuration file (called ApplicationInsights.config) that contains information about the telemetry modules and the classes that are used to generate the data. It also includes the secret key that is used to communicate with your Azure account.

The second addition is dependent on the type of application that you created. In the example, it was an ASP.NET MVC Web application. To allow for telemetry to be sent, a small JavaScript script is added to the _Layout.cshml file. This script instantiates an appInsights object and invokes the tracePageView method. This sends a page view event to the Application Insights resource.

For different types of applications, the mechanism for sending the telemetry details will change. The ApplicationInsights.config file is consistent across the different projects. However, whereas ASP.NET web applications have an obvious place to put the tracePageView call, that is not the case with a Universal Windows Platform application. Instead, these applications create a property named TelemetryClient at the Application level. Then you can instrument your application with calls to the TrackPageView, TrackEvent, or other methods to push different metrics from your application to the Application Insights resource.

SUMMARY

This chapter introduced two tools — ILDasm and JustDecompile — which demonstrated how easy it is to reverse-engineer .NET assemblies and learn their inner workings. You also learned how to use Dotfuscator and Application Insights to do the following:

  • Protect your intellectual property using obfuscation
  • Harden your applications against modification using tamper defense
  • Add telemetry to your application

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.172.252