Fix resolving of constants

This commit is contained in:
Harry Stoltz
2025-01-08 21:56:08 +00:00
parent 71876b65dd
commit aab3d59599
11 changed files with 282 additions and 62 deletions
+11 -1
View File
@@ -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;
}
}
}
}
+48 -7
View File
@@ -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}");
}
+51
View File
@@ -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");
}
}
}
+51
View File
@@ -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");
}
}
}
-19
View File
@@ -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;
}
+99 -11
View File
@@ -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;
+7 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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();
}