diff --git a/MSILEmulator/Context.cs b/MSILEmulator/Context.cs index 9e8009c..d3f5031 100644 --- a/MSILEmulator/Context.cs +++ b/MSILEmulator/Context.cs @@ -1,4 +1,5 @@ -using System; +using dnlib.DotNet.Emit; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,6 +11,15 @@ namespace MSILEmulator { public Dictionary Args = new(); public Dictionary Locals = new(); + public Dictionary Offsets = new(); public Stack Stack = new(); + + public Context(IList instructions) + { + for (int i = 0; i < instructions.Count; i++) + { + Offsets[instructions[i].Offset] = i; + } + } } } diff --git a/MSILEmulator/ILMethod.cs b/MSILEmulator/ILMethod.cs index 2e20358..ec3aa71 100644 --- a/MSILEmulator/ILMethod.cs +++ b/MSILEmulator/ILMethod.cs @@ -1,6 +1,7 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; using MSILEmulator.Instructions.Arithmetic; +using MSILEmulator.Instructions.Branch; using MSILEmulator.Instructions.Load; using MSILEmulator.Instructions.Logic; using MSILEmulator.Instructions.Store; @@ -20,21 +21,21 @@ namespace MSILEmulator { Method = method; Instructions = Method.Body.Instructions.ToList(); - Ctx = new(); + Ctx = new(Instructions); } public ILMethod(MethodDef method, int start, int end) { Method = method; Instructions = Method.Body.Instructions.Skip(start).Take(end - start).ToList(); - Ctx = new(); + Ctx = new(Instructions); } public ILMethod(IEnumerable instructions) { Method = null; Instructions = instructions.ToList(); - Ctx = new(); + Ctx = new(Instructions); } public void SetArg(int index, object value) @@ -55,6 +56,11 @@ namespace MSILEmulator var instr = Instructions[i]; i = EmulateInstruction(Ctx, instr, i); + + if (i == -1) + { + break; + } } return Ctx; @@ -65,9 +71,11 @@ namespace MSILEmulator switch (instr.OpCode.Code) { - // Load case Code.Ldc_I4: + case Code.Ldc_I4_S: + ctx.Stack.Push(instr.GetLdcI4Value()); + break; case Code.Ldc_I4_0: case Code.Ldc_I4_1: case Code.Ldc_I4_2: @@ -77,11 +85,14 @@ namespace MSILEmulator case Code.Ldc_I4_6: case Code.Ldc_I4_7: case Code.Ldc_I4_8: - case Code.Ldc_I4_S: - LdcI4.Emulate(ctx, instr); + ctx.Stack.Push((int)instr.OpCode.Code - (int)Code.Ldc_I4_0); break; case Code.Ldc_I4_M1: - LdcI4_M1.Emulate(ctx); + ctx.Stack.Push((int)-1); + break; + + case Code.Ldc_I8: + ctx.Stack.Push((long)instr.Operand); break; case Code.Ldarg: @@ -162,6 +173,36 @@ namespace MSILEmulator Xor.Emulate(ctx); break; + + // Branching + case Code.Br: + case Code.Br_S: + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + case Code.Beq: + case Code.Beq_S: + return Beq.Emulate(ctx, instr); + case Code.Bne_Un: + case Code.Bne_Un_S: + return Bne.Emulate(ctx, instr); + + // Conversion + case Code.Conv_U8: + var type = ctx.Stack.Peek().GetType(); + if (type == typeof(int)) + ctx.Stack.Push((long)(int)ctx.Stack.Pop()); + else + throw new NotImplementedException(); + break; + + + // Misc + case Code.Nop: + // ...No-op + break; + + case Code.Ret: + return -1; + default: throw new EmulatorException($"Unhandled OpCode {instr.OpCode}"); } diff --git a/MSILEmulator/Instructions/Branch/Beq.cs b/MSILEmulator/Instructions/Branch/Beq.cs new file mode 100644 index 0000000..43692a6 --- /dev/null +++ b/MSILEmulator/Instructions/Branch/Beq.cs @@ -0,0 +1,51 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Branch +{ + internal class Beq + { + public static int Emulate(Context ctx, Instruction instr) + { + object val2 = ctx.Stack.Pop(); + object val1 = ctx.Stack.Pop(); + + if (val1.GetType() != val2.GetType()) + { + throw new EmulatorException($"Attempted to compare different types {val1.GetType()} != {val2.GetType()}"); + } + + // Must be a better way of doing this? + + if (val1.GetType() == typeof(int)) + { + if ((int)val1 == (int)val2) + { + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + } + else + { + return ctx.Offsets[instr.Offset] + 1; + } + } + + if (val1.GetType() == typeof(long)) + { + if ((long)val1 == (long)val2) + { + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + } + else + { + return ctx.Offsets[instr.Offset] + 1; + } + } + + throw new NotImplementedException($"Comparison of {val1.GetType().Name} not handled"); + } + } +} diff --git a/MSILEmulator/Instructions/Branch/Bne.cs b/MSILEmulator/Instructions/Branch/Bne.cs new file mode 100644 index 0000000..c3d59c8 --- /dev/null +++ b/MSILEmulator/Instructions/Branch/Bne.cs @@ -0,0 +1,51 @@ +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MSILEmulator.Instructions.Branch +{ + internal class Bne + { + public static int Emulate(Context ctx, Instruction instr) + { + object val2 = ctx.Stack.Pop(); + object val1 = ctx.Stack.Pop(); + + if (val1.GetType() != val2.GetType()) + { + throw new EmulatorException($"Attempted to compare different types {val1.GetType()} != {val2.GetType()}"); + } + + // Must be a better way of doing this? + + if (val1.GetType() == typeof(int)) + { + if ((int)val1 != (int)val2) + { + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + } + else + { + return ctx.Offsets[instr.Offset] + 1; + } + } + + if (val1.GetType() == typeof(long)) + { + if ((long)val1 != (long)val2) + { + return ctx.Offsets[((Instruction)instr.Operand).Offset]; + } + else + { + return ctx.Offsets[instr.Offset] + 1; + } + } + + throw new NotImplementedException($"Comparison of {val1.GetType().Name} not handled"); + } + } +} diff --git a/MSILEmulator/Instructions/Load/LdcI4.cs b/MSILEmulator/Instructions/Load/LdcI4.cs deleted file mode 100644 index 587c64d..0000000 --- a/MSILEmulator/Instructions/Load/LdcI4.cs +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index ce5dbaf..0000000 --- a/MSILEmulator/Instructions/Load/LdcI4_M1.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/UnConfuserEx/Protections/Constants/ConstantsRemover.cs b/UnConfuserEx/Protections/Constants/ConstantsRemover.cs index 0651ae9..0ef9ee4 100644 --- a/UnConfuserEx/Protections/Constants/ConstantsRemover.cs +++ b/UnConfuserEx/Protections/Constants/ConstantsRemover.cs @@ -151,6 +151,11 @@ namespace UnConfuserEx.Protections resolver.Resolve(getter, instances.ToList()); Logger.Debug($"Removed all instances of getter ${getter.FullName}"); + + // TODO: Can't remove the method def because sometimes it's still being referenced? + // dnSpy can never find any uses though...? + getter.Body.Instructions.Clear(); + getter.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); } module.GlobalType.Fields.Remove(dataField); @@ -386,11 +391,12 @@ namespace UnConfuserEx.Protections { var instrs = getter.Body.Instructions; - if (instrs[5].OpCode == OpCodes.Call) + int offset = instrs[0].OpCode == OpCodes.Call && instrs[0].Operand.ToString()!.Contains("Assembly::GetExecutingAssembly") ? 5 : 1; + if (instrs[offset].OpCode == OpCodes.Call) { return GetterType.X86; } - else if (instrs[5].IsLdcI4() && instrs[7].IsLdcI4()) + else if (instrs[offset].IsLdcI4() && instrs[offset + 2].IsLdcI4()) { return GetterType.Normal; } diff --git a/UnConfuserEx/Protections/Constants/IResolver.cs b/UnConfuserEx/Protections/Constants/IResolver.cs index 1e7cab4..9eff76b 100644 --- a/UnConfuserEx/Protections/Constants/IResolver.cs +++ b/UnConfuserEx/Protections/Constants/IResolver.cs @@ -3,9 +3,10 @@ using dnlib.DotNet.Emit; using System; using System.Collections.Generic; using System.Linq; -using static System.Runtime.InteropServices.JavaScript.JSType; using System.Runtime.InteropServices; using System.Text; +using de4dot.blocks; +using MSILEmulator; namespace UnConfuserEx.Protections.Constants { @@ -17,18 +18,102 @@ namespace UnConfuserEx.Protections.Constants protected (int stringId, int numId, int objectId) GetIdsFromGetter(MethodDef getter) { - var locals = getter.Body.Variables; - var keyLocal = getter.Body.Variables.Locals[0]; + var blocks = new Blocks(getter); + blocks.RemoveDeadBlocks(); - var indices = getter.Body.Instructions - .Where(i => i.IsLdloc() && i.GetLocal(locals) == keyLocal) - .Select(i => getter.Body.Instructions.IndexOf(i) + 1).ToList(); + int blocksFound = 0; + foreach (var block in blocks.MethodBlocks.GetAllBlocks()) + { + // Replace each decode block with a known ldc.i4 for each decode type + foreach (var instr in block!.Instructions) + { + if (instr.OpCode == OpCodes.Newarr) + { + block.Instructions.Clear(); + block.Instructions.Add(new Instr(Instruction.CreateLdcI4(0xAA))); + block.Instructions.Add(new Instr(Instruction.Create(OpCodes.Ret))); + blocksFound++; + break; + } + else if (instr.OpCode == OpCodes.Ldtoken) + { + block.Instructions.Clear(); + block.Instructions.Add(new Instr(Instruction.CreateLdcI4(0xBB))); + block.Instructions.Add(new Instr(Instruction.Create(OpCodes.Ret))); + blocksFound++; + break; + } + else if (instr.OpCode == OpCodes.Call && instr.Operand is IMethodDefOrRef callee && callee.Name.Contains("get_UTF8")) + { + block.Instructions.Clear(); + block.Instructions.Add(new Instr(Instruction.CreateLdcI4(0xCC))); + block.Instructions.Add(new Instr(Instruction.Create(OpCodes.Ret))); + blocksFound++; + break; + } + } - var stringId = getter.Body.Instructions[indices[0]].GetLdcI4Value(); - var numId = getter.Body.Instructions[indices[1]].GetLdcI4Value(); - var objectId = getter.Body.Instructions[indices[2]].GetLdcI4Value(); + // Replace every instruction that isn't a ldloc, ldc.i4, ret, or a branch with a nop + for (int i = 0; i < block.Instructions.Count; i++) + { + var instr = block.Instructions[i]; + if (instr.IsLdloc() || + instr.IsLdcI4() || + instr.OpCode == OpCodes.Ldc_I8 || + instr.OpCode == OpCodes.Conv_U8 || + instr.OpCode == OpCodes.Ret || + instr.IsConditionalBranch() || instr.IsBr()) + { + continue; + } + block.Instructions[i] = new Instr(Instruction.Create(OpCodes.Nop)); + } + } - return (stringId, numId, objectId); + if (blocksFound != 3) + { + throw new Exception("Failed to get constant getter ids"); + } + + IList instructions; + IList exceptionHandlers; + blocks.GetCode(out instructions, out exceptionHandlers); + + // Delete instructions up until the first ldloc.0 + while (instructions.Count > 0 && !instructions[0].IsLdloc()) + { + instructions.RemoveAt(0); + } + + // Emulate the branching to find the correct IDs + int? stringId = null, numId = null, objectId = null; + for (int i = 0; i < 4; i++) + { + var ilMethod = new ILMethod(instructions); + ilMethod.SetLocal(0, i); + ilMethod.SetLocal(1, 0xDD); + + var ctx = ilMethod.Emulate(); + switch (ctx.Stack.Peek()) + { + case 0xAA: + numId = i; + break; + case 0xBB: + objectId = i; + break; + case 0xCC: + stringId = i; + break; + case 0xDD: + // Default constants + break; + default: + throw new Exception("asdasdasd"); + } + } + + return ((int)stringId!, (int)numId!, (int)objectId!); } protected int GetNextInstanceInMethod(MethodDef getter, MethodDef method, out TypeSig? genericType) @@ -62,7 +147,10 @@ namespace UnConfuserEx.Protections.Constants protected 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); + if (count > data.Length) + { + count = (count << 4) | (count >> 0x1C); + } string result = string.Intern(Encoding.UTF8.GetString(data, id + 4, (int)count)); method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldstr; diff --git a/UnConfuserEx/Protections/Constants/NormalResolver.cs b/UnConfuserEx/Protections/Constants/NormalResolver.cs index 1aa79e7..d79f14b 100644 --- a/UnConfuserEx/Protections/Constants/NormalResolver.cs +++ b/UnConfuserEx/Protections/Constants/NormalResolver.cs @@ -20,8 +20,11 @@ namespace UnConfuserEx.Protections.Constants public override void Resolve(MethodDef getter, IList instances) { - var key1 = (int)getter.Body.Instructions[5].Operand; - var key2 = (int)getter.Body.Instructions[7].Operand; + var instrs = getter.Body.Instructions; + int offset = instrs[0].OpCode == OpCodes.Call && instrs[0].Operand.ToString()!.Contains("Assembly::GetExecutingAssembly") ? 5 : 1; + + var key1 = (int)instrs[offset].Operand; + var key2 = (int)instrs[offset + 2].Operand; var (stringId, numId, objectId) = GetIdsFromGetter(getter); @@ -37,7 +40,7 @@ namespace UnConfuserEx.Protections.Constants while (instanceOffset != -1) { - var instrs = method.Body.Instructions; + instrs = method.Body.Instructions; var id = instrs[instanceOffset].GetLdcI4Value(); id = (id * key1) ^ key2; diff --git a/UnConfuserEx/UnConfuserEx.cs b/UnConfuserEx/UnConfuserEx.cs index 34f2fc1..5ea28c7 100644 --- a/UnConfuserEx/UnConfuserEx.cs +++ b/UnConfuserEx/UnConfuserEx.cs @@ -41,15 +41,19 @@ namespace UnConfuserEx { // If this is present, it MUST be removed first new AntiTamperRemover(), - + + // This must then be removed second new ControlFlowRemover(), + + // And these can all be removed in any order new ResourcesRemover(), new ConstantsRemover(), new RefProxyRemover(), new AntiDumpRemover(), - new AntiDebugRemover(), new UnicodeRemover(), + // Except for this, which requires constants to be removed + new AntiDebugRemover(), }; foreach (var p in pipeline) @@ -98,6 +102,7 @@ namespace UnConfuserEx else { NativeModuleWriterOptions writerOptions = new NativeModuleWriterOptions(module, true); + //writerOptions.Logger = DummyLogger.NoThrowInstance; module.NativeWrite(newPath, writerOptions); } } diff --git a/UnConfuserEx/Utils.cs b/UnConfuserEx/Utils.cs index b83b312..2faf89d 100644 --- a/UnConfuserEx/Utils.cs +++ b/UnConfuserEx/Utils.cs @@ -37,7 +37,7 @@ namespace UnConfuserEx long compressedSize = inStream.Length - inStream.Position; Logger.Debug($"Compressed bytes: 0x{compressedSize:X} -> Uncompressed bytes: 0x{uncompressedSize:X}"); - + decoder.Code(inStream, outStream, compressedSize, uncompressedSize, null); return outStream.ToArray(); }