mirror of
https://github.com/MadMin3r/UnconfuserEx
synced 2026-06-23 09:25:19 +00:00
Initial Version
This commit is contained in:
+41
@@ -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/
|
||||||
@@ -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<int, object> Args = new();
|
||||||
|
public Dictionary<int, object> Locals = new();
|
||||||
|
public Stack<object> Stack = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction> 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<Instruction> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="dnlib" Version="3.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -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
|
||||||
@@ -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<MethodDef> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction>? 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<ImageSectionHeader> 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<Instruction>?) 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<Instruction> 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<ImageSectionHeader> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction> derivation;
|
||||||
|
|
||||||
|
public DynamicDeriver(IList<Instruction> derivation)
|
||||||
|
{
|
||||||
|
this.derivation = derivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint[] DeriveKey(uint[] dst, uint[] src)
|
||||||
|
{
|
||||||
|
SortedSet<int> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Local> Locals;
|
||||||
|
private Local CtxLocal;
|
||||||
|
private HashSet<BaseBlock> 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<Instruction> instructions;
|
||||||
|
IList<ExceptionHandler> 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<Instr> 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 <seed>
|
||||||
|
// 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}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction> 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<MethodDef> 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<Instruction>?) 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<MethodDef> GetAllGetters(ModuleDefMD module)
|
||||||
|
{
|
||||||
|
var methods = module.GlobalType.Methods;
|
||||||
|
var getters = new List<MethodDef>();
|
||||||
|
|
||||||
|
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<MethodDef> GetAllInstances(MethodDef getter)
|
||||||
|
{
|
||||||
|
var placesUsed = new HashSet<MethodDef>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction> decryptInstructions;
|
||||||
|
private Func<uint[], uint[], uint[]> decryptDelegate;
|
||||||
|
|
||||||
|
public DynamicDecryptor(IList<Instruction> decryptInstructions)
|
||||||
|
{
|
||||||
|
this.decryptInstructions = decryptInstructions;
|
||||||
|
decryptDelegate = GetDecryptDelegate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Func<uint[], uint[], uint[]> GetDecryptDelegate()
|
||||||
|
{
|
||||||
|
SortedSet<int> 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<int, int> 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<uint[], uint[], uint[]>)deriverMethod.CreateDelegate(typeof(Func<uint[], uint[], uint[]>));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace UnConfuserEx.Protections.Constants
|
||||||
|
{
|
||||||
|
internal interface IDecryptor
|
||||||
|
{
|
||||||
|
|
||||||
|
byte[] DecryptData(uint[] data, uint[] key);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using dnlib.DotNet;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace UnConfuserEx.Protections.Constants
|
||||||
|
{
|
||||||
|
internal interface IResolver
|
||||||
|
{
|
||||||
|
|
||||||
|
public void Resolve(MethodDef method, IList<MethodDef> instances);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MethodDef> 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<int>(method, instrOffset, id);
|
||||||
|
method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldc_I4;
|
||||||
|
break;
|
||||||
|
case ElementType.R8:
|
||||||
|
FixNumberConstant<double>(method, instrOffset, id);
|
||||||
|
method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldc_R8;
|
||||||
|
break;
|
||||||
|
case ElementType.R4:
|
||||||
|
FixNumberConstant<Single>(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<T>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MethodDef> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MethodDef> ObfuscatedMethods = new List<MethodDef>();
|
||||||
|
|
||||||
|
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<Instruction> instructions;
|
||||||
|
IList<ExceptionHandler> 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<Instruction> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Local> locals = new();
|
||||||
|
private Block? switchBlock = null;
|
||||||
|
private List<Block> 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<Instruction, Block>();
|
||||||
|
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<Block> 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>(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>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<MethodDef> HandlerMethods = new();
|
||||||
|
private HashSet<TypeDef> Delegates = new();
|
||||||
|
private HashSet<MethodDef> DelegateCtors = new();
|
||||||
|
private Dictionary<MethodDef, RefProxyHandler> DelegateHandlers = new();
|
||||||
|
private Dictionary<FieldDef, Tuple<OpCode, IMethodDefOrRef>> 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<MethodDef> GetAllInstances(MethodDef delegateHandler)
|
||||||
|
{
|
||||||
|
var placesUsed = new HashSet<MethodDef>();
|
||||||
|
|
||||||
|
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<FieldDef>();
|
||||||
|
foreach (var method in Module!.GetTypes().SelectMany(m => m.Methods).Where(m => m.HasBody))
|
||||||
|
{
|
||||||
|
var instrsToRemove = new List<int>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UTF8String, string> FieldNames = new();
|
||||||
|
public Dictionary<UTF8String, string> MethodNames = new();
|
||||||
|
public Dictionary<UTF8String, string> PropNames = new();
|
||||||
|
public Dictionary<UTF8String, string> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<TypeDef, TypeInfo> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Instruction> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <module path> <output path>");
|
||||||
|
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<IProtection>
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWindowsForms>True</UseWindowsForms>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="log4net.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="log4net.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutomaticGraphLayout.GraphViewerGDI" Version="1.1.12" />
|
||||||
|
<PackageReference Include="de4dot.blocks" Version="3.2.0" />
|
||||||
|
<PackageReference Include="dnlib" Version="3.5.0" />
|
||||||
|
<PackageReference Include="log4net" Version="2.0.14" />
|
||||||
|
<PackageReference Include="LZMA-SDK" Version="19.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MSILEmulator\MSILEmulator.csproj" />
|
||||||
|
<ProjectReference Include="..\X86Emulator\X86Emulator.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -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<T> split, IList<T> remaining) SplitArray<T>(IList<T> 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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<log4net>
|
||||||
|
<appender name="Console" type="log4net.Appender.AnsiColorTerminalAppender">
|
||||||
|
|
||||||
|
<layout type="log4net.Layout.PatternLayout">
|
||||||
|
<conversionPattern value="%-5timestamp [%thread] %-5level %-15logger - %message%newline" />
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
<mapping>
|
||||||
|
<level value="FATAL" />
|
||||||
|
<foreColor value="Red" />
|
||||||
|
<attributes value="Bright,Underscore" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="ERROR" />
|
||||||
|
<foreColor value="Red" />
|
||||||
|
<attributes value="Bright" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="WARN" />
|
||||||
|
<foreColor value="Yellow" />
|
||||||
|
<attributes value="Dim" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<foreColor value="Cyan" />
|
||||||
|
<attributes value="Dim" />
|
||||||
|
</mapping>
|
||||||
|
<mapping>
|
||||||
|
<level value="INFO" />
|
||||||
|
<foreColor value="Green" />
|
||||||
|
</mapping>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<appender-ref ref="Console" />
|
||||||
|
</root>
|
||||||
|
</log4net>
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> stack, Registers registers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> stack, Registers registers)
|
||||||
|
{
|
||||||
|
int val = registers.GetValue(Operand.Base);
|
||||||
|
|
||||||
|
val = -val;
|
||||||
|
|
||||||
|
registers.SetValue(Operand.Base, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> stack, Registers registers)
|
||||||
|
{
|
||||||
|
int val = registers.GetValue(Operand.Base);
|
||||||
|
|
||||||
|
val = ~val;
|
||||||
|
|
||||||
|
registers.SetValue(Operand.Base, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> stack, Registers registers)
|
||||||
|
{
|
||||||
|
int val = stack.Pop();
|
||||||
|
|
||||||
|
if (Operand != null)
|
||||||
|
{
|
||||||
|
registers.SetValue(Operand.Base, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="dnlib" Version="3.5.0" />
|
||||||
|
<PackageReference Include="SharpDisasm" Version="1.1.11" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -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<Instruction> 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<int>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user