How to obfuscate ASP.NET?

In this guide, you will learn how to protect your ASP.NET web application using a cross-platform obfuscator.

Contents

Why is it important to obfuscate web applications?

ASP.NET enables developers to create fully functional web applications as well as REST APIs. As a cross-platform solution based on .NET Core, ASP.NET supports diverse development environments. However, like any other .NET application, your code, even in binary form, is susceptible to decompilation. This means that anyone with access to your DLLs can reconstruct the original C# code. Free tools such as dotPeek and ILSpy facilitate this process. Therefore, it is essential to obfuscate your .NET assemblies. Obfuscation is the process of transforming original code into a form that is nearly impossible to understand. This is achieved by adding extraneous code, altering the logic of conditional and unconditional branches, and utilizing virtualization. Virtualization creates a unique virtual machine for each method, executing the original instructions which have been converted to an internal representation unknown to hackers. In this article, we will develop a simple ASP.NET web app that validates a password submitted via a query.

Create an ASP.NET web app that checks passwords

Launch Visual Studio, click on ‘New’ and then ‘Project’, and select ‘ASP.NET Core Web API’. Then, enable the following options: ‘Enable OpenAPI support’, ‘Do not use top-level statements’, and ‘Use controllers’. Allow Visual Studio to generate the project skeleton.

Add two plain classes: ‘Password’ for the query and ‘PasswordValidationResult’ for the validation results, as shown below:

public class Password
{
    public string? Value { get; set; }
}

public class PasswordValidationResult
{
    public bool Valid { get; set; }
}

Now, let’s add a new controller named CheckPasswordController:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.Text;

namespace WebApplication1.Controllers
{
    [ArmDot.Client.VirtualizeCode]
    [ApiController]
    [Route("[controller]")]
    public class CheckPasswordController : ControllerBase
    {
        [HttpGet(Name = "ValidatePassword")]
        public PasswordValidationResult Get([FromQuery] Password password)
        {
            return new PasswordValidationResult
            {
                Valid = false
            };
        }
    }
}

As you can see, it currently returns false for all passwords. Build the project and run it to ensure that our API is functioning as expected: no matter what password is entered, the API returns that the entered password is invalid.

To validate a password, let’s calculate its hash:

[HttpGet(Name = "ValidatePassword")]
public PasswordValidationResult Get([FromQuery] Password password)
{
    var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(password.Value)));

    return new PasswordValidationResult
    {
        Valid = false
    };
}

Set a breakpoint to observe the correct value of the hash. Build and run the project, enter ‘ArmDot’, and click ‘Execute’. You should be redirected to Visual Studio; now you can view the hash value:

The value of valid hash

Modify the handler so that it compares the hash value with the correct one:

[HttpGet(Name = "ValidatePassword")]
public PasswordValidationResult Get([FromQuery] Password password)
{
    var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(password.Value)));

    return new PasswordValidationResult
    {
        Valid = hash == "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0="
    };
}

Run the project and check that it works correctly: enter ‘ArmDot’ and verify that the API returns ‘true’ for this password.

Now let’s run ILSpy and see what the code for the password validation algorithm looks like:

The algorithm code is not protected

As you can see, the code is easily readable, which poses a major problem: anyone can inspect this code. This is where a .NET obfuscator comes to the rescue!

How do you enable obfuscation?

To obfuscate a web app, you need to add two packages: ArmDot.Client and ArmDot.Engine.MSBuildTasks. ArmDot.Client contains obfuscation attributes that can be applied to classes, methods, or even an entire assembly, to direct the obfuscator on what actions to take. ArmDot.Engine.MSBuildTasks provides an obfuscation task that you specify in your project file so that when the compiler outputs a .NET assembly, the obfuscator can immediately begin its work.

After adding the packages, open the project file for editing and add the obfuscation task as shown below:

	<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>
	
</Project>

Finally, mark the password validation method with the attribute ‘ArmDot.Client.VirtualizeCode’ so that the obfuscator will virtualize the code of this method:

[ArmDot.Client.VirtualizeCode]
[HttpGet(Name = "ValidatePassword")]
public PasswordValidationResult Get([FromQuery] Password password)
{
    var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(password.Value)));

    return new PasswordValidationResult
    {
        Valid = hash == "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0="
    };
}

Run the project and check if it works correctly.

Now run ILSpy once again and navigate to the password validation method. What do we see? The code is completely obfuscated!

Obfuscated code

Conclusion

ASP.NET web apps, being .NET applications, need to be obfuscated if you are not willing to expose your code to everyone in the world. .NET obfuscators offer several obfuscation techniques, including name obfuscation, control flow obfuscation, and the most advanced method—virtualization. After virtualization, the code becomes very hard to decipher because the original instructions are converted to an internal form, which is unique each time. This leaves no opportunity for a hacker to access your algorithm.