mirror of
https://github.com/MadMin3r/UnconfuserEx
synced 2026-06-23 09:25:19 +00:00
Fix resolving of constants
This commit is contained in:
+11
-1
@@ -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<int, object> Args = new();
|
||||
public Dictionary<int, object> Locals = new();
|
||||
public Dictionary<uint, int> Offsets = new();
|
||||
public Stack<object> Stack = new();
|
||||
|
||||
public Context(IList<Instruction> instructions)
|
||||
{
|
||||
for (int i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
Offsets[instructions[i].Offset] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Instruction> 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}");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Instruction> instructions;
|
||||
IList<ExceptionHandler> 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;
|
||||
|
||||
@@ -20,8 +20,11 @@ namespace UnConfuserEx.Protections.Constants
|
||||
|
||||
public override void Resolve(MethodDef getter, IList<MethodDef> 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user