Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Using ConfuserEx to Protect Your xamarin.forms App

3.62/5 (3 votes)
8 Aug 2019CPOL2 min read 11.6K  
Using ConfuserEx to protect your xamarin.forms app

Introduction

This tip is about how to use ConfuserEx to protect your DLL. This will also be able to provide a way for you to use ConfuserEx with Xamarin.Forms and Android.

Background

I have finished building my app, and then decided to look for a way to protect my DLL.

There were many Confucate software and most of them don't support Xamarin.forms and MsBuild.

Those that support Xamarin.forms are mostly paid applications, with limited features.

I decided to look for an opensource project that could do the job for me.

And I found ConfuserEx that has advanced features like encrypting strings, property rename, etc.

You could read about ConfuserEx feature here.

Using the Code

For you to be able to use ConfuserEx with Visual Studio 2019, we would need to use MSBuild.

Now ConfuserEx has its own way on using it. The problem with that is, those protected DLLs won't be included in the apk.

So for you to be able to Confucate your project, you will have to create the apk, then disassemble it, then Confucate your DLL and then recreate the apk.

The whole process is a pain, and also that is why we use Visual Studio so we could skip those steps.

So let's start by building a Task that will confucate our DLL and copy it to the right place.

So that Visual Studio will be able to find that confucated DLL and include it in the apk when we archive.

Start building a project class library and name it however you like. In this example, we would be using name Tasker.

Create a class and name it ConfuserEx that inherits from Microsoft.Build.Utilities.Task:

C#
using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Microsoft.Build.Utilities;

namespace Tasker
{
    public class ConfuserEx : Task
    {
        // the full path to Confuser.CLI.exe
        public string ApplicationPath { get; set; }
          
        //The DLL source eg $(TargetPath)
        public string Source { get; set; }
         
        // Included modules eg DLL
        // example test1.dll; test2.dll;
        public string Modules { get; set; }

        // This could be "linksrc" or "asset" that exists in obj folder
        public string AssetMap { get; set; }
        
        // Xamarin/Mono for Android uses their own version of internal library, 
        // so you should tell ConfuserEx where to find it.
        public string MonoDir { get; set; }

        private string _outputDir { get; set; }

        private string _baseDir { get; set; }

        // Include AntiDebug
        public bool AntiDebug { get; set; } = true;

        // Include AntiIldasm
        public bool AntiIldasm { get; set; } = true;

        // Include Constants
        public bool Constants { get; set; } = true;

        // Include CtrlFlow
        public bool CtrlFlow { get; set; } = true;

        // Include RefProxy
        public bool RefProxy { get; set; } = true;
        
        // Include Rename
        public bool ReName { get; set; } = true;
        
        // Protect the Resources
        public bool Resources { get; set; } = true;

        public override bool Execute()
        {
                
        }
     }
}

Now we would need to look for our Modules path. Source contains path to bin map that we are not interested in right now.

bin map does not contain all the external DLLs.

Create a method and name it SetDir:

C#
private bool Setdir()
{
    var module = Modules.Split(';').FirstOrDefault();
    var directory = Path.GetDirectoryName(Source.Replace("\\bin", "\\obj"));
    var d = new DirectoryInfo(Path.GetDirectoryName(directory)).GetFiles
    ($"*{module}*", SearchOption.AllDirectories).FirstOrDefault();
    if (d != null)
    {
        module = d.Name;
        _outputDir = Path.Combine(Path.GetDirectoryName(d.FullName), "ConfuserEx_Temp");
        
        if (string.IsNullOrEmpty(AssetMap))
            _baseDir = Path.GetDirectoryName(d.FullName);
        else
        {
            var doc = new DirectoryInfo(Path.GetDirectoryName(d.FullName));
            var assetDir = doc.GetDirectories
            ($"*{AssetMap}*", SearchOption.AllDirectories).FirstOrDefault();
            if (assetDir != null)
            {
                base.Log.LogMessage(MessageImportance.High, "Found AssetDir :" + 
                                    assetDir.FullName);
                _baseDir = assetDir.FullName;
                
            }
            else
            {
                _baseDir = Path.GetDirectoryName(d.FullName);
                base.Log.LogMessage(MessageImportance.High, 
                "AssetMap could not be found :" + AssetMap + " using " + 
                _baseDir + " insetad");
            }
        }
        Source = Path.Combine(Path.GetDirectoryName(Source), module);
        foreach (var m in Modules.Split(';'))
        {
            var p = Path.Combine(Path.GetDirectoryName(Source), m);
            if (File.Exists(p))
            {
                var path = d.FullName;
                var sub = Path.Combine(Path.GetDirectoryName(d.FullName), m);
                if (File.Exists(sub))
                    File.Delete(sub);
                File.Copy(p, sub);
            }
        }
        return true;
    }
    
    return false;
}    

ConfuserEx uses a crproj file, to configure your rules.

So let's start by creating a method that will create this file, and provide the rules for ConfuserEx to use:

C#
private void CreateProjectFile()
{
    if (!Directory.Exists(Path.GetDirectoryName(settingsPath)))
        Directory.CreateDirectory(Path.GetDirectoryName(settingsPath));
    if (!File.Exists(settingsPath))
    {
        var str = new StringBuilder()
            .AppendLine($"<project debug=\"false\" 
            outputDir=\"{_outputDir}\" baseDir=\"{_baseDir}\" 
            xmlns=\"http://confuser.codeplex.com\">")
            .AppendLine("  <rule pattern=\"true\" 
            preset=\"normal\" inherit =\"false\">")
            .AppendLine($"           <protection id=\"anti debug\" 
            action=\"{(AntiDebug ? "add" : "remove")}\" />")
            .AppendLine($"           <protection id=\"anti ildasm\" 
            action=\"{(AntiIldasm ? "add" : "remove")}\" />")
            .AppendLine($"           <protection id=\"constants\" 
            action=\"{(Constants ? "add" : "remove")}\" />")
            .AppendLine($"           <protection id=\"ctrl flow\" 
            action=\"{(CtrlFlow ? "add" : "remove")}\" />")
            .AppendLine($"           <protection id=\"ref proxy\" 
            action=\"{(RefProxy ? "add" : "remove")}\" />")
            .AppendLine($"           <protection id=\"resources\" 
            action=\"{(Resources ? "add" : "remove")}\" />");
        if (ReName)
        {
            str.AppendLine("           <protection id=\"rename\">")
               .AppendLine("               <argument name=\"mode\" 
               value=\"letters\" />")
               .AppendLine("           </protection>");
        }
        else
        {
            str.AppendLine("           <protection id=\"rename\" 
            action=\"remove\" />");
        }
        str.AppendLine("  </rule>");
        
        foreach (var m in Modules.Split(';'))
        {
            str.AppendLine($"  <module path=\"{m}\" />");
        }
        /// Add External dll
        var externalDll = new DirectoryInfo(_baseDir).GetFiles($"*.dll*");
        foreach (var dll in externalDll)
        {
            var fileName = dll.Name;
            if (!Modules.Contains(fileName))
                str.AppendLine($"  <module path=\"{fileName}\"
                external=\"true\" />");
        }
        
        if (!string.IsNullOrEmpty(MonoDir))
        {
            var numreg = new Regex("([^0-9])");
            var docs = new DirectoryInfo(MonoDir).GetDirectories();
            
            str.AppendLine($"   <probePath>{docs.OrderBy(x => Convert.ToInt32
            (numreg.Replace(x.Name, ""))).FirstOrDefault().FullName}</probePath>");
            str.AppendLine($"   <probePath>{docs.OrderBy(x => Convert.ToInt32
            (numreg.Replace(x.Name, ""))).LastOrDefault().FullName}</probePath>");
        }
        
        str.AppendLine("</project>");
        File.WriteAllText(settingsPath, str.ToString());
    }
    else // Validate changes
    {
        XDocument xd = XDocument.Load(settingsPath);
        if (!string.Equals(xd.Root.Attribute("outputDir").Value, _outputDir) || 
        !string.Equals(xd.Root.Attribute("baseDir").Value, _baseDir))
        {
            File.Delete(settingsPath);
            CreateProjectFile();
            return;
        }
        
        var protectors = xd.Descendants().Where
        (x => string.Equals("protection", x.Name.LocalName)).ToList();
        base.Log.LogMessage(MessageImportance.High, "Found protectors :" + protectors.Count);
        foreach (XElement item in protectors)
        {
            var id = item.Attribute("id")?.Value;
            var action = item.Attribute("action")?.Value ?? "";
            var actionName = "";
            switch (id)
            {
                case "anti debug":
                    actionName = AntiDebug ? "add" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                    
                case "anti ildasm":
                    actionName = AntiIldasm ? "add" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                    
                case "constants":
                    actionName = Constants ? "add" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                    
                case "ctrl flow":
                    actionName = CtrlFlow ? "add" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                    
                case "ref proxy":
                    actionName = RefProxy ? "add" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                    
                case "rename":
                    actionName = ReName ? "" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
                case "resources":
                    actionName = Resources ? "" : "remove";
                    if (action != actionName)
                    {
                        File.Delete(settingsPath);
                        CreateProjectFile();
                        return;
                    }
                    break;
            }
        }
    }
}    

Now that we created our crproj file, we could start ConfuserEx to Confucate our DLL, then copy them to the right place:

C#
public override bool Execute()
{
    try
    {
        if (!Setdir())
        {
            base.Log.LogMessage(MessageImportance.High, "Could not find :" + 
            Modules.Split(';').FirstOrDefault() + " please rebuild");
            return false;
        }
        var nettype = Path.GetFileNameWithoutExtension(Source);
        settingsPath = Path.Combine(new DirectoryInfo
        (Path.GetDirectoryName(Source)).Parent.Parent.FullName, 
        "ConfuserEx_Settings", nettype + "_ConfuserEx_Project.crproj");
        while (Await())
            Thread.Sleep(300);
        Lock();
        CreateProjectFile();
        var applicationPath = Path.GetDirectoryName(ApplicationPath);
        var name = Path.GetFileName(ApplicationPath);
        var args = $"-n \"{settingsPath}\" 
        {(StayOpen ? "/K" : "")}";
        var directory = Path.GetDirectoryName(Source);
        var process = new Process
        {
            StartInfo = new ProcessStartInfo()
            {
                Arguments = args,
                FileName = name,
                WorkingDirectory = applicationPath,
                WindowStyle = Hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal
            }
        };
        
        var exited = false;
        process.EnableRaisingEvents = true;
        process.Exited += new EventHandler((o, e) =>
        {
            exited = true;
        });
        string counter = ".";
        base.Log.LogMessage(MessageImportance.High, "Obfuscation-Start:" + Source);
        base.Log.LogMessage(MessageImportance.High, "ApplicationWorkingDirectory:" + 
                            applicationPath);
        base.Log.LogMessage(MessageImportance.High, "ApplicationFileName:" + name);
        base.Log.LogMessage(MessageImportance.High, "Args:" + args);
        process.Start();
        const int SleepAmount = 100;
        var elapsedTime = 0;
        while (!exited)
        {
            base.Log.LogMessage(MessageImportance.High, counter);
            counter += ".";
            elapsedTime += SleepAmount;
            Thread.Sleep(SleepAmount);
        }
        
        Thread.Sleep(1000);
        // Copy and replace the output dll to bin and obj folders
        if (Directory.Exists(_outputDir))
        {
            List<string> alreadyCopied = new List<string>();
            var files = new DirectoryInfo(_outputDir).GetFiles
                        ("*.*", SearchOption.AllDirectories);
            if (files.Any())
            {
                foreach (var file in files)
                {
                    if (Source.Contains(file.Name))
                    {
                        base.Log.LogMessage(MessageImportance.High, 
                        $"Copy:{file.FullName} To {Source}");
                        file.CopyTo(Source, true);
                        base.Log.LogMessage(MessageImportance.High, 
                        $"Copy:{file.FullName} To {Path.Combine(_baseDir, file.Name)}");
                        file.CopyTo(Path.Combine(_baseDir, file.Name), true);
                        alreadyCopied.Add(Path.Combine(_baseDir, file.Name));
                    }
                    else
                    {
                        base.Log.LogMessage(MessageImportance.High, 
                        $"Copy:{file.FullName} To {Path.Combine(directory, file.Name)}");
                        file.CopyTo(Path.Combine(directory, file.Name), true);
                        base.Log.LogMessage(MessageImportance.High, 
                        $"Copy:{file.FullName} To {Path.Combine(_baseDir, file.Name)}");
                        file.CopyTo(Path.Combine(_baseDir, file.Name), true);
                        alreadyCopied.Add(Path.Combine(_baseDir, file.Name));
                    }
                    var dic = new DirectoryInfo(Path.GetDirectoryName
                    (Source.Replace("\\bin", "\\obj"))).GetFiles
                    ($"*{file.Name}*", SearchOption.AllDirectories);
                    foreach (var fullName in dic.Where(x => !alreadyCopied.Any
                    (a => a == x.FullName)).Select(x => x.FullName))
                    {
                        if (!fullName.Contains(_outputDir))
                            file.CopyTo(fullName, true);
                    }
                }
            }
        }
        base.Log.LogMessage(MessageImportance.High, "Obfuscation-Finish:" + Source);
        
        return true;
    }
    finally
    {
        DeleteFolder(_outputDir); // delete the temp map 
    }
}

// Remove folder and all its content
protected void DeleteFolder(string dir)
{
    if (Directory.Exists(dir))
    {
        var dic = new DirectoryInfo(dir);
        foreach (var file in dic.GetFiles("*.*", SearchOption.AllDirectories))
            file.Delete();
        try
        {
            dic.Delete(true);
        }
        catch
        {
        
        }
    }
}    

Our task is now done, build it, then close the project.

Open your .droid project and edit your project file.

Add those lines at the bottom:

XML
<UsingTask TaskName="Tasker.ConfuserEx" 
AssemblyFile="E:\Projects\Tasker\bin\Release\netstandard2.0\tasker.dll" />

<Target Name="PostBuild" AfterTargets="PostBuildEvent; PreBuildEvent" 
Condition="'$(Configuration)'=='Release' And Exists('$(TargetPath)')">
<ConfuserEx ApplicationPath="E:\Programs\ConfuserEx\Confuser.CLI.exe"
Source="$(TargetPath)"
AssetMap="linksrc"
RefProxy="false"
ReName="false"
Modules="test1.dll;test2.dll"
MonoDir="C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\
Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid" />
</Target>

Now build and deploy your app.

History

  • 9th August, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)