From 5704a8f74ed6f8bc7b28a0797000198766c37066 Mon Sep 17 00:00:00 2001 From: Harry Stoltz Date: Thu, 28 Apr 2022 14:22:22 +0100 Subject: [PATCH] Initial Version --- .gitignore | 41 ++ MSILEmulator/Context.cs | 15 + MSILEmulator/EmulatorException.cs | 25 ++ MSILEmulator/ILMethod.cs | 178 ++++++++ MSILEmulator/Instructions/Arithmetic/Add.cs | 20 + MSILEmulator/Instructions/Arithmetic/Mul.cs | 19 + MSILEmulator/Instructions/Arithmetic/Neg.cs | 18 + MSILEmulator/Instructions/Arithmetic/Sub.cs | 19 + MSILEmulator/Instructions/Load/Ldarg.cs | 34 ++ MSILEmulator/Instructions/Load/LdcI4.cs | 19 + MSILEmulator/Instructions/Load/LdcI4_M1.cs | 16 + MSILEmulator/Instructions/Load/Ldelem.cs | 20 + MSILEmulator/Instructions/Load/Ldloc.cs | 35 ++ MSILEmulator/Instructions/Logic/And.cs | 19 + MSILEmulator/Instructions/Logic/Not.cs | 18 + MSILEmulator/Instructions/Logic/Or.cs | 19 + MSILEmulator/Instructions/Logic/Shl.cs | 19 + MSILEmulator/Instructions/Logic/Shr.cs | 19 + MSILEmulator/Instructions/Logic/Shr_Un.cs | 19 + MSILEmulator/Instructions/Logic/Xor.cs | 19 + MSILEmulator/Instructions/Store/Stelem.cs | 21 + MSILEmulator/Instructions/Store/Stloc.cs | 27 ++ MSILEmulator/MSILEmulator.csproj | 11 + UnConfuserEx.sln | 51 +++ .../Protections/AntiDebug/AntiDebugRemover.cs | 49 +++ .../AntiTamper/AntiTamperRemover.cs | 309 ++++++++++++++ .../Protections/AntiTamper/DynamicDeriver.cs | 48 +++ .../Protections/AntiTamper/IKeyDeriver.cs | 15 + .../Protections/AntiTamper/NormalDeriver.cs | 33 ++ .../Protections/Constants/ConstantsCFG.cs | 267 ++++++++++++ .../Protections/Constants/ConstantsRemover.cs | 392 ++++++++++++++++++ .../Protections/Constants/DynamicDecryptor.cs | 194 +++++++++ .../Protections/Constants/IDecryptor.cs | 9 + .../Protections/Constants/IResolver.cs | 12 + .../Protections/Constants/NormalDecryptor.cs | 48 +++ .../Protections/Constants/NormalResolver.cs | 176 ++++++++ .../Protections/Constants/X86Resolver.cs | 59 +++ .../ControlFlow/ControlFlowRemover.cs | 122 ++++++ .../ControlFlow/SwitchDeobfuscator.cs | 272 ++++++++++++ UnConfuserEx/Protections/IProtection.cs | 18 + .../Protections/RefProxy/RefProxyHandler.cs | 111 +++++ .../Protections/RefProxy/RefProxyRemover.cs | 216 ++++++++++ UnConfuserEx/Protections/Renamer/TypeInfo.cs | 155 +++++++ .../Protections/Renamer/TypeRenamer.cs | 60 +++ .../Protections/Renamer/UnicodeRemover.cs | 103 +++++ .../Protections/Resources/IDecryptor.cs | 14 + .../Protections/Resources/NormalDecryptor.cs | 54 +++ .../Protections/Resources/ResourcesRemover.cs | 236 +++++++++++ UnConfuserEx/UnConfuserEx.cs | 119 ++++++ UnConfuserEx/UnConfuserEx.csproj | 32 ++ UnConfuserEx/Utils.cs | 127 ++++++ UnConfuserEx/log4net.xml | 40 ++ X86Emulator/EmulatorException.cs | 25 ++ X86Emulator/Instructions/Add.cs | 45 ++ X86Emulator/Instructions/IMul.cs | 88 ++++ X86Emulator/Instructions/Instruction.cs | 13 + X86Emulator/Instructions/Mov.cs | 38 ++ X86Emulator/Instructions/Neg.cs | 28 ++ X86Emulator/Instructions/Not.cs | 28 ++ X86Emulator/Instructions/Pop.cs | 29 ++ X86Emulator/Instructions/Push.cs | 34 ++ X86Emulator/Instructions/Sub.cs | 45 ++ X86Emulator/Instructions/Xor.cs | 36 ++ X86Emulator/Registers.cs | 83 ++++ X86Emulator/X86Emulator.csproj | 13 + X86Emulator/X86Method.cs | 133 ++++++ 66 files changed, 4629 insertions(+) create mode 100644 .gitignore create mode 100644 MSILEmulator/Context.cs create mode 100644 MSILEmulator/EmulatorException.cs create mode 100644 MSILEmulator/ILMethod.cs create mode 100644 MSILEmulator/Instructions/Arithmetic/Add.cs create mode 100644 MSILEmulator/Instructions/Arithmetic/Mul.cs create mode 100644 MSILEmulator/Instructions/Arithmetic/Neg.cs create mode 100644 MSILEmulator/Instructions/Arithmetic/Sub.cs create mode 100644 MSILEmulator/Instructions/Load/Ldarg.cs create mode 100644 MSILEmulator/Instructions/Load/LdcI4.cs create mode 100644 MSILEmulator/Instructions/Load/LdcI4_M1.cs create mode 100644 MSILEmulator/Instructions/Load/Ldelem.cs create mode 100644 MSILEmulator/Instructions/Load/Ldloc.cs create mode 100644 MSILEmulator/Instructions/Logic/And.cs create mode 100644 MSILEmulator/Instructions/Logic/Not.cs create mode 100644 MSILEmulator/Instructions/Logic/Or.cs create mode 100644 MSILEmulator/Instructions/Logic/Shl.cs create mode 100644 MSILEmulator/Instructions/Logic/Shr.cs create mode 100644 MSILEmulator/Instructions/Logic/Shr_Un.cs create mode 100644 MSILEmulator/Instructions/Logic/Xor.cs create mode 100644 MSILEmulator/Instructions/Store/Stelem.cs create mode 100644 MSILEmulator/Instructions/Store/Stloc.cs create mode 100644 MSILEmulator/MSILEmulator.csproj create mode 100644 UnConfuserEx.sln create mode 100644 UnConfuserEx/Protections/AntiDebug/AntiDebugRemover.cs create mode 100644 UnConfuserEx/Protections/AntiTamper/AntiTamperRemover.cs create mode 100644 UnConfuserEx/Protections/AntiTamper/DynamicDeriver.cs create mode 100644 UnConfuserEx/Protections/AntiTamper/IKeyDeriver.cs create mode 100644 UnConfuserEx/Protections/AntiTamper/NormalDeriver.cs create mode 100644 UnConfuserEx/Protections/Constants/ConstantsCFG.cs create mode 100644 UnConfuserEx/Protections/Constants/ConstantsRemover.cs create mode 100644 UnConfuserEx/Protections/Constants/DynamicDecryptor.cs create mode 100644 UnConfuserEx/Protections/Constants/IDecryptor.cs create mode 100644 UnConfuserEx/Protections/Constants/IResolver.cs create mode 100644 UnConfuserEx/Protections/Constants/NormalDecryptor.cs create mode 100644 UnConfuserEx/Protections/Constants/NormalResolver.cs create mode 100644 UnConfuserEx/Protections/Constants/X86Resolver.cs create mode 100644 UnConfuserEx/Protections/ControlFlow/ControlFlowRemover.cs create mode 100644 UnConfuserEx/Protections/ControlFlow/SwitchDeobfuscator.cs create mode 100644 UnConfuserEx/Protections/IProtection.cs create mode 100644 UnConfuserEx/Protections/RefProxy/RefProxyHandler.cs create mode 100644 UnConfuserEx/Protections/RefProxy/RefProxyRemover.cs create mode 100644 UnConfuserEx/Protections/Renamer/TypeInfo.cs create mode 100644 UnConfuserEx/Protections/Renamer/TypeRenamer.cs create mode 100644 UnConfuserEx/Protections/Renamer/UnicodeRemover.cs create mode 100644 UnConfuserEx/Protections/Resources/IDecryptor.cs create mode 100644 UnConfuserEx/Protections/Resources/NormalDecryptor.cs create mode 100644 UnConfuserEx/Protections/Resources/ResourcesRemover.cs create mode 100644 UnConfuserEx/UnConfuserEx.cs create mode 100644 UnConfuserEx/UnConfuserEx.csproj create mode 100644 UnConfuserEx/Utils.cs create mode 100644 UnConfuserEx/log4net.xml create mode 100644 X86Emulator/EmulatorException.cs create mode 100644 X86Emulator/Instructions/Add.cs create mode 100644 X86Emulator/Instructions/IMul.cs create mode 100644 X86Emulator/Instructions/Instruction.cs create mode 100644 X86Emulator/Instructions/Mov.cs create mode 100644 X86Emulator/Instructions/Neg.cs create mode 100644 X86Emulator/Instructions/Not.cs create mode 100644 X86Emulator/Instructions/Pop.cs create mode 100644 X86Emulator/Instructions/Push.cs create mode 100644 X86Emulator/Instructions/Sub.cs create mode 100644 X86Emulator/Instructions/Xor.cs create mode 100644 X86Emulator/Registers.cs create mode 100644 X86Emulator/X86Emulator.csproj create mode 100644 X86Emulator/X86Method.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f77d48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Visual Studio Cache files (starting with VS 2015) +.vs/ + +# Launch Settings +launchSettings.json + +#ignore thumbnails created by windows +Thumbs.db +#Ignore files build by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* + +!packages/*/build/ +packages/ +/Confuser.Test +*.sln.* +gh-pages/ + +.idea/ \ No newline at end of file diff --git a/MSILEmulator/Context.cs b/MSILEmulator/Context.cs new file mode 100644 index 0000000..9e8009c --- /dev/null +++ b/MSILEmulator/Context.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator +{ + public class Context + { + public Dictionary Args = new(); + public Dictionary Locals = new(); + public Stack Stack = new(); + } +} diff --git a/MSILEmulator/EmulatorException.cs b/MSILEmulator/EmulatorException.cs new file mode 100644 index 0000000..31998ce --- /dev/null +++ b/MSILEmulator/EmulatorException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator +{ + internal class EmulatorException : Exception + { + + public EmulatorException() + { + } + + public EmulatorException(string message) : base(message) + { + } + + public EmulatorException(string message, Exception inner) : base(message, inner) + { + } + + } +} diff --git a/MSILEmulator/ILMethod.cs b/MSILEmulator/ILMethod.cs new file mode 100644 index 0000000..ee5133b --- /dev/null +++ b/MSILEmulator/ILMethod.cs @@ -0,0 +1,178 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using MSILEmulator.Instructions.Arithmetic; +using MSILEmulator.Instructions.Load; +using MSILEmulator.Instructions.Logic; +using MSILEmulator.Instructions.Store; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MSILEmulator +{ + public class ILMethod + { + private MethodDef? Method; + private List Instructions; + private Context Ctx; + + public ILMethod(MethodDef method) + { + Method = method; + Instructions = Method.Body.Instructions.ToList(); + Ctx = new(); + } + + public ILMethod(MethodDef method, int start, int end) + { + Method = method; + Instructions = Method.Body.Instructions.Skip(start).Take(end - start).ToList(); + Ctx = new(); + } + + public ILMethod(IList instructions) + { + Method = null; + Instructions = instructions.ToList(); + Ctx = new(); + } + + public void SetArg(int index, object value) + { + Ctx.Args[index] = value; + } + + public void SetLocal(int index, object value) + { + Ctx.Locals[index] = value; + } + + public void Reset() + { + Ctx = new(); + } + + public Context Emulate() + { + + for (int i = 0; i < Instructions.Count; ) + { + var instr = Instructions[i]; + + i = EmulateInstruction(Ctx, instr, i); + } + + return Ctx; + } + + private int EmulateInstruction(Context ctx, Instruction instr, int index) + { + + switch (instr.OpCode.Code) + { + + // Load + case Code.Ldc_I4: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_S: + LdcI4.Emulate(ctx, instr); + break; + case Code.Ldc_I4_M1: + LdcI4_M1.Emulate(ctx); + break; + + case Code.Ldarg: + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg_S: + Ldarg.Emulate(ctx, instr); + break; + + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + Ldloc.Emulate(ctx, instr); + break; + + case Code.Ldelem_U4: + Ldelem.EmulateU4(ctx, instr); + break; + + // Store + case Code.Stloc: + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + case Code.Stloc_S: + Stloc.Emulate(ctx, instr); + break; + + case Code.Stelem_I4: + Stelem.Emulate(ctx, instr); + break; + + case Code.Stfld: + // TODO: Keep fields in ctx in case they get referenced again? + // For now just treat it as a no-op + break; + + // Arithmetic operators + case Code.Add: + Add.Emulate(ctx); + break; + case Code.Mul: + Mul.Emulate(ctx); + break; + case Code.Neg: + Neg.Emulate(ctx); + break; + case Code.Or: + Or.Emulate(ctx); + break; + case Code.Sub: + Sub.Emulate(ctx); + break; + + // Logic operators + case Code.And: + And.Emulate(ctx); + break; + case Code.Not: + Not.Emulate(ctx); + break; + case Code.Shl: + Shl.Emulate(ctx); + break; + case Code.Shr: + Shr.Emulate(ctx); + break; + case Code.Shr_Un: + Shr_Un.Emulate(ctx); + break; + case Code.Xor: + Xor.Emulate(ctx); + break; + + default: + throw new EmulatorException($"Unhandled OpCode {instr.OpCode}"); + } + + return ++index; + } + + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Add.cs b/MSILEmulator/Instructions/Arithmetic/Add.cs new file mode 100644 index 0000000..a18e487 --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Add.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Add + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 + val2); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Mul.cs b/MSILEmulator/Instructions/Arithmetic/Mul.cs new file mode 100644 index 0000000..d07df54 --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Mul.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Mul + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 * val2); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Neg.cs b/MSILEmulator/Instructions/Arithmetic/Neg.cs new file mode 100644 index 0000000..f1d8e49 --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Neg.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Neg + { + public static void Emulate(Context ctx) + { + int val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(-val); + } + } +} diff --git a/MSILEmulator/Instructions/Arithmetic/Sub.cs b/MSILEmulator/Instructions/Arithmetic/Sub.cs new file mode 100644 index 0000000..983472b --- /dev/null +++ b/MSILEmulator/Instructions/Arithmetic/Sub.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Arithmetic +{ + internal class Sub + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 - val2); + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldarg.cs b/MSILEmulator/Instructions/Load/Ldarg.cs new file mode 100644 index 0000000..6582f3d --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldarg.cs @@ -0,0 +1,34 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldarg + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Ldarg_0; + } + else + { + index = (int)instr.Operand; + } + + if (ctx.Args.TryGetValue(index, out var val)) + { + ctx.Stack.Push(val); + } + else + { + ctx.Stack.Push(0); + } + } + } +} diff --git a/MSILEmulator/Instructions/Load/LdcI4.cs b/MSILEmulator/Instructions/Load/LdcI4.cs new file mode 100644 index 0000000..587c64d --- /dev/null +++ b/MSILEmulator/Instructions/Load/LdcI4.cs @@ -0,0 +1,19 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class LdcI4 + { + public static void Emulate(Context ctx, Instruction instr) + { + int val = instr.GetLdcI4Value(); + + ctx.Stack.Push(val); + } + } +} diff --git a/MSILEmulator/Instructions/Load/LdcI4_M1.cs b/MSILEmulator/Instructions/Load/LdcI4_M1.cs new file mode 100644 index 0000000..ce5dbaf --- /dev/null +++ b/MSILEmulator/Instructions/Load/LdcI4_M1.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class LdcI4_M1 + { + public static void Emulate(Context ctx) + { + ctx.Stack.Push((int)-1); + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldelem.cs b/MSILEmulator/Instructions/Load/Ldelem.cs new file mode 100644 index 0000000..6d54bc6 --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldelem.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldelem + { + public static void EmulateU4(Context ctx, Instruction instr) + { + int index = (int)ctx.Stack.Pop(); + var array = (uint[])ctx.Stack.Pop(); + + ctx.Stack.Push(unchecked((int)array[index])); + } + } +} diff --git a/MSILEmulator/Instructions/Load/Ldloc.cs b/MSILEmulator/Instructions/Load/Ldloc.cs new file mode 100644 index 0000000..13728a7 --- /dev/null +++ b/MSILEmulator/Instructions/Load/Ldloc.cs @@ -0,0 +1,35 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Load +{ + internal class Ldloc + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Ldloc_0; + } + else + { + index = ((Local)instr.Operand).Index; + } + + if (ctx.Locals.TryGetValue(index, out var local)) + { + ctx.Stack.Push(local); + } + else + { + throw new NotImplementedException("Attempted to load local that hasn't been set."); + } + } + + } +} diff --git a/MSILEmulator/Instructions/Logic/And.cs b/MSILEmulator/Instructions/Logic/And.cs new file mode 100644 index 0000000..19672fb --- /dev/null +++ b/MSILEmulator/Instructions/Logic/And.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class And + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 & val2); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Not.cs b/MSILEmulator/Instructions/Logic/Not.cs new file mode 100644 index 0000000..d07df0c --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Not.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Not + { + public static void Emulate(Context ctx) + { + int val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(~val); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Or.cs b/MSILEmulator/Instructions/Logic/Or.cs new file mode 100644 index 0000000..cf6ed11 --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Or.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Or + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val1 | val2); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shl.cs b/MSILEmulator/Instructions/Logic/Shl.cs new file mode 100644 index 0000000..455881d --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shl.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shl + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val << shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shr.cs b/MSILEmulator/Instructions/Logic/Shr.cs new file mode 100644 index 0000000..868f38b --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shr.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shr + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val >> shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Shr_Un.cs b/MSILEmulator/Instructions/Logic/Shr_Un.cs new file mode 100644 index 0000000..991b722 --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Shr_Un.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Shr_Un + { + public static void Emulate(Context ctx) + { + var shift = (int)ctx.Stack.Pop(); + var val = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val >>> shift); + } + } +} diff --git a/MSILEmulator/Instructions/Logic/Xor.cs b/MSILEmulator/Instructions/Logic/Xor.cs new file mode 100644 index 0000000..528712c --- /dev/null +++ b/MSILEmulator/Instructions/Logic/Xor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Logic +{ + internal class Xor + { + public static void Emulate(Context ctx) + { + int val2 = (int)ctx.Stack.Pop(); + int val1 = (int)ctx.Stack.Pop(); + + ctx.Stack.Push(val2 ^ val1); + } + } +} diff --git a/MSILEmulator/Instructions/Store/Stelem.cs b/MSILEmulator/Instructions/Store/Stelem.cs new file mode 100644 index 0000000..56a81f5 --- /dev/null +++ b/MSILEmulator/Instructions/Store/Stelem.cs @@ -0,0 +1,21 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Store +{ + internal class Stelem + { + public static void Emulate(Context ctx, Instruction instr) + { + int value = (int)ctx.Stack.Pop(); + int index = (int)ctx.Stack.Pop(); + var array = (int[])ctx.Stack.Pop(); + + array[index] = value; + } + } +} diff --git a/MSILEmulator/Instructions/Store/Stloc.cs b/MSILEmulator/Instructions/Store/Stloc.cs new file mode 100644 index 0000000..15e9fb7 --- /dev/null +++ b/MSILEmulator/Instructions/Store/Stloc.cs @@ -0,0 +1,27 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Store +{ + internal class Stloc + { + public static void Emulate(Context ctx, Instruction instr) + { + int index; + if (instr.Operand == null) + { + index = instr.OpCode.Code - Code.Stloc_0; + } + else + { + index = ((Local)instr.Operand).Index; + } + + ctx.Locals[index] = ctx.Stack.Pop(); + } + } +} diff --git a/MSILEmulator/MSILEmulator.csproj b/MSILEmulator/MSILEmulator.csproj new file mode 100644 index 0000000..304e68a --- /dev/null +++ b/MSILEmulator/MSILEmulator.csproj @@ -0,0 +1,11 @@ + + + + net7.0 + + + + + + + diff --git a/UnConfuserEx.sln b/UnConfuserEx.sln new file mode 100644 index 0000000..6fbac52 --- /dev/null +++ b/UnConfuserEx.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32414.318 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnConfuserEx", "UnConfuserEx\UnConfuserEx.csproj", "{BDFFB219-67C3-48C3-B927-B1A5630E3EFA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X86Emulator", "X86Emulator\X86Emulator.csproj", "{009A0138-E76D-4BB4-911E-A6B45308A7D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSILEmulator", "MSILEmulator\MSILEmulator.csproj", "{85DFCC88-C540-4FE8-B735-118493103212}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Debug|x64.ActiveCfg = Debug|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Debug|x64.Build.0 = Debug|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Release|Any CPU.Build.0 = Release|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Release|x64.ActiveCfg = Release|Any CPU + {BDFFB219-67C3-48C3-B927-B1A5630E3EFA}.Release|x64.Build.0 = Release|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Debug|x64.Build.0 = Debug|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Release|Any CPU.Build.0 = Release|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Release|x64.ActiveCfg = Release|Any CPU + {009A0138-E76D-4BB4-911E-A6B45308A7D0}.Release|x64.Build.0 = Release|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Debug|x64.ActiveCfg = Debug|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Debug|x64.Build.0 = Debug|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Release|Any CPU.Build.0 = Release|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Release|x64.ActiveCfg = Release|Any CPU + {85DFCC88-C540-4FE8-B735-118493103212}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D45B4A92-B0BC-4257-A9F2-1512ECF5C7FF} + EndGlobalSection +EndGlobal diff --git a/UnConfuserEx/Protections/AntiDebug/AntiDebugRemover.cs b/UnConfuserEx/Protections/AntiDebug/AntiDebugRemover.cs new file mode 100644 index 0000000..96f54fa --- /dev/null +++ b/UnConfuserEx/Protections/AntiDebug/AntiDebugRemover.cs @@ -0,0 +1,49 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.AntiDebug +{ + internal class AntiDebugRemover : IProtection + { + public string Name => "AntiDebug"; + + private bool ModuleCctorHasAntiDump = false; + private List AntiDebugMethods = new(); + + + public bool IsPresent(ref ModuleDefMD module) + { + if (module.GlobalType.FindStaticConstructor() is MethodDef cctor && + cctor.HasBody) + { + var instrs = cctor.Body.Instructions; + + ModuleCctorHasAntiDump = instrs[0].OpCode == OpCodes.Ldtoken && + (TypeDef)instrs[0].Operand == module.GlobalType && + instrs[5].OpCode == OpCodes.Call && + ((IMethodDefOrRef)instrs[5].Operand).Name == "GetHINSTANCE"; + + } + + return ModuleCctorHasAntiDump; + } + + public bool Remove(ref ModuleDefMD module) + { + if (ModuleCctorHasAntiDump) + { + var cctor = module.GlobalType.FindStaticConstructor() as MethodDef; + + cctor.Body.Instructions.Clear(); + cctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + } + + return true; + } + } +} diff --git a/UnConfuserEx/Protections/AntiTamper/AntiTamperRemover.cs b/UnConfuserEx/Protections/AntiTamper/AntiTamperRemover.cs new file mode 100644 index 0000000..725291e --- /dev/null +++ b/UnConfuserEx/Protections/AntiTamper/AntiTamperRemover.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SRE = System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.PE; +using UnConfuserEx.Protections; +using System.IO; +using UnConfuserEx.Protections.AntiTamper; +using log4net; + +namespace UnConfuserEx.Protections +{ + internal class AntiTamperRemover : IProtection + { + static ILog Logger = LogManager.GetLogger("AntiTamper"); + + private enum DeriverType + { + Normal, + Dynamic + } + + MethodDef? decryptMethod; + + string IProtection.Name => "AntiTamper"; + + public bool IsPresent(ref ModuleDefMD module) + { + decryptMethod = GetDecryptMethod(module); + + return decryptMethod != null; + } + + public bool Remove(ref ModuleDefMD module) + { + ImageSectionHeader? encryptedSection = GetEncryptedSection(module); + if (encryptedSection == null) + { + Logger.Error("Failed to find encrypted section"); + return false; + } + Logger.Debug($"Found encrypted data in section {Encoding.UTF8.GetString(encryptedSection.Name)}"); + + uint[]? initialKeys = GetInitialKeys(); + if (initialKeys == null) + { + Logger.Error("Failed to find initial keys in decrypt method"); + return false; + } + Logger.Debug($"Found initial decryption keys"); + + (DeriverType? deriverType, List? derivation) = GetDeriverTypeAndDerivation(); + if (deriverType == null || derivation == null) + { + Logger.Error("[-] Failed to get the key deriver type and it's derivation"); + return false; + } + Logger.Debug($"Detected deriver type is {deriverType}"); + + (uint[] dst, uint[] src) = PrepareKeyArrays(module, encryptedSection, initialKeys); + + IKeyDeriver deriver; + if (deriverType == DeriverType.Normal) + { + deriver = new NormalDeriver(); + } + else + { + deriver = new DynamicDeriver(derivation); + } + + Logger.Debug($"Deriving decryption key"); + uint[] key = deriver.DeriveKey(dst, src); + + Logger.Debug($"Decrypting method bodies"); + return DecryptSection(ref module, key, encryptedSection); + } + + private ImageSectionHeader? GetEncryptedSection(ModuleDefMD module) + { + int name = -1; + + var instrs = decryptMethod!.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Ldloc_S + && instrs[i + 1].OpCode == OpCodes.Ldc_I4 + && instrs[i + 2].OpCode.FlowControl == FlowControl.Cond_Branch) + { + name = (int)instrs[i + 1].Operand; + break; + } + } + + if (name == -1) + { + return null; + } + + IList sections = module.Metadata.PEImage.ImageSectionHeaders; + foreach (var section in sections) + { + var sectionName = section.Name; + var name1 = sectionName[0] | sectionName[1] << 8 | sectionName[2] << 16 | sectionName[3] << 24; + var name2 = sectionName[4] | sectionName[5] << 8 | sectionName[6] << 16 | sectionName[7] << 24; + + if (name == (name1 * name2)) + { + return section; + } + + } + return null; + } + + private uint[]? GetInitialKeys() + { + var instrs = decryptMethod!.Body.Instructions; + int firstInstr = -1; + for (int i = 0; i < instrs.Count - 1; i++) + { + if (instrs[i].OpCode == OpCodes.Ldc_I4 + && instrs[i + 1].OpCode == OpCodes.Stloc_S) + { + firstInstr = i; + break; + } + } + + if (firstInstr == -1) + { + return null; + } + + uint[] keys = new uint[4]; + for (int i = 0; i < keys.Length; i++) + { + keys[i] = (uint)(int)instrs[firstInstr + (i * 2)].Operand; + } + + return keys; + } + + private (DeriverType?, List?) GetDeriverTypeAndDerivation() + { + /* + * Start of derivation + * + * 1F10 : ldc.i4.s 16 + * 32B5 : blt.s IL_0151 + * ???? : ?????? <<<< first derivation instr + * + * End of derivation + * + * ???? ; ?????? <<<< last derivation instr + * 1F40 : ldc.i4.s 64 + * 130E : stloc.s V_14 + * 1105 : ldloc.s V_5 + * + */ + var instrs = decryptMethod!.Body.Instructions; + + var firstInstr = -1; + for (int i = 0; i < instrs.Count - 1; i++) + { + if (instrs[i].OpCode == OpCodes.Ldc_I4_S + && instrs[i + 1].OpCode == OpCodes.Blt_S) + { + firstInstr = i + 2; + break; + } + } + + if (firstInstr == -1) + { + return (null, null); + } + + var lastInstr = -1; + for (int i = firstInstr; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Stelem_I4 + && instrs[i + 1].OpCode == OpCodes.Ldc_I4_S + && instrs[i + 2].OpCode == OpCodes.Stloc_S) + { + lastInstr = i; + break; + } + } + + if (lastInstr == -1) + { + return (null, null); + } + + int derivationLength = (lastInstr - firstInstr); + + List derivation = new(); + for (int i = 0; i <= derivationLength; i++) + { + derivation.Add(instrs[firstInstr + i]); + } + + // Normal deriver is 16 iterations of 10 instructions + const int normalDeriationLength = 16 * 10; + DeriverType type = (derivationLength == normalDeriationLength) ? DeriverType.Normal : DeriverType.Dynamic; + return (type, derivation); + } + + private static MethodDef? GetDecryptMethod(ModuleDefMD module) + { + var cctor = module.GlobalType.FindStaticConstructor(); + + if (cctor == null || !(cctor.HasBody) || cctor.Body.Instructions[0].OpCode != OpCodes.Call) + return null; + + var method = cctor.Body.Instructions[0].Operand as MethodDef; + + foreach (var instr in method!.Body.Instructions) + { + if (instr.Operand != null && instr.Operand.ToString()!.Contains("Marshal::GetHINSTANCE")) + { + return method; + } + } + return null; + } + + private static (uint[], uint[]) PrepareKeyArrays(ModuleDefMD module, ImageSectionHeader encryptedSection, uint[] initialKeys) + { + uint z = initialKeys[0], x = initialKeys[1], c = initialKeys[2], v = initialKeys[3]; + + var reader = module.Metadata.PEImage.CreateReader(); + IList sections = module.Metadata.PEImage.ImageSectionHeaders; + foreach (var section in sections) + { + if (section == encryptedSection) + { + continue; + } + else + { + var size = section.SizeOfRawData >> 2; + var loc = section.PointerToRawData; + reader.Position = loc; + for (int i = 0; i < size; i++) + { + var t = (z ^ reader.ReadUInt32()) + x + c * v; + z = x; + x = v; + v = t; + } + } + } + uint[] dst = new uint[16], src = new uint[16]; + for (int i = 0; i < 16; i++) + { + dst[i] = v; + src[i] = x; + z = (x >> 5) | (x << 27); + x = (c >> 3) | (c << 29); + c = (v >> 7) | (v << 25); + v = (z >> 11) | (z << 21); + } + + return (dst, src); + } + + private bool DecryptSection(ref ModuleDefMD module, uint[] key, ImageSectionHeader encryptedSection) + { + var reader = module.Metadata.PEImage.CreateReader(); + byte[] image = reader.ReadRemainingBytes(); + + var size = encryptedSection.SizeOfRawData >> 2; + var pos = encryptedSection.PointerToRawData; + reader.Position = pos; + uint[] result = new uint[size]; + for (uint i = 0; i < size; i++) + { + uint data = reader.ReadUInt32(); + result[i] = data ^ key[i & 0xf]; + key[i & 0xf] = (key[i & 0xf] ^ result[i]) + 0x3dbb2819; + } + byte[] byteResult = new byte[size << 2]; + Buffer.BlockCopy(result, 0, byteResult, 0, byteResult.Length); + + var stream = new MemoryStream(image) + { + Position = pos + }; + stream.Write(byteResult, 0, byteResult.Length); + + ModuleDefMD newModule = ModuleDefMD.Load(stream); + module = newModule; + + decryptMethod = GetDecryptMethod(module)!; + + module.GlobalType.Methods.Remove(decryptMethod); + module.GlobalType.FindStaticConstructor().Body.Instructions.RemoveAt(0); + + return true; + } + } +} diff --git a/UnConfuserEx/Protections/AntiTamper/DynamicDeriver.cs b/UnConfuserEx/Protections/AntiTamper/DynamicDeriver.cs new file mode 100644 index 0000000..fa5a363 --- /dev/null +++ b/UnConfuserEx/Protections/AntiTamper/DynamicDeriver.cs @@ -0,0 +1,48 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MSILEmulator; + +namespace UnConfuserEx.Protections.AntiTamper +{ + internal class DynamicDeriver : IKeyDeriver + { + private IList derivation; + + public DynamicDeriver(IList derivation) + { + this.derivation = derivation; + } + + public uint[] DeriveKey(uint[] dst, uint[] src) + { + SortedSet arrays = new(); + for (int i = 0; i < derivation.Count - 2; i++) + { + if (derivation[i].OpCode == OpCodes.Ldloc_S + && derivation[i + 2].OpCode == OpCodes.Ldelem_U4) + { + arrays.Add(((Local)derivation[i].Operand).Index); + } + } + int[] arrayIndices = arrays.ToArray(); + if (arrayIndices.Length != 2) + { + throw new Exception("There should be two key arrays used in a dynamic derivation"); + } + + var ilMethod = new ILMethod(derivation); + + ilMethod.SetLocal(arrayIndices[0], dst); + ilMethod.SetLocal(arrayIndices[1], src); + + ilMethod.Emulate(); + + return dst; + } + + } +} diff --git a/UnConfuserEx/Protections/AntiTamper/IKeyDeriver.cs b/UnConfuserEx/Protections/AntiTamper/IKeyDeriver.cs new file mode 100644 index 0000000..266f1e6 --- /dev/null +++ b/UnConfuserEx/Protections/AntiTamper/IKeyDeriver.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.AntiTamper +{ + internal interface IKeyDeriver + { + + public uint[] DeriveKey(uint[] dst, uint[] src); + + } +} diff --git a/UnConfuserEx/Protections/AntiTamper/NormalDeriver.cs b/UnConfuserEx/Protections/AntiTamper/NormalDeriver.cs new file mode 100644 index 0000000..c23ea97 --- /dev/null +++ b/UnConfuserEx/Protections/AntiTamper/NormalDeriver.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.AntiTamper +{ + internal class NormalDeriver : IKeyDeriver + { + public uint[] DeriveKey(uint[] dst, uint[] src) + { + var ret = new uint[0x10]; + for (int i = 0; i < 0x10; i++) + { + switch (i % 3) + { + case 0: + ret[i] = dst[i] ^ src[i]; + break; + case 1: + ret[i] = dst[i] * src[i]; + break; + case 2: + ret[i] = dst[i] + src[i]; + break; + } + } + return ret; + } + + } +} diff --git a/UnConfuserEx/Protections/Constants/ConstantsCFG.cs b/UnConfuserEx/Protections/Constants/ConstantsCFG.cs new file mode 100644 index 0000000..f3aceb8 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/ConstantsCFG.cs @@ -0,0 +1,267 @@ +using de4dot.blocks; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Constants +{ + internal class ConstantsCFG + { + private static TypeDef? CFGCtxStruct = null; + + private MethodDef Method; + private Blocks @Blocks; + private IList Locals; + private Local CtxLocal; + private HashSet Visited = new(); + + public ConstantsCFG(MethodDef method) + { + Method = method; + Locals = method.Body.Variables.Locals; + CtxLocal = method.Body.Instructions[0].GetLocal(Locals); + @Blocks = new Blocks(Method); + } + + public void RemoveFromMethod() + { + RemoveFromBlock(@Blocks.MethodBlocks.GetAllBlocks()[0], new CFGCtx(0)); + + IList instructions; + IList exceptionHandlers; + @Blocks.GetCode(out instructions, out exceptionHandlers); + DotNetUtils.RestoreBody(Method, instructions, exceptionHandlers); + } + + private void RemoveFromBlock(Block block, CFGCtx curCtx) + { + if (Visited.Contains(block)) + { + return; + } + Visited.Add(block); + + List instrs = block.Instructions; + // Iterate through methods and find calls to next + // Only replace with an ldc.i4 if the instruction after the call to next + // is NOT a pop + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Ldloca_S && + instrs[i].Instruction.GetLocal(Locals) == CtxLocal) + { + // This is most likely a call to the Rand method, + // but could be another constructor call + if (instrs[i + 1].IsLdcI4() && + instrs[i + 2].IsLdcI4()) + { + // Call to Rand + var f = (byte)instrs[i + 1].GetLdcI4Value(); + var q = (uint)instrs[i + 2].GetLdcI4Value(); + + var rand = curCtx.Rand(f, q); + if (instrs[i + 4].OpCode != OpCodes.Pop) + { + // This is a value that's actually going to be used + // The instruction before should be an ldc.i4 and after should be a xor + if (instrs[i - 1].IsLdcI4() && + instrs[i + 4].OpCode == OpCodes.Xor) + { + var xorVal = (uint)instrs[i - 1].GetLdcI4Value(); + var key = (int)(xorVal ^ rand); + instrs.RemoveRange(i - 1, 6); + instrs.Insert(i - 1, new Instr(Instruction.CreateLdcI4(key))); + } + } + else + { + // This call to rand is just for switching up the values in the ctx + instrs.RemoveRange(i, 5); + i--; + } + } + else + { + // Call to constructor + var seed = (uint)instrs[i + 1].GetLdcI4Value(); + curCtx = new CFGCtx(seed); + + instrs.RemoveRange(i, 3); + i--; + } + } + } + + if (block.FallThrough != null) + { + RemoveFromBlock(block.FallThrough, new CFGCtx(curCtx)); + } + + if (block.FallThrough == null) + { + if (block.Parent is TryBlock tryBlock && + block.LastInstr.IsLeave()) + { + RemoveFromBlock(tryBlock.TryHandlerBlocks[0].GetAllBlocks()[0], new CFGCtx(curCtx)); + } + else if (block.Parent is HandlerBlock handlerBlock && + block.LastInstr.OpCode == OpCodes.Endfinally) + { + var index = Blocks.MethodBlocks.GetAllBlocks().IndexOf(block); + RemoveFromBlock(Blocks.MethodBlocks.GetAllBlocks()[index + 1], new CFGCtx(curCtx)); + } + else if (block.Parent is FilterHandlerBlock filterHandlerBlock && + block.LastInstr.OpCode == OpCodes.Endfilter) + { + var index = Blocks.MethodBlocks.GetAllBlocks().IndexOf(block); + RemoveFromBlock(Blocks.MethodBlocks.GetAllBlocks()[index + 1], new CFGCtx(curCtx)); + } + else if (block.LastInstr.OpCode != OpCodes.Ret) + { + + } + } + + if (block.Targets != null) + { + foreach (var target in block.Targets) + { + RemoveFromBlock(target, new CFGCtx(curCtx)); + } + } + } + + public static bool IsCFGPresent(MethodDef method) + { + var instrs = method.Body.Instructions; + + // Constants CFG will always create the CFG context at the start of a method + // Signature: + // ldloca.s + // ldc.i4 + // call ctor + + if (instrs.Count > 3 && + instrs[0].OpCode == OpCodes.Ldloca_S && + instrs[1].IsLdcI4() && + instrs[2].OpCode == OpCodes.Call) + { + var ctor = instrs[2].Operand as MethodDef; + + if (ctor == null) + { + return false; + } + + if (CFGCtxStruct != null && + ctor.DeclaringType == CFGCtxStruct) + { + return true; + } + + // Not sure if we can always guarantee the parameter will be named seed + // so if this fails can try and get a more general method + if (ctor.Name == ".ctor" && + ctor.ParamDefs.Count == 1 && + ctor.ParamDefs[0].Name == "seed") + { + CFGCtxStruct = ctor.DeclaringType; + return true; + } + else if (ctor.Signature.ToString() == "System.Void (System.UInt32)") + { + CFGCtxStruct = ctor.DeclaringType; + return true; + } + } + + return false; + + } + + private struct CFGCtx + { + public uint a; + public uint b; + public uint c; + public uint d; + + public CFGCtx(uint seed) + { + seed = (a = seed * 0x21412321U); + seed = (b = seed * 0x21412321U); + seed = (c = seed * 0x21412321U); + d = seed * 0x21412321U; + } + + public CFGCtx(CFGCtx other) + { + a = other.a; + b = other.b; + c = other.c; + d = other.d; + } + + public uint Rand(byte f, uint q) + { + if ((f & 0x80) != 0) + { + switch (f & 3) + { + case 0: + a = q; + break; + case 1: + b = q; + break; + case 2: + c = q; + break; + case 3: + d = q; + break; + } + } + else + { + switch (f & 3) + { + case 0: + a ^= q; + break; + case 1: + b += q; + break; + case 2: + c ^= q; + break; + case 3: + d -= q; + break; + } + } + switch ((f >> 2) & 3) + { + case 0: + return a; + case 1: + return b; + case 2: + return c; + default: + return d; + } + } + + public override string ToString() + { + return $"a: 0x{a:X8}, b: 0x{b:X8}, c: 0x{c:X8}, d: 0x{d:X8}"; + } + + } + } +} diff --git a/UnConfuserEx/Protections/Constants/ConstantsRemover.cs b/UnConfuserEx/Protections/Constants/ConstantsRemover.cs new file mode 100644 index 0000000..d3050d2 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/ConstantsRemover.cs @@ -0,0 +1,392 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using dnlib.DotNet.Emit; +using UnConfuserEx.Protections.Constants; +using log4net; + +namespace UnConfuserEx.Protections +{ + internal class ConstantsRemover : IProtection + { + private static ILog Logger = LogManager.GetLogger("Constants"); + + private enum DecryptionType + { + Normal, + Dynamic + }; + + private enum GetterType + { + Normal, + Dynamic, + X86 + }; + + public string Name => "Constants"; + + MethodDef? initializeMethod; + byte[]? data; + FieldDef? dataField; + + public bool IsPresent(ref ModuleDefMD module) + { + var cctor = module.GlobalType.FindStaticConstructor(); + + if (cctor == null || !(cctor.HasBody)) + return false; + + IList instrs; + + // Check the first call in the cctor first + if (cctor.Body.Instructions[0].OpCode == OpCodes.Call) + { + var method = cctor.Body.Instructions[0].Operand as MethodDef; + + instrs = method!.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Call + && instrs[i + 1].OpCode == OpCodes.Stsfld + && instrs[i + 2].OpCode == OpCodes.Ret) + { + initializeMethod = method; + + return true; + } + } + } + + // If we didn't find it there, check the cctor itself + instrs = cctor.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Call + && instrs[i + 1].OpCode == OpCodes.Stsfld) + { + initializeMethod = cctor; + + return true; + } + } + return false; + } + + public bool Remove(ref ModuleDefMD module) + { + IList constantGetters = GetAllGetters(module); + + if (constantGetters.Count == 0) + return true; + + if (!GetEncryptedData()) + { + Logger.Error("Failed to get encrypted constant data"); + return false; + } + + if (!DecryptData()) + { + Logger.Error("[-] Failed to decrypt constant data"); + return false; + } + + // Only LZMA is actually implemented? + if (!DecompressData()) + { + Logger.Error("[-] Failed to decompress constant data"); + return false; + } + + var instrs = initializeMethod!.Body.Instructions; + for (var i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Ldsfld && + instrs[i + 1].IsLdloc() && + instrs[i + 3].IsStloc()) + { + for (int j = 0; j < data!.Length; j += 4) + { + byte b1 = data[j]; + byte b2 = data[j + 2]; + byte b3 = data[j + 3]; + data[j] = b2; + data[j + 2] = b3; + data[j + 3] = b1; + } + + break; + } + } + + foreach (var getter in constantGetters) + { + var instances = GetAllInstances(getter); + + var getterType = GetGetterType(getter); + + Constants.IResolver resolver; + switch (getterType) + { + case GetterType.X86: + resolver = new X86Resolver(module, data!); + break; + case GetterType.Normal: + resolver = new NormalResolver(data!); + break; + default: + throw new NotImplementedException(); + } + + resolver.Resolve(getter, instances.ToList()); + + Logger.Debug($"Removed all instances of getter ${getter.FullName}"); + //module.GlobalType.Remove(getter); + } + + module.GlobalType.Fields.Remove(dataField); + + // Delete the encrypted data and decryption instructions + if (initializeMethod == module.GlobalType.FindStaticConstructor()) + { + for (int i = 0; i < initializeMethod.Body.Instructions.Count; i++) + { + var instr = initializeMethod.Body.Instructions[i]; + + if (instr.OpCode == OpCodes.Ldtoken && + instr.Operand is TypeDef td && + td == module.GlobalType) + { + break; + } + initializeMethod.Body.Instructions.RemoveAt(0); + i--; + } + initializeMethod.Body.Instructions.UpdateInstructionOffsets(); + } + else + { + throw new NotImplementedException("Constants decryption not done in the static constructor!"); + } + return true; + } + + private bool GetEncryptedData() + { + var instrs = initializeMethod!.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Ldtoken + && instrs[i + 1].OpCode == OpCodes.Call) + { + dataField = instrs[i].Operand as FieldDef; + data = dataField!.InitialValue; + + return true; + } + } + return false; + } + + private (DecryptionType?, List?) GetDecryptionType() + { + var instrs = initializeMethod!.Body.Instructions; + + bool firstLoopEnd = true; + var firstInstr = -1; + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Ldc_I4_S + && instrs[i + 1].OpCode == OpCodes.Blt_S) + { + if (firstLoopEnd) + { + firstLoopEnd = false; + continue; + } + firstInstr = i + 2; + break; + } + } + + if (firstInstr == -1) + { + return (null, null); + } + + var lastInstr = -1; + for (int i = firstInstr; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Stloc_S + && instrs[i + 1].OpCode == OpCodes.Br_S) + { + lastInstr = i - 1; + break; + } + } + + if (lastInstr == -1) + { + return (null, null); + } + + int decryptlength = (lastInstr - firstInstr); + + var decryptInstructions = initializeMethod.Body.Instructions.Skip(firstInstr).Take(decryptlength).ToList(); + const int normalDecryptLength = 16 * 10; + DecryptionType type = (decryptlength == normalDecryptLength) ? DecryptionType.Normal : DecryptionType.Dynamic; + + return (type, decryptInstructions); + } + + private uint[]? GetInitialArray() + { + uint? key = null; + bool? shlFirst = null; + + var instrs = initializeMethod!.Body.Instructions; + + // Get the key value and if we left shift first + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Newarr + && instrs[i + 2].OpCode == OpCodes.Ldc_I4) + { + key = (uint)(int)instrs[i + 2].Operand; + } + else if (instrs[i].OpCode == OpCodes.Ldc_I4_S + && instrs[i].Operand is sbyte val + && val == 12) + { + shlFirst = instrs[i + 1].OpCode == OpCodes.Shl; + } + + if (key != null && shlFirst != null) + break; + } + + if (key == null || shlFirst == null) + return null; + + + uint[] ret = new uint[16]; + for (int j = 0; j < 16; j++) + { + key ^= (bool)shlFirst ? key << 12 : key >> 12; + key ^= (bool)shlFirst ? key >> 25 : key << 25; + key ^= (bool)shlFirst ? key << 27 : key >> 27; + ret[j] = (uint)key; + } + return ret; + } + + private bool DecryptData() + { + var (type, decryptInstructions) = GetDecryptionType(); + if (type == null || decryptInstructions == null) + { + Logger.Error("Failed to get decryption type"); + return false; + } + + uint[] uintData = new uint[data!.Length >> 2]; + Buffer.BlockCopy(data, 0, uintData, 0, data.Length); + + uint[]? key = GetInitialArray(); + if (key == null) + { + Logger.Error("Failed to get initial array"); + return false; + } + + IDecryptor decryptor; + if (type == DecryptionType.Normal) + { + decryptor = new NormalDecryptor(); + } + else + { + decryptor = new DynamicDecryptor(decryptInstructions); + } + + data = decryptor.DecryptData(uintData, key); + + return true; + } + + private bool DecompressData() + { + var decompressed = Utils.DecompressDataLZMA(data!); + if (decompressed == null) + return false; + + data = decompressed; + return true; + } + + private static IList GetAllGetters(ModuleDefMD module) + { + var methods = module.GlobalType.Methods; + var getters = new List(); + + foreach (var method in methods) + { + if (!method.HasBody) + continue; + + var instrs = method.Body.Instructions; + if (instrs[0].OpCode == OpCodes.Call && instrs[0].Operand.ToString()!.Contains("Assembly::GetExecutingAssembly") + && instrs[1].OpCode == OpCodes.Call && instrs[1].Operand.ToString()!.Contains("Assembly::GetCallingAssembly")) + { + getters.Add(method); + } + } + + return getters; + } + + private HashSet GetAllInstances(MethodDef getter) + { + var placesUsed = new HashSet(); + + foreach (var method in getter.Module.GetTypes().SelectMany(m => m.Methods)) + { + if (!method.HasBody) + continue; + + foreach (var instr in method.Body.Instructions) + { + if (instr.OpCode == OpCodes.Call && instr.Operand is MethodSpec called) + { + if (called.Method.Equals(getter)) + { + placesUsed.Add(method.ResolveMethodDef()); + } + } + } + } + return placesUsed; + } + + private static GetterType GetGetterType(MethodDef getter) + { + var instrs = getter.Body.Instructions; + + if (instrs[5].OpCode == OpCodes.Call) + { + return GetterType.X86; + } + else if (instrs[5].IsLdcI4() && instrs[7].IsLdcI4()) + { + return GetterType.Normal; + } + else + { + return GetterType.Dynamic; + } + } + + } +} diff --git a/UnConfuserEx/Protections/Constants/DynamicDecryptor.cs b/UnConfuserEx/Protections/Constants/DynamicDecryptor.cs new file mode 100644 index 0000000..356be06 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/DynamicDecryptor.cs @@ -0,0 +1,194 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using SRE = System.Reflection.Emit; + +namespace UnConfuserEx.Protections.Constants +{ + internal class DynamicDecryptor : IDecryptor + { + private IList decryptInstructions; + private Func decryptDelegate; + + public DynamicDecryptor(IList decryptInstructions) + { + this.decryptInstructions = decryptInstructions; + decryptDelegate = GetDecryptDelegate(); + } + + private Func GetDecryptDelegate() + { + SortedSet arrays = new(); + for (int i = 0; i < decryptInstructions.Count - 2; i++) + { + if (decryptInstructions[i + 2].OpCode == OpCodes.Ldelem_U4) + { + if (decryptInstructions[i].OpCode == OpCodes.Ldloc_S) + { + arrays.Add(((Local)decryptInstructions[i].Operand).Index); + } + else if (decryptInstructions[i].OpCode.Value >= OpCodes.Ldloc_0.Value + && decryptInstructions[i].OpCode.Value <= OpCodes.Ldloc_3.Value) + { + arrays.Add(decryptInstructions[i].OpCode.Value - OpCodes.Ldloc_0.Value); + } + } + } + int[] arrayIndices = arrays.ToArray(); + if (arrayIndices.Length != 2) + { + throw new Exception("There should be two key arrays used in a dynamic derivation"); + } + + var deriverMethod = new SRE.DynamicMethod("", typeof(uint[]), new Type[] { typeof(uint[]), typeof(uint[]) }); + var il = deriverMethod.GetILGenerator(); + + // Setup locals + Dictionary locals = new(); + locals.Add(arrayIndices[0], 0); + il.DeclareLocal(typeof(uint[])); // key + locals.Add(arrayIndices[1], 1); + il.DeclareLocal(typeof(uint[])); // temp + + // Store passed arrays in the expected locals + il.Emit(SRE.OpCodes.Ldarg_0); + il.Emit(SRE.OpCodes.Stloc_S, 0); + il.Emit(SRE.OpCodes.Ldarg_1); + il.Emit(SRE.OpCodes.Stloc_S, 1); + + // Derivation + foreach (var instr in decryptInstructions) + { + var opcode = (SRE.OpCode)typeof(SRE.OpCodes).GetField(instr.OpCode.Code.ToString())!.GetValue(null)!; + + if (instr.Operand == null) + { + if (instr.OpCode.OpCodeType == OpCodeType.Macro) + { + if (instr.IsLdloc()) + { + var index = instr.OpCode.Value - OpCodes.Ldloc_0.Value; + int operand; + if (locals.ContainsKey(index)) + { + operand = locals[index]; + } + else + { + operand = locals.Count; + locals.Add(index, operand); + + il.DeclareLocal(typeof(uint)); + } + il.Emit(SRE.OpCodes.Ldloc_S, operand); + } + else if (instr.IsStloc()) + { + var index = instr.OpCode.Value - OpCodes.Ldloc_0.Value; + int operand; + if (locals.ContainsKey(index)) + { + operand = locals[index]; + } + else + { + operand = locals.Count; + locals.Add(index, operand); + + il.DeclareLocal(typeof(uint)); + } + il.Emit(SRE.OpCodes.Stloc_S, operand); + } + else + { + il.Emit(opcode); + } + } + else + { + il.Emit(opcode); + } + } + else if (instr.Operand is Local local) + { + var index = local.Index; + int operand; + if (locals.ContainsKey(index)) + { + operand = locals[index]; + } + else + { + operand = locals.Count; + locals.Add(index, operand); + + il.DeclareLocal(typeof(uint)); + } + il.Emit(opcode, operand); + } + else if (instr.Operand is sbyte sb) + { + il.Emit(opcode, sb); + } + else if (instr.Operand is int i) + { + il.Emit(opcode, i); + } + else if (instr.Operand is short s) + { + il.Emit(opcode, s); + } + else if (instr.Operand is byte b) + { + il.Emit(opcode, b); + } + else if (instr.Operand is long l) + { + il.Emit(opcode, l); + } + else + { + throw new Exception($"Unhandled operand type: { instr.Operand.GetType() }"); + } + } + + // Return the temp array + il.Emit(SRE.OpCodes.Ldloc_S, 1); + il.Emit(SRE.OpCodes.Ret); + + var deriverDelegate = (Func)deriverMethod.CreateDelegate(typeof(Func)); + return deriverDelegate; + } + + public byte[] DecryptData(uint[] data, uint[] key) + { + uint[] temp = new uint[key.Length]; + byte[] ret = new byte[data.Length << 2]; + int s = 0, d = 0; + while (s < data.Length) + { + for (int j = 0; j < 16; j++) + { + temp[j] = data[s + j]; + } + + temp = decryptDelegate.Invoke(key, temp); + + for (int j = 0; j < 16; j++) + { + uint t = temp[j]; + ret[d++] = (byte)t; + ret[d++] = (byte)(t >> 8); + ret[d++] = (byte)(t >> 16); + ret[d++] = (byte)(t >> 24); + key[j] ^= t; + } + s += 16; + } + + return ret; + } + + } +} diff --git a/UnConfuserEx/Protections/Constants/IDecryptor.cs b/UnConfuserEx/Protections/Constants/IDecryptor.cs new file mode 100644 index 0000000..d63a8c8 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/IDecryptor.cs @@ -0,0 +1,9 @@ +namespace UnConfuserEx.Protections.Constants +{ + internal interface IDecryptor + { + + byte[] DecryptData(uint[] data, uint[] key); + + } +} diff --git a/UnConfuserEx/Protections/Constants/IResolver.cs b/UnConfuserEx/Protections/Constants/IResolver.cs new file mode 100644 index 0000000..aabc037 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/IResolver.cs @@ -0,0 +1,12 @@ +using dnlib.DotNet; +using System.Collections.Generic; + +namespace UnConfuserEx.Protections.Constants +{ + internal interface IResolver + { + + public void Resolve(MethodDef method, IList instances); + + } +} diff --git a/UnConfuserEx/Protections/Constants/NormalDecryptor.cs b/UnConfuserEx/Protections/Constants/NormalDecryptor.cs new file mode 100644 index 0000000..54df6a2 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/NormalDecryptor.cs @@ -0,0 +1,48 @@ +namespace UnConfuserEx.Protections.Constants +{ + internal class NormalDecryptor : IDecryptor + { + + public byte[] DecryptData(uint[] data, uint[] key) + { + uint[] temp = new uint[key.Length]; + byte[] ret = new byte[data.Length << 2]; + int s = 0, d = 0; + while (s < data.Length) + { + for (int j = 0; j < 16; j++) + { + temp[j] = data[s + j]; + } + temp[0] = temp[0] ^ key[0]; + temp[1] = temp[1] ^ key[1]; + temp[2] = temp[2] ^ key[2]; + temp[3] = temp[3] ^ key[3]; + temp[4] = temp[4] ^ key[4]; + temp[5] = temp[5] ^ key[5]; + temp[6] = temp[6] ^ key[6]; + temp[7] = temp[7] ^ key[7]; + temp[8] = temp[8] ^ key[8]; + temp[9] = temp[9] ^ key[9]; + temp[10] = temp[10] ^ key[10]; + temp[11] = temp[11] ^ key[11]; + temp[12] = temp[12] ^ key[12]; + temp[13] = temp[13] ^ key[13]; + temp[14] = temp[14] ^ key[14]; + temp[15] = temp[15] ^ key[15]; + for (int j = 0; j < 16; j++) + { + uint t = temp[j]; + ret[d++] = (byte)t; + ret[d++] = (byte)(t >> 8); + ret[d++] = (byte)(t >> 16); + ret[d++] = (byte)(t >> 24); + key[j] ^= t; + } + s += 16; + } + return ret; + } + + } +} diff --git a/UnConfuserEx/Protections/Constants/NormalResolver.cs b/UnConfuserEx/Protections/Constants/NormalResolver.cs new file mode 100644 index 0000000..cc2a322 --- /dev/null +++ b/UnConfuserEx/Protections/Constants/NormalResolver.cs @@ -0,0 +1,176 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using log4net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace UnConfuserEx.Protections.Constants +{ + internal class NormalResolver : IResolver + { + private static ILog Logger = LogManager.GetLogger("Constants"); + + private byte[] data; + + public NormalResolver(byte[] data) + { + this.data = data; + } + + public void Resolve(MethodDef getter, IList instances) + { + var key1 = (int)getter.Body.Instructions[5].Operand; + var key2 = (int)getter.Body.Instructions[7].Operand; + + var (stringId, numId, objectId) = GetIdsFromGetter(getter); + + foreach (var method in instances) + { + if (ConstantsCFG.IsCFGPresent(method)) + { + new ConstantsCFG(method).RemoveFromMethod(); + } + + TypeSig? genericType; + int instanceOffset = GetNextInstanceInMethod(getter, method, out genericType); + + while (instanceOffset != -1) + { + var instrs = method.Body.Instructions; + + var id = instrs[instanceOffset].GetLdcI4Value(); + id = (id * key1) ^ key2; + int type = (int)((uint)id >> 0x1e); + id = (id & 0x3fffffff) << 2; + + try + { + if (type == stringId) + { + FixStringConstant(method, instanceOffset, id); + } + else if (type == numId) + { + FixNumberConstant(method, instanceOffset, id, genericType!); + } + else if (type == objectId) + { + FixObjectConstant(method, instanceOffset, id, genericType!); + } + else + { + FixDefaultConstant(method, instanceOffset, genericType!); + } + } + catch (Exception ex) + { + Logger.Error($"Failed to remove constants obfuscation from method ${method.FullName} ({ex.Message})"); + return; + } + + + instanceOffset = GetNextInstanceInMethod(getter, method, out genericType); + } + + method.Body.UpdateInstructionOffsets(); + } + } + + private int GetNextInstanceInMethod(MethodDef getter, MethodDef method, out TypeSig? genericType) + { + var instrs = method.Body.Instructions; + + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Call && + instrs[i].Operand is MethodSpec ms && + ms.Method.ResolveMethodDef() is MethodDef md && + md.Equals(getter) && + instrs[i - 1].IsLdcI4()) + { + genericType = ms.GenericInstMethodSig.GenericArguments[0]; + return i - 1; + } + } + genericType = null; + return -1; + } + + private void FixStringConstant(MethodDef method, int instrOffset, int id) + { + uint count = (uint)(data![id] | (data[id + 1] << 8) | (data[id + 2] << 16) | (data[id + 3] << 24)); + count = (count << 4) | (count >> 0x1C); + string result = string.Intern(Encoding.UTF8.GetString(data, id + 4, (int)count)); + + method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldstr; + method.Body.Instructions[instrOffset].Operand = result; + method.Body.Instructions.RemoveAt(instrOffset + 1); + } + + private void FixNumberConstant(MethodDef method, int instrOffset, int id, TypeSig type) + { + switch (type.ElementType) + { + case ElementType.I4: + FixNumberConstant(method, instrOffset, id); + method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldc_I4; + break; + case ElementType.R8: + FixNumberConstant(method, instrOffset, id); + method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldc_R8; + break; + case ElementType.R4: + FixNumberConstant(method, instrOffset, id); + method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldc_R4; + break; + + default: + throw new NotImplementedException($"Can't fix number constant. Type is {type.TypeName}"); + } + } + + private void FixNumberConstant(MethodDef method, int instrOffset, int id) + { + T[] array = new T[1]; + Buffer.BlockCopy(data!, id, array, 0, Marshal.SizeOf(default(T))); + + method.Body.Instructions[instrOffset].Operand = array[0]; + method.Body.Instructions.RemoveAt(instrOffset + 1); + } + + private void FixObjectConstant(MethodDef method, int instrOffset, int id, TypeSig type) + { + int num0 = data![id] | (data[id + 1] << 8) | (data[id + 2] << 16) | (data[id + 3] << 24); + uint num1 = (uint)(data![id + 4] | (data[id + 5] << 8) | (data[id + 6] << 16) | (data[id + 7] << 24)); + num1 = (num1 << 4) | (num1 >> 0x1C); + + throw new NotImplementedException("Object constant not handled"); + } + + private void FixDefaultConstant(MethodDef method, int instrOffset, TypeSig type) + { + method.Body.Instructions[instrOffset].OpCode = OpCodes.Initobj; + method.Body.Instructions[instrOffset].Operand = type.ToTypeDefOrRef(); + method.Body.Instructions.RemoveAt(instrOffset + 1); + } + + private (int stringId, int numId, int objectId) GetIdsFromGetter(MethodDef getter) + { + var locals = getter.Body.Variables; + var keyLocal = getter.Body.Variables.Locals[0]; + + var indices = getter.Body.Instructions + .Where(i => i.IsLdloc() && i.GetLocal(locals) == keyLocal) + .Select(i => getter.Body.Instructions.IndexOf(i) + 1).ToList(); + + var stringId = getter.Body.Instructions[indices[0]].GetLdcI4Value(); + var numId = getter.Body.Instructions[indices[1]].GetLdcI4Value(); + var objectId = getter.Body.Instructions[indices[2]].GetLdcI4Value(); + + return (stringId, numId, objectId); + } + } +} diff --git a/UnConfuserEx/Protections/Constants/X86Resolver.cs b/UnConfuserEx/Protections/Constants/X86Resolver.cs new file mode 100644 index 0000000..56408dc --- /dev/null +++ b/UnConfuserEx/Protections/Constants/X86Resolver.cs @@ -0,0 +1,59 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using log4net; +using System; +using System.Collections.Generic; +using X86Emulator; + +namespace UnConfuserEx.Protections.Constants +{ + internal class X86Resolver : IResolver + { + private static ILog Logger = LogManager.GetLogger("Constants"); + + private ModuleDefMD Module; + private byte[] Data; + + public X86Resolver(ModuleDefMD module, byte[] data) + { + Module = module; + Data = data; + } + + public void Resolve(MethodDef getter, IList instances) + { + var x86MethodDef = (MethodDef)getter.Body.Instructions[5].Operand; + + var x86Method = new X86Method(Module, x86MethodDef); + + foreach (var method in instances) + { + int id = -1; + TypeSig? genericType = null; + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Call + && instrs[i].Operand is MethodSpec m + && m.Method.ResolveMethodDef().Equals(getter)) + { + id = (int)instrs[i - 1].Operand; + genericType = m.GenericInstMethodSig.GenericArguments[0]; + break; + } + } + + if (id == -1) + throw new Exception("Failed to get ID for constant decryption"); + + + id = x86Method.Emulate(new int[] { id }); + var type = (int)((uint)id >> 0x1E); + id = (id & 0x3FFFFFFF) << 2; + + Logger.Debug($"X86 Constant getter:\n\tid: {id}\n\ttype: {type}\n\tgenericType: {genericType}"); + } + } + + } +} diff --git a/UnConfuserEx/Protections/ControlFlow/ControlFlowRemover.cs b/UnConfuserEx/Protections/ControlFlow/ControlFlowRemover.cs new file mode 100644 index 0000000..b328235 --- /dev/null +++ b/UnConfuserEx/Protections/ControlFlow/ControlFlowRemover.cs @@ -0,0 +1,122 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using log4net; +using UnConfuserEx.Protections.ControlFlow; +using de4dot.blocks; +using de4dot.blocks.cflow; + +namespace UnConfuserEx.Protections +{ + internal class ControlFlowRemover : IProtection + { + static ILog Logger = LogManager.GetLogger("ControlFlow"); + + public string Name => "ControlFlow"; + + private readonly IList ObfuscatedMethods = new List(); + + public bool IsPresent(ref ModuleDefMD module) + { + /* + * Go through all of the methods in the module + * if they all contain a switch then it's present + */ + foreach (var method in module.GetTypes().SelectMany(t => t.Methods)) + { + if (IsMethodObfuscated(method)) + { + ObfuscatedMethods.Add(method); + } + } + + return ObfuscatedMethods.Any(); + } + + public bool Remove(ref ModuleDefMD module) + { + int numSolved = 0; + int numFailed = 0; + + Logger.Debug($"Found {ObfuscatedMethods.Count} obfuscated methods"); + foreach (var method in ObfuscatedMethods) + { + try + { + Logger.Debug($"Removing obfuscation from method {method.FullName}"); + + var deobfuscator = new BlocksCflowDeobfuscator(); + var blocks = new Blocks(method); + blocks.RemoveDeadBlocks(); + blocks.RepartitionBlocks(); + blocks.UpdateBlocks(); + + blocks.Method.Body.SimplifyBranches(); + blocks.Method.Body.OptimizeBranches(); + + deobfuscator.Initialize(blocks); + deobfuscator.Add(new SwitchDeobfuscator(module)); + deobfuscator.Deobfuscate(); + + blocks.RepartitionBlocks(); + + IList instructions; + IList exceptionHandlers; + blocks.GetCode(out instructions, out exceptionHandlers); + DotNetUtils.RestoreBody(method, instructions, exceptionHandlers); + + if (IsMethodObfuscated(method)) + { + throw new Exception("Method still obfuscated after deobfuscation"); + } + + numSolved++; + } + catch (Exception e) + { + Logger.Error($"Failed to remove obfuscation for method {method.FullName} ({e.Message})"); + Logger.Error(e.StackTrace); + numFailed++; + } + } + + var msg = $"Removed obfuscation from {numSolved} methods. Failed to remove from {numFailed} methods"; + if (numFailed > 0) + { + Logger.Error(msg); + } + else + { + Logger.Debug(msg); + } + return true; + } + + private static bool IsMethodObfuscated(MethodDef method) + { + if (!method.HasBody || method.Body.Instructions.Count == 0) + return false; + + + return IsSwitchObfuscation(method.Body.Instructions.ToList()); + } + + public static bool IsSwitchObfuscation(List instrs) + { + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Switch + && instrs[i - 1].OpCode == OpCodes.Rem_Un + && instrs[i - 2].IsLdcI4() + && instrs[i].Operand is Instruction[] cases + && cases.Length == instrs[i - 2].GetLdcI4Value()) + { + return true; + } + } + return false; + } + } +} diff --git a/UnConfuserEx/Protections/ControlFlow/SwitchDeobfuscator.cs b/UnConfuserEx/Protections/ControlFlow/SwitchDeobfuscator.cs new file mode 100644 index 0000000..d9766bb --- /dev/null +++ b/UnConfuserEx/Protections/ControlFlow/SwitchDeobfuscator.cs @@ -0,0 +1,272 @@ +using de4dot.blocks; +using de4dot.blocks.cflow; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using log4net; +using System.Collections.Generic; +using System.Linq; +using X86Emulator; + +namespace UnConfuserEx.Protections.ControlFlow +{ + internal class SwitchDeobfuscator : BlockDeobfuscator + { + static ILog Logger = LogManager.GetLogger("ControlFlow"); + + private ModuleDefMD Module; + private List locals = new(); + private Block? switchBlock = null; + private List switchCases = new(); + private Local? switchLocal = null; + + private int methodOffset = -1; + private X86Method? x86Method = null; + + public SwitchDeobfuscator(ModuleDefMD module) + { + Module = module; + } + + protected override bool Deobfuscate(Block block) + { + bool modified = false; + + if (block.LastInstr.OpCode == OpCodes.Switch && IsSwitchBlock(block)) + { + modified = DeobfuscateSwitchBlock(); + } + return modified; + } + + private bool IsSwitchBlock(Block curBlock) + { + var InstrToBlock = new Dictionary(); + foreach (var block in allBlocks) + { + foreach (var instr in block.Instructions.Select(i => i.Instruction)) + { + InstrToBlock[instr] = block; + } + } + + var instrs = curBlock.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Switch && + instrs[i - 1].OpCode == OpCodes.Rem_Un && + instrs[i - 2].IsLdcI4() && + instrs[i].Operand is Instruction[] cases && + cases.Length == instrs[i - 2].GetLdcI4Value()) + { + locals = blocks.Method.Body.Variables.Locals.ToList(); + switchBlock = curBlock; + switchCases = switchBlock.Targets; + switchLocal = instrs[^4].Instruction.GetLocal(locals); + + if (instrs[i - 5].OpCode == OpCodes.Call) + { + methodOffset = i - 5; + x86Method = new X86Method(Module, (MethodDef)instrs[i - 5].Operand); + } + + return true; + } + } + + return false; + } + + private bool DeobfuscateSwitchBlock() + { + bool ret = false; + int processedCount = 0; + + List curBlocks = new(); + foreach (var block in allBlocks) + { + if (block.FallThrough == switchBlock) + { + curBlocks.Add(block); + } + } + + bool modified; + do + { + modified = false; + + // Sometimes de4dot doesn't split the ldc.i4 going into the switchblock + // so we need to handle that in a special case + if (switchBlock!.FirstInstr.IsLdcI4()) + { + var key = switchBlock.FirstInstr.GetLdcI4Value(); + var (nextKey, nextCase) = GetNextKeyAndCase(key); + + var nextBlock = switchCases[nextCase]; + UpdateKeyInBlock(nextBlock, nextKey); + + switchBlock.Sources[0].SetNewFallThrough(nextBlock); + switchBlock.Instructions[0] = new Instr(Instruction.Create(OpCodes.Nop)); + + modified = true; + + } + + foreach (var block in curBlocks) + { + if (block.LastInstr.IsLdcI4()) + { + var key = block.LastInstr.GetLdcI4Value(); + var (nextKey, nextCase) = GetNextKeyAndCase(key); + + var nextBlock = switchCases[nextCase]; + UpdateKeyInBlock(nextBlock, nextKey); + + block.ReplaceLastInstrsWithBranch(1, nextBlock); + + modified = true; + processedCount++; + } + else if (block.Instructions.Count >= 5 && + block.Instructions[^2].IsLdcI4() && + block.Instructions[^4].IsLdcI4()) + { + if (!block.Instructions[^5].IsLdcI4()) + { + continue; + } + var key = block.Instructions[^5].GetLdcI4Value(); + var mulVal = block.Instructions[^4].GetLdcI4Value(); + var xorVal = block.Instructions[^2].GetLdcI4Value(); + + var switchVal = (key * mulVal) ^ xorVal; + var (nextKey, nextCase) = GetNextKeyAndCase(switchVal); + + var nextBlock = switchCases[nextCase]; + UpdateKeyInBlock(nextBlock, nextKey); + + block.ReplaceLastInstrsWithBranch(5, nextBlock); + + modified = true; + processedCount++; + } + else if (block.Sources.Count == 2 && + block.Instructions.Count == 5 && + block.Instructions[2].IsLdcI4() && + block.LastInstr.OpCode == OpCodes.Xor) + { + if (!block.Instructions[1].IsLdcI4()) + { + continue; + } + var key = block.Instructions[1].GetLdcI4Value(); + var mulVal = block.Instructions[2].GetLdcI4Value(); + + var sources = new List(block.Sources); + foreach (var source in sources) + { + var xorVal = source.FirstInstr.GetLdcI4Value(); + + var switchVal = (key * mulVal) ^ xorVal; + var (nextKey, nextCase) = GetNextKeyAndCase(switchVal); + + var nextBlock = switchCases[nextCase]; + UpdateKeyInBlock(nextBlock, nextKey); + + source.ReplaceLastInstrsWithBranch(source.Instructions.Count, nextBlock); + } + + modified = true; + processedCount++; + } + else if (block.Sources.Count == 2 && + block.Instructions.Count == 1 && + block.Instructions[0].OpCode == OpCodes.Pop) + { + var sources = new List(block.Sources); + foreach (var source in sources) + { + var key = source.FirstInstr.GetLdcI4Value(); + + var (nextKey, nextCase) = GetNextKeyAndCase(key); + + var nextBlock = switchCases[nextCase]; + UpdateKeyInBlock(nextBlock, nextKey); + + source.ReplaceLastInstrsWithBranch(source.Instructions.Count, nextBlock); + } + + modified = true; + processedCount++; + } + } + + if (modified) + { + ret = true; + } + + } while (modified); + + if (processedCount != curBlocks.Count) + { + Logger.Warn($"Not all obfuscated blocks were processed! Only processed {processedCount} out of {curBlocks.Count}"); + } + + return ret; + } + + private (int nextKey, int nextCase) GetNextKeyAndCase(int key) + { + if (x86Method == null) + { + var xorKey = switchBlock!.FirstInstr.GetLdcI4Value(); + var nextKey = key ^ xorKey; + return (nextKey, nextKey % switchCases.Count); + } + else + { + var nextKey = x86Method.Emulate(new int[] { key }); + return (nextKey, nextKey % switchCases.Count); + } + } + + private void UpdateKeyInBlock(Block block, int key) + { + if (block.IsConditionalBranch()) + { + if (block.FallThrough == null || block.FallThrough.FallThrough == null) + { + return; + } + + if (block.FallThrough.FallThrough == switchBlock) + { + block = block.FallThrough; + } + else + { + block = block.FallThrough.FallThrough; + } + } + + if (block.LastInstr.OpCode == OpCodes.Switch) + { + UpdateKeyInBlock(block.FallThrough, key); + return; + } + + for (int i = 0; i < block.Instructions.Count; i++) + { + var instr = block.Instructions[i]; + if (instr.IsLdloc() && instr.Instruction.GetLocal(locals) == switchLocal) + { + block.Replace(i, 1, Instruction.CreateLdcI4(key)); + return; + } + } + + } + + } +} diff --git a/UnConfuserEx/Protections/IProtection.cs b/UnConfuserEx/Protections/IProtection.cs new file mode 100644 index 0000000..b5d46e5 --- /dev/null +++ b/UnConfuserEx/Protections/IProtection.cs @@ -0,0 +1,18 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections +{ + internal interface IProtection + { + string Name { get; } + + public bool IsPresent(ref ModuleDefMD module); + + public bool Remove(ref ModuleDefMD module); + } +} diff --git a/UnConfuserEx/Protections/RefProxy/RefProxyHandler.cs b/UnConfuserEx/Protections/RefProxy/RefProxyHandler.cs new file mode 100644 index 0000000..825a10d --- /dev/null +++ b/UnConfuserEx/Protections/RefProxy/RefProxyHandler.cs @@ -0,0 +1,111 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using MSILEmulator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using X86Emulator; + +namespace UnConfuserEx.Protections.Delegates +{ + internal class RefProxyHandler + { + private MethodDef Handler; + private X86Method? X86Method; + private int[] NameChars = new int[5]; + private int[] Shifts = new int[4]; + + public RefProxyHandler(ModuleDefMD module, MethodDef handler) + { + Handler = handler; + + var instrs = handler.Body.Instructions; + var nameCharsFound = 0; + var shiftsFound = 0; + for (int i = 0; i < instrs.Count - 2; i++) + { + // Find the name indices and the shifts + if (nameCharsFound == 5) + { + break; + } + + if (instrs[i].OpCode == OpCodes.Callvirt && + instrs[i].Operand is IMethodDefOrRef md && + md.Name.Contains("get_Name") && + instrs[i + 1].IsLdcI4()) + { + NameChars[nameCharsFound++] = instrs[i + 1].GetLdcI4Value(); + } + else if (instrs[i].IsLdcI4() && + instrs[i].GetLdcI4Value() == 0x1f) + { + Shifts[shiftsFound++] = instrs[i - 1].GetLdcI4Value(); + } + } + + var method = instrs.Where(i => i.OpCode == OpCodes.Call).Skip(1).First().Operand as MethodDef; + if (method!.IsNative) + { + X86Method = new X86Method(module, method); + } + } + + public MDToken GetMethodMDToken(FieldDef field) + { + var fieldSig = field.FieldSig.ExtraData; + + int key; + if (field.FieldType is CModOptSig optSig) + { + key = (int)optSig.Modifier.MDToken.Raw; + } + else + { + throw new Exception("First field type wasn't an optional modifier - need to iterate"); + } + + key += ((field.Name.String[NameChars[0]] ^ (char)fieldSig[^1]) << Shifts[0]) + + ((field.Name.String[NameChars[1]] ^ (char)fieldSig[^2]) << Shifts[1]) + + ((field.Name.String[NameChars[2]] ^ (char)fieldSig[^4]) << Shifts[2]) + + ((field.Name.String[NameChars[3]] ^ (char)fieldSig[^5]) << Shifts[3]); + + if (X86Method != null) + { + key = X86Method.Emulate(new int[] { key }); + } + else + { + throw new NotImplementedException("Only x86 delegate removal is supported"); + } + + key *= GetFieldHash(field); + + return new MDToken(key); + } + + public OpCode GetOpCode(FieldDef field, byte opKey) + { + var opCode = (Code)(field.Name.String[NameChars[4]] ^ opKey); + return opCode.ToOpCode(); + } + + private int GetFieldHash(FieldDef field) + { + var customAttribute = field.CustomAttributes[0]; + + var ctor = (MethodDef)customAttribute.Constructor; + var arg = (int)customAttribute.ConstructorArguments[0].Value; + + var ilMethod = new ILMethod(ctor, 3, ctor.Body.Instructions.Count - 1); + + ilMethod.SetArg(1, arg); + + Context ctx = ilMethod.Emulate(); + + return (int)ctx.Stack.Pop(); + } + } +} diff --git a/UnConfuserEx/Protections/RefProxy/RefProxyRemover.cs b/UnConfuserEx/Protections/RefProxy/RefProxyRemover.cs new file mode 100644 index 0000000..847cdda --- /dev/null +++ b/UnConfuserEx/Protections/RefProxy/RefProxyRemover.cs @@ -0,0 +1,216 @@ +using de4dot.blocks.cflow; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.MD; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Delegates +{ + internal class RefProxyRemover : IProtection + { + public string Name => "RefProxy"; + + private ModuleDefMD? Module; + private List HandlerMethods = new(); + private HashSet Delegates = new(); + private HashSet DelegateCtors = new(); + private Dictionary DelegateHandlers = new(); + private Dictionary> ResolvedDelegates = new(); + + public bool IsPresent(ref ModuleDefMD module) + { + Module = module; + + // Check in the default module for methods with the signature + // static void SMethod1(RuntimeFieldHandle field, byte opKey) + + foreach (var method in Module.GlobalType.Methods) + { + if (method.MethodSig.ToString() == "System.Void (System.RuntimeFieldHandle,System.Byte)") + { + HandlerMethods.Add(method); + } + } + + return HandlerMethods.Any(); + } + + public bool Remove(ref ModuleDefMD module) + { + foreach (var handler in HandlerMethods) + { + var instances = GetAllInstances(handler); + + Delegates.UnionWith(instances.Select(instance => instance.DeclaringType)); + DelegateCtors.UnionWith(instances); + + var delegateHandler = new RefProxyHandler(module, handler); + DelegateHandlers[handler] = delegateHandler; + } + + ResolveAllDelegates(); + ReplaceDelegateInvocations(); + + RemoveHandlers(); + RemoveDelegateClasses(); + + return true; + } + + private HashSet GetAllInstances(MethodDef delegateHandler) + { + var placesUsed = new HashSet(); + + foreach (var method in Module!.GetTypes().SelectMany(m => m.Methods)) + { + if (!method.HasBody) + continue; + + foreach (var instr in method.Body.Instructions) + { + if (instr.OpCode == OpCodes.Call && instr.Operand is MethodDef called) + { + if (called == delegateHandler) + { + placesUsed.Add(method); + } + } + } + } + + return placesUsed; + } + + private void ResolveAllDelegates() + { + foreach (var @delegate in DelegateCtors) + { + for (int i = 0; i < @delegate.Body.Instructions.Count - 2; i += 3) + { + var field = (FieldDef)@delegate.Body.Instructions[i].Operand; + var opKey = (byte)@delegate.Body.Instructions[i + 1].GetLdcI4Value(); + var handler = (MethodDef)@delegate.Body.Instructions[i + 2].Operand; + + var token = DelegateHandlers[handler].GetMethodMDToken(field); + var opCode = DelegateHandlers[handler].GetOpCode(field, opKey); + + if (token.Table == Table.MemberRef) + { + var method = Module!.ResolveMemberRef(token.Rid); + ResolvedDelegates[field] = new(opCode, method); + } + else if (token.Table == Table.Method) + { + var method = Module!.ResolveMethod(token.Rid); + ResolvedDelegates[field] = new(opCode, method); + } + else + { + throw new NotImplementedException($"Unhandled token type: {token.Table}"); + } + } + } + } + + private void ReplaceDelegateInvocations() + { + var fieldStack = new Stack(); + foreach (var method in Module!.GetTypes().SelectMany(m => m.Methods).Where(m => m.HasBody)) + { + var instrsToRemove = new List(); + + var instrs = method.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + var instr = instrs[i]; + if (instr.OpCode == OpCodes.Ldsfld && + instr.Operand is FieldDef f && + ResolvedDelegates.ContainsKey(f)) + { + instrsToRemove.Add(i); + fieldStack.Push(f); + } + else if (instr.OpCode == OpCodes.Call && + instr.Operand is MethodDef m) + { + // Normal delegate invocation + if (fieldStack.Count > 0 && + m.DeclaringType == fieldStack.Peek().DeclaringType) + { + var field = fieldStack.Pop(); + var (opCode, resolvedMethod) = ResolvedDelegates[field]; + + instr.OpCode = opCode; + instr.Operand = resolvedMethod; + } + // Static method inside one of the MulticastDelegates + else if (Delegates.Contains(m.DeclaringType)) + { + var staticInvoke = (MethodDef)instr.Operand; + var invokeInstrs = staticInvoke.Body.Instructions; + + // Two possibilities: + // This static method has not been fixed + if (invokeInstrs[0].OpCode == OpCodes.Ldsfld) + { + var field = (FieldDef)invokeInstrs[0].Operand; + var (opCode, resolvedMethod) = ResolvedDelegates[field]; + + instr.OpCode = opCode; + instr.Operand = resolvedMethod; + } + // This static method has already been fixed + else + { + var invokeInstr = staticInvoke.Body.Instructions[staticInvoke.Parameters.Count]; + + instrs[i].OpCode = invokeInstr.OpCode; + instrs[i].Operand = invokeInstr.Operand; + } + } + + } + } + + if (instrsToRemove.Count > 0) + { + if (fieldStack.Count > 0) + { + throw new Exception("Delegate Field stack not empty!"); + } + + instrsToRemove.Reverse(); + + foreach (var instrIndex in instrsToRemove) + { + instrs[instrIndex].OpCode = instrs[instrIndex + 1].OpCode; + instrs[instrIndex].Operand = instrs[instrIndex + 1].Operand; + + instrs.RemoveAt(instrIndex + 1); + } + } + } + } + + private void RemoveHandlers() + { + foreach (var handler in HandlerMethods) + { + handler.DeclaringType.Methods.Remove(handler); + } + } + + private void RemoveDelegateClasses() + { + foreach (var type in DelegateCtors.Select(ctor => ctor.DeclaringType)) + { + Module!.Types.Remove(type); + } + } + + } +} \ No newline at end of file diff --git a/UnConfuserEx/Protections/Renamer/TypeInfo.cs b/UnConfuserEx/Protections/Renamer/TypeInfo.cs new file mode 100644 index 0000000..3737045 --- /dev/null +++ b/UnConfuserEx/Protections/Renamer/TypeInfo.cs @@ -0,0 +1,155 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Renamer +{ + internal class TypeInfo + { + private readonly TypeDef Type; + public string OriginalName; + public string NewName; + + public Dictionary FieldNames = new(); + public Dictionary MethodNames = new(); + public Dictionary PropNames = new(); + public Dictionary GenericParamNames = new(); + + public TypeInfo(TypeDef type) + { + Type = type; + NewName = OriginalName = type.Name; + + + RenameGenericParameters(); + RenameMethods(); + RenameFields(); + RenameProperties(); + + if (Utils.IsInvalidName(OriginalName)) + { + NewName = TypeRenamer.GetRenamer(type).Generate(); + if (type.HasGenericParameters) + { + NewName += "`" + Type.GenericParameters.Count; + } + type.Name = NewName; + } + + } + + private void RenameGenericParameters() + { + if (Type.HasGenericParameters) + { + if (Type.GenericParameters.Count == 1) + { + GenericParamNames[Type.GenericParameters[0].Name] = "T"; + Type.GenericParameters[0].Name = "T"; + } + else + { + var count = 0; + foreach (var param in Type.GenericParameters) + { + GenericParamNames[param.Name] = "T" + count; + param.Name = "T" + count++; + } + } + } + } + + private void RenameMethods() + { + var staticCount = 0; + var count = 0; + foreach (var method in Type.Methods) + { + if (Utils.IsInvalidName(method.Name)) + { + + string newName; + if (method.ImplMap != null) + { + newName = method.ImplMap.Name; + } + else if (method.IsStatic) + { + newName = "StaticMethod" + staticCount++; + } + else + { + newName = "Method" + count++; + } + + MethodNames[method.Name] = newName; + method.Name = newName; + } + + var paramCount = 0; + foreach (var param in method.Parameters) + { + if (param.Name == "") + { + if (!param.HasParamDef) + { + param.CreateParamDef(); + } + param.ParamDef.Name = "A_" + paramCount++; + } + } + + if (method.HasGenericParameters) + { + if (method.GenericParameters.Count == 1) + { + method.GenericParameters[0].Name = "T"; + } + else + { + var genericParamCount = 0; + foreach (var param in method.GenericParameters) + { + param.Name = "T" + genericParamCount++; + } + } + } + + } + } + + private void RenameFields() + { + var count = 0; + foreach (var field in Type.Fields) + { + if (Utils.IsInvalidName(field.Name)) + { + var newName = "Field" + count++; + + FieldNames[field.Name] = newName; + field.Name = newName; + } + } + } + + private void RenameProperties() + { + var count = 0; + foreach (var prop in Type.Properties) + { + if (Utils.IsInvalidName(prop.Name)) + { + var newName = "Prop" + count++; + + PropNames[prop.Name] = newName; + prop.Name = newName; + } + } + } + + } +} diff --git a/UnConfuserEx/Protections/Renamer/TypeRenamer.cs b/UnConfuserEx/Protections/Renamer/TypeRenamer.cs new file mode 100644 index 0000000..88300eb --- /dev/null +++ b/UnConfuserEx/Protections/Renamer/TypeRenamer.cs @@ -0,0 +1,60 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Renamer +{ + internal class TypeRenamer + { + private static TypeRenamer UnkRenamer = new("Type"); + private static TypeRenamer ClassRenamer = new("Class"); + private static TypeRenamer EnumRenamer = new("Enum"); + private static TypeRenamer DelegateRenamer = new("Delegate"); + private static TypeRenamer StructRenamer = new("Struct"); + private static TypeRenamer InterfaceRenamer = new("Interface"); + + + private int Count = 0; + private string Prefix; + + public TypeRenamer(string prefix) + { + Prefix = prefix; + } + + public string Generate() + { + return Prefix + Count++; + } + + public static TypeRenamer GetRenamer(TypeDef type) + { + if (type.IsInterface) + { + return InterfaceRenamer; + } + else if (type.IsEnum) + { + return EnumRenamer; + } + else if (type.IsClass) + { + if (type.IsDelegate) + { + return DelegateRenamer; + } + else if (type.IsValueType) + { + return StructRenamer; + } + return ClassRenamer; + } + + return UnkRenamer; + } + + } +} diff --git a/UnConfuserEx/Protections/Renamer/UnicodeRemover.cs b/UnConfuserEx/Protections/Renamer/UnicodeRemover.cs new file mode 100644 index 0000000..818571f --- /dev/null +++ b/UnConfuserEx/Protections/Renamer/UnicodeRemover.cs @@ -0,0 +1,103 @@ +using dnlib.DotNet; +using dnlib.DotNet.Writer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnConfuserEx.Protections.Renamer; + +namespace UnConfuserEx.Protections +{ + internal class UnicodeRemover : IProtection + { + public string Name => "Renamer"; + + private ModuleDefMD? Module = null; + private Dictionary NewTypeInfo = new(); + + public bool IsPresent(ref ModuleDefMD module) + { + return true; + } + + public bool Remove(ref ModuleDefMD module) + { + Module = module; + + RenameTypeDefs(); + RenameTypeRefs(); + RenameMemberRefs(); + + return true; + } + + private void RenameTypeDefs() + { + foreach (var type in Module!.GetTypes()) + { + NewTypeInfo[type] = new TypeInfo(type); + } + } + + private void RenameTypeRefs() + { + foreach (var typeRef in Module!.GetTypeRefs()) + { + if (Utils.IsInvalidName(typeRef.Name)) + { + throw new NotImplementedException(); + } + } + } + + private void RenameMemberRefs() + { + foreach (var methodDef in Module!.GetTypes().SelectMany(type => type.Methods)) + { + foreach (var ov in methodDef.Overrides) + { + RenameMemberRef(ov.MethodBody); + RenameMemberRef(ov.MethodDeclaration); + } + + if (!methodDef.HasBody) + { + continue; + } + + foreach (var instr in methodDef.Body.Instructions) + { + if (instr.Operand is MemberRef || instr.Operand is MethodSpec) + { + RenameMemberRef((IMemberRef)instr.Operand); + } + } + } + } + + private void RenameMemberRef(IMemberRef memberRef) + { + if (Utils.IsInvalidName(memberRef.Name)) + { + var declaringType = memberRef.DeclaringType.ResolveTypeDefThrow(); + var typeInfo = NewTypeInfo[declaringType]; + + if (memberRef.IsField) + { + memberRef.Name = typeInfo.FieldNames[memberRef.Name]; + } + else if (memberRef.IsMethod) + { + memberRef.Name = typeInfo.MethodNames[memberRef.Name]; + } + else + { + throw new NotImplementedException(); + } + + } + } + } +} diff --git a/UnConfuserEx/Protections/Resources/IDecryptor.cs b/UnConfuserEx/Protections/Resources/IDecryptor.cs new file mode 100644 index 0000000..f1adda4 --- /dev/null +++ b/UnConfuserEx/Protections/Resources/IDecryptor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Resources +{ + internal interface IDecryptor + { + public byte[] Decrypt(uint[] key, uint[] data); + + } +} diff --git a/UnConfuserEx/Protections/Resources/NormalDecryptor.cs b/UnConfuserEx/Protections/Resources/NormalDecryptor.cs new file mode 100644 index 0000000..9eef938 --- /dev/null +++ b/UnConfuserEx/Protections/Resources/NormalDecryptor.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx.Protections.Resources +{ + internal class NormalDecryptor : IDecryptor + { + + public byte[] Decrypt(uint[] key, uint[] data) + { + uint[] temp = new uint[key.Length]; + byte[] ret = new byte[data.Length << 2]; + int s = 0, d = 0; + while (s < data.Length) + { + for (int j = 0; j < 16; j++) + { + temp[j] = data[s + j]; + } + temp[0] = temp[0] ^ key[0]; + temp[1] = temp[1] ^ key[1]; + temp[2] = temp[2] ^ key[2]; + temp[3] = temp[3] ^ key[3]; + temp[4] = temp[4] ^ key[4]; + temp[5] = temp[5] ^ key[5]; + temp[6] = temp[6] ^ key[6]; + temp[7] = temp[7] ^ key[7]; + temp[8] = temp[8] ^ key[8]; + temp[9] = temp[9] ^ key[9]; + temp[10] = temp[10] ^ key[10]; + temp[11] = temp[11] ^ key[11]; + temp[12] = temp[12] ^ key[12]; + temp[13] = temp[13] ^ key[13]; + temp[14] = temp[14] ^ key[14]; + temp[15] = temp[15] ^ key[15]; + for (int j = 0; j < 16; j++) + { + uint t = temp[j]; + ret[d++] = (byte)t; + ret[d++] = (byte)(t >> 8); + ret[d++] = (byte)(t >> 16); + ret[d++] = (byte)(t >> 24); + key[j] ^= t; + } + s += 16; + } + return ret; + } + + } +} diff --git a/UnConfuserEx/Protections/Resources/ResourcesRemover.cs b/UnConfuserEx/Protections/Resources/ResourcesRemover.cs new file mode 100644 index 0000000..6eb43d0 --- /dev/null +++ b/UnConfuserEx/Protections/Resources/ResourcesRemover.cs @@ -0,0 +1,236 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using log4net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnConfuserEx.Protections.Resources; + +namespace UnConfuserEx.Protections +{ + internal class ResourcesRemover : IProtection + { + private static ILog Logger = LogManager.GetLogger("Resources"); + + private enum DecryptionType + { + Normal, + Dynamic + }; + + public string Name => "Resources"; + + MethodDef? initializeMethod; + byte[]? data; + + FieldDef? dataField; + FieldDef? assemblyField; + MethodDef? handlerMethod; + + public bool IsPresent(ref ModuleDefMD module) + { + var cctor = module.GlobalType.FindStaticConstructor(); + + if (cctor == null || !(cctor.HasBody)) + return false; + + IList instrs; + + // Check the first call in the cctor first + if (cctor.Body.Instructions[0].OpCode == OpCodes.Call) + { + var method = cctor.Body.Instructions[0].Operand as MethodDef; + + instrs = method!.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) + { + if (instrs[i].OpCode == OpCodes.Stsfld + && instrs[i + 1].OpCode == OpCodes.Call + && instrs[i + 1].Operand.ToString()!.Contains("AppDomain::get_CurrentDomain") + && instrs[i + 2].OpCode == OpCodes.Ldnull + && instrs[i + 3].OpCode == OpCodes.Ldftn) + { + + assemblyField = instrs[i].Operand as FieldDef; + initializeMethod = method; + handlerMethod = instrs[i + 3].Operand as MethodDef; + + return true; + } + } + } + + // If we didn't find it there, check the cctor itself + instrs = cctor.Body.Instructions; + for (int i = 0; i < instrs.Count - 3; i++) + { + if (instrs[i].OpCode == OpCodes.Stsfld + && instrs[i + 1].OpCode == OpCodes.Call + && instrs[i + 1].Operand.ToString()!.Contains("AppDomain::get_CurrentDomain") + && instrs[i + 2].OpCode == OpCodes.Ldnull + && instrs[i + 3].OpCode == OpCodes.Ldftn) + { + assemblyField = instrs[i].Operand as FieldDef; + initializeMethod = cctor; + handlerMethod = instrs[i + 3].Operand as MethodDef; + + return true; + } + } + return false; + } + + public bool Remove(ref ModuleDefMD module) + { + if (!GetEncryptedData()) + { + Logger.Error("Failed to get encrypted resource data"); + return false; + } + + if (!DecryptData()) + { + Logger.Error("Failed to decrypt resource data"); + return false; + } + + if (!DecompressData()) + { + Logger.Error("Failed to decompress resource data"); + return false; + } + + var loadedModule = ModuleDefMD.Load(data); + foreach (var resource in loadedModule.Resources) + { + int index = module.Resources.ToList().FindIndex(r => r.Name.Equals(resource.Name)); + if ( index != -1) + { + module.Resources.RemoveAt(index); + } + module.Resources.Add(resource); + } + + module.GlobalType.Methods.Remove(handlerMethod); + module.GlobalType.Fields.Remove(assemblyField); + module.GlobalType.Fields.Remove(dataField); + + // Remove call to the initialize method + if (initializeMethod == module.GlobalType.FindStaticConstructor()) + { + // TODO: Is this ever actually in the static constructor? + } + else + { + // It's the first call in the cctor + module.GlobalType.FindStaticConstructor().Body.Instructions.RemoveAt(0); + module.GlobalType.Methods.Remove(initializeMethod); + } + + return true; + } + + private bool GetEncryptedData() + { + var instrs = initializeMethod!.Body.Instructions; + for (int i = 0; i < instrs.Count; i++) + { + if (instrs[i].OpCode == OpCodes.Ldtoken + && instrs[i + 1].OpCode == OpCodes.Call) + { + dataField = instrs[i].Operand as FieldDef; + data = dataField!.InitialValue; + return true; + } + } + return false; + } + private uint[]? GetInitialArray() + { + uint? key = null; + bool? shlFirst = null; + + var instrs = initializeMethod!.Body.Instructions; + for (int i = 0; i < instrs.Count - 2; i++) + { + if (instrs[i].OpCode == OpCodes.Newarr + && instrs[i + 2].OpCode == OpCodes.Ldc_I4) + { + key = (uint)(int)instrs[i + 2].Operand; + } + else if (instrs[i].OpCode == OpCodes.Ldc_I4_S + && instrs[i].Operand is sbyte val + && val == 13) + { + shlFirst = instrs[i + 1].OpCode == OpCodes.Shl; + } + + if (key != null && shlFirst != null) + break; + } + + if (key == null || shlFirst == null) + return null; + + + uint[] ret = new uint[16]; + for (int j = 0; j < 16; j++) + { + key ^= (bool)shlFirst ? key << 13 : key >> 13; + key ^= (bool)shlFirst ? key >> 25 : key << 25; + key ^= (bool)shlFirst ? key << 27 : key >> 27; + ret[j] = (uint)key; + } + return ret; + } + + private DecryptionType GetDecryptionType() + { + // TODO: Support dynamic decryption + return DecryptionType.Normal; + } + + private bool DecryptData() + { + DecryptionType type = GetDecryptionType(); + + uint[] uintData = new uint[data!.Length >> 2]; + Buffer.BlockCopy(data, 0, uintData, 0, data.Length); + + uint[]? key = GetInitialArray(); + if (key == null) + { + Logger.Error("Failed to get initial array"); + return false; + } + + IDecryptor decryptor; + if (type == DecryptionType.Normal) + { + decryptor = new NormalDecryptor(); + } + else + { + throw new NotImplementedException(); + } + + data = decryptor.Decrypt(key, uintData); + + return true; + } + + private bool DecompressData() + { + var decompressed = Utils.DecompressDataLZMA(data!); + if (decompressed == null) + return false; + + data = decompressed; + return true; + } + } +} diff --git a/UnConfuserEx/UnConfuserEx.cs b/UnConfuserEx/UnConfuserEx.cs new file mode 100644 index 0000000..9cacec3 --- /dev/null +++ b/UnConfuserEx/UnConfuserEx.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using dnlib.DotNet; +using dnlib.DotNet.Writer; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using UnConfuserEx.Protections; +using log4net; +using log4net.Config; +using UnConfuserEx.Protections.Delegates; +using UnConfuserEx.Protections.AntiDebug; + +namespace UnConfuserEx +{ + internal class UnConfuserEx + { + static ILog Logger = LogManager.GetLogger("UnConfuserEx"); + + static int Main(string[] args) + { + XmlConfigurator.Configure(typeof(UnConfuserEx).Assembly.GetManifestResourceStream("UnConfuserEx.log4net.xml")); + + if (args.Length < 1 || args.Length > 2) + { + Logger.Error("Usage: unconfuser.exe "); + return 1; + } + + var path = args[0]; + if (!File.Exists(path)) + { + Logger.Error($"File {path} does not exist"); + return 1; + } + + // Load the module + ModuleContext context = new(); + ModuleDefMD module = ModuleDefMD.Load(path, context); + + var pipeline = new List + { + // If this is present, it MUST be removed first + new AntiTamperRemover(), + + new ControlFlowRemover(), + new ResourcesRemover(), + new ConstantsRemover(), + new RefProxyRemover(), + new UnicodeRemover(), + new AntiDebugRemover(), + }; + + foreach (var p in pipeline) + { + if (p.IsPresent(ref module)) + { + Logger.Info($"{p.Name} detected, attempting to remove"); + try + { + if (p.Remove(ref module)) + { + Logger.Info($"Successfully removed {p.Name} protection"); + } + else + { + Logger.Error($"Failed to remove {p.Name} protection"); + return 1; + } + } + catch (Exception ex) + { + Logger.Fatal($"Caught exception when trying to remove {p.Name} protection"); + Logger.Error(ex.ToString()); + return 1; + } + } + } + + var newPath = $"{Path.GetDirectoryName(path)}\\{Path.GetFileNameWithoutExtension(path)}-deobfuscated{Path.GetExtension(path)}"; + if (args.Length == 2) + { + // Use the user supplied path + newPath = args[1]; + } + + // Write the module back + Logger.Info($"All detected protections removed. Writing new module to {newPath}"); + + try + { + if (module.IsILOnly) + { + ModuleWriterOptions writerOptions = new ModuleWriterOptions(module); + module.Write(newPath, writerOptions); + } + else + { + NativeModuleWriterOptions writerOptions = new NativeModuleWriterOptions(module, true); + writerOptions.MetadataOptions.Flags = MetadataFlags.PreserveAll; + module.NativeWrite(newPath, writerOptions); + } + } + catch (Exception ex) + { + Logger.Error("Failed to write module"); + Logger.Error(ex.ToString()); + return 1; + } + Logger.Info("Deobfuscated module successfully written"); + + // Done + return 0; + } + + } +} \ No newline at end of file diff --git a/UnConfuserEx/UnConfuserEx.csproj b/UnConfuserEx/UnConfuserEx.csproj new file mode 100644 index 0000000..4c7b446 --- /dev/null +++ b/UnConfuserEx/UnConfuserEx.csproj @@ -0,0 +1,32 @@ + + + + Exe + net7.0-windows + enable + True + True + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UnConfuserEx/Utils.cs b/UnConfuserEx/Utils.cs new file mode 100644 index 0000000..4d15385 --- /dev/null +++ b/UnConfuserEx/Utils.cs @@ -0,0 +1,127 @@ +using dnlib.DotNet.Emit; +using log4net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnConfuserEx +{ + internal class Utils + { + private static ILog Logger = LogManager.GetLogger("Utils"); + + + public static byte[]? DecompressDataLZMA(byte[] data) + { + var stream = new MemoryStream(data); + var decompressedStream = new MemoryStream(); + var decoder = new SevenZip.Compression.LZMA.Decoder(); + + var properties = new byte[5]; + if (stream.Read(properties, 0, 5) != 5) + { + Logger.Error("LZMA stream is too short"); + return null; + } + decoder.SetDecoderProperties(properties); + + // TODO: I've seen some binaries where this is actually a long + // so should probably handle both cases? + + byte[] size = new byte[4]; + if (stream.Read(size, 0, sizeof(int)) != sizeof(int)) + { + Logger.Error("Failed to read stream length"); + return null; + } + + long uncompressedSize = BitConverter.ToInt32(size); + + long compressedSize = stream.Length - stream.Position; + decoder.Code(stream, decompressedStream, compressedSize, uncompressedSize, null); + return decompressedStream.ToArray(); + } + + public static (IList split, IList remaining) SplitArray(IList array, int index) + { + return (array.Take(index).ToList(), array.Skip(index).ToList()); + } + + public static OpCode InvertBranch(OpCode branch) + { + switch (branch.Code) + { + case Code.Beq: + return OpCodes.Bne_Un; + case Code.Bge: + return OpCodes.Blt; + case Code.Bge_Un: + return OpCodes.Blt_Un; + case Code.Bgt: + return OpCodes.Ble; + case Code.Bgt_Un: + return OpCodes.Ble_Un; + case Code.Ble: + return OpCodes.Bgt; + case Code.Ble_Un: + return OpCodes.Bgt_Un; + case Code.Blt: + return OpCodes.Bge; + case Code.Blt_Un: + return OpCodes.Bge_Un; + case Code.Bne_Un: + return OpCodes.Beq; + + case Code.Brtrue: + return OpCodes.Brfalse; + case Code.Brfalse: + return OpCodes.Brtrue; + + case Code.Beq_S: + return OpCodes.Bne_Un_S; + case Code.Bge_S: + return OpCodes.Blt_S; + case Code.Bge_Un_S: + return OpCodes.Blt_Un_S; + case Code.Bgt_S: + return OpCodes.Ble_S; + case Code.Bgt_Un_S: + return OpCodes.Ble_Un_S; + case Code.Ble_S: + return OpCodes.Bgt_S; + case Code.Ble_Un_S: + return OpCodes.Bgt_Un_S; + case Code.Blt_S: + return OpCodes.Bge_S; + case Code.Blt_Un_S: + return OpCodes.Bge_Un_S; + case Code.Bne_Un_S: + return OpCodes.Beq_S; + + case Code.Brtrue_S: + return OpCodes.Brfalse_S; + case Code.Brfalse_S: + return OpCodes.Brtrue_S; + + default: + throw new NotSupportedException($"Can't invert branch opcode {branch}"); + } + } + + public static int Mod(int x, int n) + { + return (x % n + n) % n; + } + + private static char[] InvalidChars = "!@#$%^&*()-=+\\,<>".ToArray(); + + public static bool IsInvalidName(string name) + { + return Encoding.UTF8.GetByteCount(name) != name.Length + || (name.Any(c => InvalidChars.Contains(c))); + } + } +} diff --git a/UnConfuserEx/log4net.xml b/UnConfuserEx/log4net.xml new file mode 100644 index 0000000..e120f04 --- /dev/null +++ b/UnConfuserEx/log4net.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/X86Emulator/EmulatorException.cs b/X86Emulator/EmulatorException.cs new file mode 100644 index 0000000..8201790 --- /dev/null +++ b/X86Emulator/EmulatorException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator +{ + internal class EmulatorException : Exception + { + public EmulatorException() + { + } + + public EmulatorException(string message) + : base(message) + { + } + + public EmulatorException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/X86Emulator/Instructions/Add.cs b/X86Emulator/Instructions/Add.cs new file mode 100644 index 0000000..899a837 --- /dev/null +++ b/X86Emulator/Instructions/Add.cs @@ -0,0 +1,45 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Add : Instruction + { + private Operand[] Operands; + + public Add(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int src; + if (Operands[1].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + src = registers.GetValue(Operands[1].Base); + } + else + { + src = Operands[1].LvalSDWord; + } + + int dst; + if (Operands[0].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + dst = registers.GetValue(Operands[0].Base); + } + else + { + dst = Operands[0].LvalSDWord; + } + + int result = dst + src; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Instructions/IMul.cs b/X86Emulator/Instructions/IMul.cs new file mode 100644 index 0000000..d045ca6 --- /dev/null +++ b/X86Emulator/Instructions/IMul.cs @@ -0,0 +1,88 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class IMul : Instruction + { + private Operand[] Operands; + + public IMul(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + if (Operands.Length == 1) + { + long eax = registers.GetValue(Registers.Register.EAX); + long val; + if (Operands[0].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + val = registers.GetValue(Operands[0].Base); + } + else + { + val = Operands[0].LvalSDWord; + } + + // Multiply value in eax with operand + long result = eax * val; + + // Store result in EDX:EAX (top 32 in edx, bottom 32 in EAX) + registers.SetValue(SharpDisasm.Udis86.ud_type.UD_R_EDX, (int)(result >> 32)); + registers.SetValue(SharpDisasm.Udis86.ud_type.UD_R_EAX, (int)(result)); + } + else if (Operands.Length == 2) + { + // Multiply Operands[0] with Operands[1] + int dst = registers.GetValue(Operands[0].Base); + int src; + if (Operands[1].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + src = registers.GetValue(Operands[1].Base); + } + else + { + src = Operands[1].LvalSDWord; + } + + // Store truncated result in Operands[0] + int result = dst * src; + registers.SetValue(Operands[0].Base, result); + } + else + { + // Multiply Operands[1] with Operands[2] + int val0; + if (Operands[1].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + val0 = registers.GetValue(Operands[1].Base); + } + else + { + val0 = Operands[1].LvalSDWord; + } + + int val1; + if (Operands[2].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + val1 = registers.GetValue(Operands[2].Base); + } + else + { + val1 = Operands[2].LvalSDWord; + } + + // Store truncated result in Operands[0] + int result = val0 * val1; + registers.SetValue(Operands[0].Base, result); + } + } + } +} diff --git a/X86Emulator/Instructions/Instruction.cs b/X86Emulator/Instructions/Instruction.cs new file mode 100644 index 0000000..aa25314 --- /dev/null +++ b/X86Emulator/Instructions/Instruction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal abstract class Instruction + { + public abstract void Emulate(Stack stack, Registers registers); + } +} diff --git a/X86Emulator/Instructions/Mov.cs b/X86Emulator/Instructions/Mov.cs new file mode 100644 index 0000000..bcc72e6 --- /dev/null +++ b/X86Emulator/Instructions/Mov.cs @@ -0,0 +1,38 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Mov : Instruction + { + private Operand[] Operands; + + public Mov(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + Operand to = Operands[0]; + Operand from = Operands[1]; + + int val; + + if (from.Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + val = registers.GetValue(from.Base); + } + else + { + val = from.LvalSDWord; + } + + registers.SetValue(to.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Neg.cs b/X86Emulator/Instructions/Neg.cs new file mode 100644 index 0000000..c99f6cb --- /dev/null +++ b/X86Emulator/Instructions/Neg.cs @@ -0,0 +1,28 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Neg : Instruction + { + private Operand Operand; + + public Neg(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = registers.GetValue(Operand.Base); + + val = -val; + + registers.SetValue(Operand.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Not.cs b/X86Emulator/Instructions/Not.cs new file mode 100644 index 0000000..369c165 --- /dev/null +++ b/X86Emulator/Instructions/Not.cs @@ -0,0 +1,28 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Not : Instruction + { + private Operand Operand; + + public Not(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = registers.GetValue(Operand.Base); + + val = ~val; + + registers.SetValue(Operand.Base, val); + } + } +} diff --git a/X86Emulator/Instructions/Pop.cs b/X86Emulator/Instructions/Pop.cs new file mode 100644 index 0000000..da86e34 --- /dev/null +++ b/X86Emulator/Instructions/Pop.cs @@ -0,0 +1,29 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Pop : Instruction + { + private Operand? Operand = null; + + public Pop(Operand? operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val = stack.Pop(); + + if (Operand != null) + { + registers.SetValue(Operand.Base, val); + } + } + } +} diff --git a/X86Emulator/Instructions/Push.cs b/X86Emulator/Instructions/Push.cs new file mode 100644 index 0000000..fac9a8d --- /dev/null +++ b/X86Emulator/Instructions/Push.cs @@ -0,0 +1,34 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Push : Instruction + { + private Operand Operand; + + public Push(Operand operand) + { + Operand = operand; + } + + public override void Emulate(Stack stack, Registers registers) + { + int val; + if (Operand.Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + val = registers.GetValue(Operand.Base); + } + else + { + val = Operand.LvalSDWord; + } + + stack.Push(val); + } + } +} diff --git a/X86Emulator/Instructions/Sub.cs b/X86Emulator/Instructions/Sub.cs new file mode 100644 index 0000000..6562271 --- /dev/null +++ b/X86Emulator/Instructions/Sub.cs @@ -0,0 +1,45 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Sub : Instruction + { + private Operand[] Operands; + + public Sub(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int src; + if (Operands[1].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + src = registers.GetValue(Operands[1].Base); + } + else + { + src = Operands[1].LvalSDWord; + } + + int dst; + if (Operands[0].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + dst = registers.GetValue(Operands[0].Base); + } + else + { + dst = Operands[0].LvalSDWord; + } + + int result = dst - src; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Instructions/Xor.cs b/X86Emulator/Instructions/Xor.cs new file mode 100644 index 0000000..6e51e1b --- /dev/null +++ b/X86Emulator/Instructions/Xor.cs @@ -0,0 +1,36 @@ +using SharpDisasm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator.Instructions +{ + internal class Xor : Instruction + { + private Operand[] Operands; + + public Xor(Operand[] operands) + { + Operands = operands; + } + + public override void Emulate(Stack stack, Registers registers) + { + int dst = registers.GetValue(Operands[0].Base); + int src; + if (Operands[1].Type == SharpDisasm.Udis86.ud_type.UD_OP_REG) + { + src = registers.GetValue(Operands[1].Base); + } + else + { + src = Operands[1].LvalSDWord; + } + + int result = src ^ dst; + registers.SetValue(Operands[0].Base, result); + } + } +} diff --git a/X86Emulator/Registers.cs b/X86Emulator/Registers.cs new file mode 100644 index 0000000..eef9493 --- /dev/null +++ b/X86Emulator/Registers.cs @@ -0,0 +1,83 @@ +using SharpDisasm.Udis86; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace X86Emulator +{ + internal class Registers + { + public int[] Values = new int[(int)Register.MAX]; + + + public int GetValue(Register register) + { + return Values[(int)register]; + } + + public int GetValue(ud_type register) + { + return register switch + { + ud_type.UD_R_EAX => Values[(int)Register.EAX], + ud_type.UD_R_ECX => Values[(int)Register.ECX], + ud_type.UD_R_EDX => Values[(int)Register.EDX], + ud_type.UD_R_EBX => Values[(int)Register.EBX], + ud_type.UD_R_ESP => Values[(int)Register.ESP], + ud_type.UD_R_EBP => Values[(int)Register.EBP], + ud_type.UD_R_ESI => Values[(int)Register.ESI], + ud_type.UD_R_EDI => Values[(int)Register.EDI], + _ => throw new EmulatorException($"Unhandled register type {register}"), + }; + } + + public void SetValue(ud_type register, int value) + { + switch (register) + { + case ud_type.UD_R_EAX: + Values[(int)Register.EAX] = value; + break; + case ud_type.UD_R_ECX: + Values[(int)Register.ECX] = value; + break; + case ud_type.UD_R_EDX: + Values[(int)Register.EDX] = value; + break; + case ud_type.UD_R_EBX: + Values[(int)Register.EBX] = value; + break; + case ud_type.UD_R_ESP: + Values[(int)Register.ESP] = value; + break; + case ud_type.UD_R_EBP: + Values[(int)Register.EBP] = value; + break; + case ud_type.UD_R_ESI: + Values[(int)Register.ESI] = value; + break; + case ud_type.UD_R_EDI: + Values[(int)Register.EDI] = value; + break; + default: + throw new EmulatorException($"Unhandled register type {register}"); + }; + } + + public enum Register + { + EAX, + ECX, + EDX, + EBX, + ESP, + EBP, + ESI, + EDI, + + MAX + } + } +} diff --git a/X86Emulator/X86Emulator.csproj b/X86Emulator/X86Emulator.csproj new file mode 100644 index 0000000..b4aefa6 --- /dev/null +++ b/X86Emulator/X86Emulator.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + enable + + + + + + + + diff --git a/X86Emulator/X86Method.cs b/X86Emulator/X86Method.cs new file mode 100644 index 0000000..70a88e9 --- /dev/null +++ b/X86Emulator/X86Method.cs @@ -0,0 +1,133 @@ +using dnlib.DotNet; +using dnlib.PE; +using System.Collections.Generic; +using X86Emulator.Instructions; +using System.Linq; +using SharpDisasm.Udis86; + +namespace X86Emulator +{ + public class X86Method + { + private ModuleDefMD Module; + private MethodDef Method; + private List Instructions = new(); + private int NumArgs = 0; + + public X86Method(ModuleDefMD module, MethodDef method) + { + Module = module; + Method = method; + + Initialize(); + } + + public int Emulate(int[] args) + { + if (args.Length != NumArgs) + { + throw new EmulatorException($"Incorrect number of args passed to method. Expected {NumArgs} got {args.Length}"); + } + + var stack = new Stack(); + var registers = new Registers(); + + if (args.Length == 1) + { + registers.SetValue(ud_type.UD_R_ECX, args[0]); + stack.Push(args[0]); + } + else if (args.Length > 1) + { + registers.SetValue(ud_type.UD_R_ECX, args[0]); + registers.SetValue(ud_type.UD_R_EDX, args[1]); + foreach (var arg in args.Skip(2)) + { + stack.Push(arg); + } + } + + foreach (var instr in Instructions) + { + instr.Emulate(stack, registers); + } + + return registers.GetValue(Registers.Register.EAX); + } + + private void Initialize() + { + NumArgs = Method.Parameters.Count; + var body = ReadNativeBody(); + + var disassembler = new SharpDisasm.Disassembler(body, SharpDisasm.ArchitectureMode.x86_32); + var instructions = disassembler.Disassemble(); + + foreach (var instr in instructions) + { + if (instr.Mnemonic == ud_mnemonic_code.UD_Iret) + { + // Need to remove the final pops before a ret + Instructions.RemoveRange(Instructions.Count - 3, 3); + break; + } + + switch (instr.Mnemonic) + { + case ud_mnemonic_code.UD_Ipush: + Instructions.Add(new Push(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Ipop: + Instructions.Add(new Pop(instr.Operands.Length == 0 ? null : instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Imov: + Instructions.Add(new Mov(instr.Operands)); + break; + case ud_mnemonic_code.UD_Inot: + Instructions.Add(new Not(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Iimul: + Instructions.Add(new IMul(instr.Operands)); + break; + case ud_mnemonic_code.UD_Ineg: + Instructions.Add(new Neg(instr.Operands[0])); + break; + case ud_mnemonic_code.UD_Ixor: + Instructions.Add(new Xor(instr.Operands)); + break; + case ud_mnemonic_code.UD_Isub: + Instructions.Add(new Sub(instr.Operands)); + break; + case ud_mnemonic_code.UD_Iadd: + Instructions.Add(new Add(instr.Operands)); + break; + + + case ud_mnemonic_code.UD_Inop: + // no-op ... + break; + + + default: + throw new EmulatorException($"Unhandled instruction type {instr.Mnemonic}"); + } + } + + } + + private byte[] ReadNativeBody() + { + var reader = Module.Metadata.PEImage.CreateReader(); + var fileOffset = Module.Metadata.PEImage.ToFileOffset(Method.NativeBody.RVA) + 0x14; + reader.Position = (uint)fileOffset; + + Module.TablesStream.TryReadMethodRow(Method.Rid + 1, out var nextMethod); + var size = Module.Metadata.PEImage.ToFileOffset((RVA)nextMethod.RVA) - fileOffset; + var bytes = ((nextMethod.ImplFlags & (ushort)MethodImplAttributes.Native) != 0) ? new byte[size] : new byte[2048]; + + + reader.ReadBytes(bytes, 0, bytes.Length); + return bytes; + } + } +} \ No newline at end of file