Initial Version

This commit is contained in:
Harry Stoltz
2022-04-28 14:22:22 +01:00
commit 5704a8f74e
66 changed files with 4629 additions and 0 deletions
+41
View File
@@ -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/
+15
View File
@@ -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();
}
}
+25
View File
@@ -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)
{
}
}
}
+178
View File
@@ -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);
}
}
}
+34
View File
@@ -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);
}
}
}
}
+19
View File
@@ -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);
}
}
}
+20
View File
@@ -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]));
}
}
}
+35
View File
@@ -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.");
}
}
}
}
+19
View File
@@ -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);
}
}
}
+18
View File
@@ -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);
}
}
}
+19
View File
@@ -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);
}
}
}
+19
View File
@@ -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);
}
}
}
+19
View File
@@ -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);
}
}
}
+19
View File
@@ -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);
}
}
}
+19
View File
@@ -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);
}
}
}
+21
View File
@@ -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;
}
}
}
+27
View File
@@ -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();
}
}
}
+11
View File
@@ -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>
+51
View File
@@ -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;
}
}
}
}
}
+18
View File
@@ -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;
}
}
}
+119
View File
@@ -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;
}
}
}
+32
View File
@@ -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>
+127
View File
@@ -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)));
}
}
}
+40
View File
@@ -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>
+25
View File
@@ -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)
{
}
}
}
+45
View File
@@ -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);
}
}
}
+88
View File
@@ -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);
}
}
}
}
+13
View File
@@ -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);
}
}
+38
View File
@@ -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);
}
}
}
+28
View File
@@ -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);
}
}
}
+28
View File
@@ -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);
}
}
}
+29
View File
@@ -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);
}
}
}
}
+34
View File
@@ -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);
}
}
}
+45
View File
@@ -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);
}
}
}
+36
View File
@@ -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);
}
}
}
+83
View File
@@ -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
}
}
}
+13
View File
@@ -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>
+133
View File
@@ -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;
}
}
}