What is Mono.Cecil?
In the following tutorial I am going to explain how you can code your own
tiny string obfuscator with Mono.Cecil.
Mono.Cecil was created by jbevain and is an open source project available on GitHub
Mono.Cecil is a library to generate and inspect programs and libraries in the ECMA CIL form.
To put it simply, you can use Cecil to:
- Analyze .NET binaries using a simple and powerful object model, without having to load assemblies to use Reflection.
- Modify .NET binaries, add new metadata structures and alter the IL code.
So with this lib we are able to modify .net programs, after they have been compiled into a executable.
The lib is very powerful and can do a lot of stuff.
Today I am going to explain how it can be used to encrypt/encode the string in your .net program.
Strings in a .Net assembly
To do this we are going to create a small test application:
The strings of this application we are going to secure.
At the current state all the strings can be seen after compilation by using a decompiler like IlSpy/SAE/Reflector.
If we think of these strings being a password / passkey a hacker would have an easy job just by finding out the password using a decompiler.
If we encrypt this string it becomes harder to find out the key.
So what we want to do is encrypt the string and decrypt it on runtime.
And thats what we are using Mono.Cecil for.
Getting Started
Download Mono.Cecil from GitHub and compile the dll file.
Now create a new console application in visual studio and add Mono.Cecil.dll as a reference.
Make sure to add these imports:
Code:
using Mono.Cecil;
using Mono.Cecil.Cil;
Once again, what we have at the moment in the target assembly is something like this:
ldstr "Hello"
Call Console.WriteLine(System.String)
But what we want to get is this:
ldstr "EncryptedString"
Call DecryptFunction()
Call Console.WriteLine(System.String)
So what we need to do is :
- Find all strings (ldstr) in our target assembly.
- Change the string to an encrypted string
- Inject a method which performs the decryption
- Call the decryption method after our string got pushed to the stack
Add this function to your project:
Code:
public static string Encode(string str)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
}
This is not a very strong encryption but you can easily change this to AES/RSA etc.
For the sake of this tutorial it should be alright.
Next what we add to our Main() method is this:
Code:
AssemblyDefinition AssemblyDef = AssemblyDefinition.ReadAssembly(@"C:\Users\Admin\Desktop\MyFile.exe");
This loads our target file we like to secure.
Now your project should look like this:
Injecting the decrypter
Now we need to inject a decrypt method (Our decode Basic64 method) into our target assembly.
To do this you can use this function:
Code:
private static MethodDefinition InjectDecrypter(AssemblyDefinition AssemblyDef)
{
foreach (ModuleDefinition ModuleDef in AssemblyDef.Modules)
{
foreach (TypeDefinition TypeDef in ModuleDef.GetTypes())
{
if (TypeDef.Name == "<Module>")
{
MethodDefinition MethodDef = CreateDecrypter(AssemblyDef);
TypeDef.Methods.Add(MethodDef);
return MethodDef;
}
}
}
throw new Exception("Decrypter not injected.");
}
private static MethodDefinition CreateDecrypter(AssemblyDefinition AssemblyDef)
{
MethodDefinition Decrypt = new MethodDefinition("Decrypt", MethodAttributes.Public | MethodAttributes.Static, AssemblyDef.MainModule.Import(typeof(string)));
Decrypt.Parameters.Add(new ParameterDefinition(AssemblyDef.MainModule.Import(typeof(string))));
List<Instruction> Body = new List<Instruction>();
Body.Add(Instruction.Create(OpCodes.Call, AssemblyDef.MainModule.Import(typeof(System.Text.Encoding).GetMethod("get_UTF8"))));
Body.Add(Instruction.Create(OpCodes.Ldarg_0));
Body.Add(Instruction.Create(OpCodes.Call, AssemblyDef.MainModule.Import(typeof(System.Convert).GetMethod("FromBase64String", new Type[] { typeof(string) }))));
Body.Add(Instruction.Create(OpCodes.Callvirt, AssemblyDef.MainModule.Import(typeof(System.Text.Encoding).GetMethod("GetString", new Type[] { typeof(byte[]) }))));
Body.Add(Instruction.Create(OpCodes.Ret));
foreach (Instruction Instr in Body)
{
Decrypt.Body.Instructions.Add(Instr);
}
return Decrypt;
}
We are rebuilding a Base64 Decode method by hand.
This method we are going to inject:
Code:
MethodDefinition MD = InjectDecrypter(AssemblyDef);
Encrypting our strings
Now we like to peek into the assembly and find all strings.
To do that we need to iterate through all Modules, Types Methods.
We can do this like that:
Code:
foreach(ModuleDefinition ModuleDef in AssemblyDef.Modules )
{
foreach(TypeDefinition TypeDef in ModuleDef.GetTypes())
{
foreach (MethodDefinition MethodDef in TypeDef.Methods)
{
}
}
}
Now we need to look into all Instructions in our Method.
Before we do that we should make sure our method has a body, then we can continue.
Code:
if (MethodDef.HasBody)
Now lets iterate through all Instructions and filter out the Ldstr-Instructions (Strings).
We encrypt these strings with Base64, and reference our decrypt method after the encrypted string, so it will get decrypted on runtime.
Code:
ILProcessor ilp = MethodDef.Body.GetILProcessor();
for (int i = 0; i < MethodDef.Body.Instructions.Count; i++)
{
Instruction InstructionDef = MethodDef.Body.Instructions[i];
if (InstructionDef.OpCode == OpCodes.Ldstr)
{
InstructionDef.Operand = Encode(InstructionDef.Operand.ToString());
ilp.InsertAfter(InstructionDef, Instruction.Create(OpCodes.Call, MD));
}
}
Writing the secured application
Now we save our changes by just typing this:
Code:
AssemblyDef.Write(@"C:\Users\Admin\Desktop\Secured.exe");
The final check
If we now open up our applciation with a decompiler, we will see this:
All strings have been encoded.
And if we start the app it works just fine!
If you plan to change the encryption method (which is highly recommended) make sure to rebuild the DecrypterMethod correctly.
You can use a decompiler to get the correct MSIL so you know what Instructions to add to the method body.
I hope this tutorial was helpful for you guys, please give me some feedback.