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
:
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
{
public string ApplicationPath { get; set; }
public string Source { get; set; }
public string Modules { get; set; }
public string AssetMap { get; set; }
public string MonoDir { get; set; }
private string _outputDir { get; set; }
private string _baseDir { get; set; }
public bool AntiDebug { get; set; } = true;
public bool AntiIldasm { get; set; } = true;
public bool Constants { get; set; } = true;
public bool CtrlFlow { get; set; } = true;
public bool RefProxy { get; set; } = true;
public bool ReName { get; set; } = true;
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
:
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:
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}\" />");
}
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
{
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:
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);
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);
}
}
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:
<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