mirror of
https://github.com/Zypherion-Technologies/UnConfuserEx
synced 2026-06-23 09:25:30 +00:00
Fix constants pipeline and resource handling
This commit is contained in:
@@ -132,18 +132,21 @@ namespace UnConfuserEx.Protections.Constants
|
||||
return (int)ctx.Stack.Pop();
|
||||
}
|
||||
|
||||
protected static int CollapseToLdcI4(IList<Instruction> instrs, int endInclusive)
|
||||
{
|
||||
if (instrs[endInclusive].IsLdcI4())
|
||||
return endInclusive;
|
||||
|
||||
int start = FindArithmeticStart(instrs, endInclusive);
|
||||
int value = ComputeIntValueBefore(instrs, endInclusive);
|
||||
|
||||
var replacement = Instruction.CreateLdcI4(value);
|
||||
instrs[start] = replacement;
|
||||
for (int k = endInclusive; k > start; k--)
|
||||
instrs.RemoveAt(k);
|
||||
protected static int CollapseToLdcI4(IList<Instruction> instrs, int endInclusive)
|
||||
{
|
||||
if (instrs[endInclusive].IsLdcI4())
|
||||
return endInclusive;
|
||||
|
||||
int start = FindArithmeticStart(instrs, endInclusive);
|
||||
int value = ComputeIntValueBefore(instrs, endInclusive);
|
||||
|
||||
instrs[start].OpCode = OpCodes.Ldc_I4;
|
||||
instrs[start].Operand = value;
|
||||
for (int k = start + 1; k <= endInclusive; k++)
|
||||
{
|
||||
instrs[k].OpCode = OpCodes.Nop;
|
||||
instrs[k].Operand = null;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
@@ -179,11 +182,255 @@ namespace UnConfuserEx.Protections.Constants
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static bool IsStringType(TypeSig type)
|
||||
{
|
||||
return type.ElementType == ElementType.String || type.FullName == "System.String";
|
||||
}
|
||||
|
||||
protected static bool IsSupportedNumberType(TypeSig type)
|
||||
{
|
||||
switch (type.ElementType)
|
||||
{
|
||||
case ElementType.I4:
|
||||
case ElementType.R8:
|
||||
case ElementType.R4:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void SimplifyStatefulHelperCalls(MethodDef method)
|
||||
{
|
||||
if (!method.HasBody || method.Body.Instructions.Count == 0)
|
||||
return;
|
||||
|
||||
var states = new Dictionary<Local, HelperLocalState>();
|
||||
var instrs = method.Body.Instructions;
|
||||
int index = 0;
|
||||
while (index < instrs.Count)
|
||||
{
|
||||
if (TryInitializeHelperLocal(method, instrs, states, index, out var initEnd))
|
||||
{
|
||||
index = initEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryFoldHelperCall(method, instrs, states, index, out var replacementEnd))
|
||||
{
|
||||
index = Math.Max(0, index - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryInitializeHelperLocal(MethodDef method, IList<Instruction> instrs, Dictionary<Local, HelperLocalState> states, int index, out int endIndex)
|
||||
{
|
||||
endIndex = index;
|
||||
if (index + 2 >= instrs.Count)
|
||||
return false;
|
||||
if (!TryGetLdlocaLocal(instrs[index], out var local))
|
||||
return false;
|
||||
if (!instrs[index + 1].IsLdcI4())
|
||||
return false;
|
||||
if (instrs[index + 2].OpCode != OpCodes.Call)
|
||||
return false;
|
||||
if (instrs[index + 2].Operand is not IMethod ctorRef)
|
||||
return false;
|
||||
var ctor = ctorRef.ResolveMethodDef();
|
||||
if (ctor == null || !ctor.HasBody || !ctor.IsInstanceConstructor)
|
||||
return false;
|
||||
if (!TypeEqualityComparer.Instance.Equals(local.Type.ToTypeDefOrRef(), ctor.DeclaringType))
|
||||
return false;
|
||||
|
||||
var seed = instrs[index + 1].GetLdcI4Value();
|
||||
if (!TryExecuteHelperCtor(ctor, (uint)seed, out var state))
|
||||
return false;
|
||||
|
||||
states[local] = state;
|
||||
endIndex = index + 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryFoldHelperCall(MethodDef method, IList<Instruction> instrs, Dictionary<Local, HelperLocalState> states, int index, out int endIndex)
|
||||
{
|
||||
endIndex = index;
|
||||
if (index + 3 >= instrs.Count)
|
||||
return false;
|
||||
if (!TryGetLdlocaLocal(instrs[index], out var local))
|
||||
return false;
|
||||
if (!states.TryGetValue(local, out var state))
|
||||
return false;
|
||||
if (!instrs[index + 1].IsLdcI4() || !instrs[index + 2].IsLdcI4())
|
||||
return false;
|
||||
if (instrs[index + 3].OpCode != OpCodes.Call)
|
||||
return false;
|
||||
if (instrs[index + 3].Operand is not IMethod helperRef)
|
||||
return false;
|
||||
var helper = helperRef.ResolveMethodDef();
|
||||
if (helper == null || !helper.HasBody || helper.MethodSig.Params.Count != 2 || helper.ReturnType.ElementType != ElementType.U4)
|
||||
return false;
|
||||
if (!helper.MethodSig.HasThis)
|
||||
return false;
|
||||
if (!TypeEqualityComparer.Instance.Equals(local.Type.ToTypeDefOrRef(), helper.DeclaringType))
|
||||
return false;
|
||||
|
||||
var arg1 = (byte)instrs[index + 1].GetLdcI4Value();
|
||||
var arg2 = (uint)instrs[index + 2].GetLdcI4Value();
|
||||
if (!TryExecuteHelperMethod(helper, state, arg1, arg2, out var result))
|
||||
return false;
|
||||
|
||||
instrs[index].OpCode = OpCodes.Ldc_I4;
|
||||
instrs[index].Operand = unchecked((int)result);
|
||||
for (int replacementIndex = index + 1; replacementIndex <= index + 3; replacementIndex++)
|
||||
{
|
||||
instrs[replacementIndex].OpCode = OpCodes.Nop;
|
||||
instrs[replacementIndex].Operand = null;
|
||||
}
|
||||
endIndex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetLdlocaLocal(Instruction instruction, out Local local)
|
||||
{
|
||||
local = null!;
|
||||
if (instruction.Operand is not Local candidate)
|
||||
return false;
|
||||
switch (instruction.OpCode.Code)
|
||||
{
|
||||
case Code.Ldloca:
|
||||
case Code.Ldloca_S:
|
||||
local = candidate;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryExecuteHelperCtor(MethodDef ctor, uint seed, out HelperLocalState state)
|
||||
{
|
||||
state = new HelperLocalState(4);
|
||||
if (!ctor.HasBody)
|
||||
return false;
|
||||
|
||||
var stack = new Stack<uint>();
|
||||
foreach (var instruction in ctor.Body.Instructions)
|
||||
{
|
||||
switch (instruction.OpCode.Code)
|
||||
{
|
||||
case Code.Ldarg_0:
|
||||
stack.Push(uint.MaxValue);
|
||||
break;
|
||||
case Code.Ldarg_1:
|
||||
stack.Push(seed);
|
||||
break;
|
||||
case Code.Ldc_I4:
|
||||
case Code.Ldc_I4_S:
|
||||
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_M1:
|
||||
stack.Push(unchecked((uint)instruction.GetLdcI4Value()));
|
||||
break;
|
||||
case Code.Mul:
|
||||
{
|
||||
var right = stack.Pop();
|
||||
var left = stack.Pop();
|
||||
stack.Push(unchecked(left * right));
|
||||
break;
|
||||
}
|
||||
case Code.Dup:
|
||||
stack.Push(stack.Peek());
|
||||
break;
|
||||
case Code.Starg:
|
||||
case Code.Starg_S:
|
||||
seed = stack.Peek();
|
||||
break;
|
||||
case Code.Stfld:
|
||||
{
|
||||
var value = stack.Pop();
|
||||
stack.Pop();
|
||||
if (instruction.Operand is not FieldDef field)
|
||||
return false;
|
||||
int fieldIndex = ctor.DeclaringType.Fields.IndexOf(field);
|
||||
if (fieldIndex < 0 || fieldIndex >= state.Fields.Length)
|
||||
return false;
|
||||
state.Fields[fieldIndex] = value;
|
||||
break;
|
||||
}
|
||||
case Code.Ret:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryExecuteHelperMethod(MethodDef helper, HelperLocalState state, byte arg1, uint arg2, out uint result)
|
||||
{
|
||||
result = 0;
|
||||
if (!helper.HasBody)
|
||||
return false;
|
||||
|
||||
int lowBits = arg1 & 3;
|
||||
if ((arg1 & 0x80) != 0)
|
||||
{
|
||||
if (lowBits < 0 || lowBits >= state.Fields.Length)
|
||||
return false;
|
||||
state.Fields[lowBits] = arg2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lowBits < 0 || lowBits >= state.Fields.Length)
|
||||
return false;
|
||||
state.Fields[lowBits] = lowBits switch
|
||||
{
|
||||
0 => state.Fields[lowBits] ^ arg2,
|
||||
1 => unchecked(state.Fields[lowBits] + arg2),
|
||||
2 => state.Fields[lowBits] ^ arg2,
|
||||
3 => unchecked(state.Fields[lowBits] - arg2),
|
||||
_ => state.Fields[lowBits]
|
||||
};
|
||||
}
|
||||
|
||||
int returnIndex = (arg1 >> 2) & 3;
|
||||
if (returnIndex < 0 || returnIndex >= state.Fields.Length)
|
||||
return false;
|
||||
result = state.Fields[returnIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed class HelperLocalState
|
||||
{
|
||||
public HelperLocalState(int size)
|
||||
{
|
||||
Fields = new uint[size];
|
||||
}
|
||||
|
||||
public uint[] Fields { get; }
|
||||
}
|
||||
|
||||
protected int GetNextInstanceInMethod(MethodDef getter, MethodDef method, out TypeSig? genericType)
|
||||
{
|
||||
return GetNextInstanceInMethod(getter, method, 0, out genericType);
|
||||
}
|
||||
|
||||
protected int GetNextInstanceInMethod(MethodDef getter, MethodDef method, int startIndex, out TypeSig? genericType)
|
||||
{
|
||||
var instrs = method.Body.Instructions;
|
||||
|
||||
for (int i = 0; i < instrs.Count; i++)
|
||||
for (int i = Math.Max(0, startIndex); i < instrs.Count; i++)
|
||||
{
|
||||
if (instrs[i].OpCode == OpCodes.Call &&
|
||||
instrs[i].Operand is MethodSpec ms &&
|
||||
@@ -192,33 +439,59 @@ namespace UnConfuserEx.Protections.Constants
|
||||
{
|
||||
genericType = ms.GenericInstMethodSig.GenericArguments[0];
|
||||
|
||||
if (instrs[i - 1].IsBr() &&
|
||||
instrs[i - 1].Operand is Instruction target &&
|
||||
target == instrs[i])
|
||||
{
|
||||
method.Body.Instructions.RemoveAt(i - 1);
|
||||
i--;
|
||||
}
|
||||
|
||||
return i - 1;
|
||||
}
|
||||
if (i > 0 && instrs[i - 1].IsBr() &&
|
||||
instrs[i - 1].Operand is Instruction target &&
|
||||
target == instrs[i])
|
||||
{
|
||||
instrs[i - 1].OpCode = OpCodes.Nop;
|
||||
instrs[i - 1].Operand = null;
|
||||
}
|
||||
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
genericType = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected bool TryValidateDataRange(int id, int size, out int normalizedId)
|
||||
{
|
||||
normalizedId = id;
|
||||
if (data == null)
|
||||
return false;
|
||||
if (id < 0)
|
||||
return false;
|
||||
if (size < 0)
|
||||
return false;
|
||||
if (id > data.Length - size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static bool CanReplaceConstant(MethodDef method, int instrOffset)
|
||||
{
|
||||
return instrOffset >= 0 && instrOffset + 1 < method.Body.Instructions.Count;
|
||||
}
|
||||
|
||||
protected void FixStringConstant(MethodDef method, int instrOffset, int id)
|
||||
{
|
||||
if (!CanReplaceConstant(method, instrOffset) || !TryValidateDataRange(id, 4, out _))
|
||||
throw new IndexOutOfRangeException("String constant header is outside the decrypted blob");
|
||||
|
||||
uint count = (uint)(data![id] | (data[id + 1] << 8) | (data[id + 2] << 16) | (data[id + 3] << 24));
|
||||
if (count > data.Length)
|
||||
{
|
||||
count = (count << 4) | (count >> 0x1C);
|
||||
}
|
||||
string result = string.Intern(Encoding.UTF8.GetString(data, id + 4, (int)count));
|
||||
if (count > data.Length || id > data.Length - 4 - count)
|
||||
throw new IndexOutOfRangeException("String constant payload is outside the decrypted blob");
|
||||
|
||||
method.Body.Instructions[instrOffset].OpCode = OpCodes.Ldstr;
|
||||
method.Body.Instructions[instrOffset].Operand = result;
|
||||
method.Body.Instructions.RemoveAt(instrOffset + 1);
|
||||
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[instrOffset + 1].OpCode = OpCodes.Nop;
|
||||
method.Body.Instructions[instrOffset + 1].Operand = null;
|
||||
}
|
||||
|
||||
protected void FixNumberConstant(MethodDef method, int instrOffset, int id, TypeSig type)
|
||||
@@ -245,15 +518,23 @@ namespace UnConfuserEx.Protections.Constants
|
||||
|
||||
protected void FixNumberConstant<T>(MethodDef method, int instrOffset, int id)
|
||||
{
|
||||
T[] array = new T[1];
|
||||
Buffer.BlockCopy(data!, id, array, 0, Marshal.SizeOf(default(T)));
|
||||
int size = Marshal.SizeOf(default(T));
|
||||
if (!CanReplaceConstant(method, instrOffset) || !TryValidateDataRange(id, size, out _))
|
||||
throw new IndexOutOfRangeException("Number constant is outside the decrypted blob");
|
||||
|
||||
method.Body.Instructions[instrOffset].Operand = array[0];
|
||||
method.Body.Instructions.RemoveAt(instrOffset + 1);
|
||||
T[] array = new T[1];
|
||||
Buffer.BlockCopy(data!, id, array, 0, size);
|
||||
|
||||
method.Body.Instructions[instrOffset].Operand = array[0];
|
||||
method.Body.Instructions[instrOffset + 1].OpCode = OpCodes.Nop;
|
||||
method.Body.Instructions[instrOffset + 1].Operand = null;
|
||||
}
|
||||
|
||||
protected void FixObjectConstant(MethodDef method, int instrOffset, int id, TypeSig type)
|
||||
{
|
||||
if (!CanReplaceConstant(method, instrOffset) || !TryValidateDataRange(id, 8, out _))
|
||||
throw new IndexOutOfRangeException("Object constant header is outside the decrypted blob");
|
||||
|
||||
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);
|
||||
@@ -263,9 +544,13 @@ namespace UnConfuserEx.Protections.Constants
|
||||
|
||||
protected 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);
|
||||
}
|
||||
if (!CanReplaceConstant(method, instrOffset))
|
||||
throw new IndexOutOfRangeException("Default constant replacement is outside the method body");
|
||||
|
||||
method.Body.Instructions[instrOffset].OpCode = OpCodes.Initobj;
|
||||
method.Body.Instructions[instrOffset].Operand = type.ToTypeDefOrRef();
|
||||
method.Body.Instructions[instrOffset + 1].OpCode = OpCodes.Nop;
|
||||
method.Body.Instructions[instrOffset + 1].Operand = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace UnConfuserEx.Protections.Constants
|
||||
new ConstantsCFG(method).RemoveFromMethod();
|
||||
}
|
||||
|
||||
SimplifyStatefulHelperCalls(method);
|
||||
|
||||
TypeSig? genericType;
|
||||
int instanceOffset = GetNextInstanceInMethod(getter, method, out genericType);
|
||||
|
||||
@@ -48,8 +50,8 @@ namespace UnConfuserEx.Protections.Constants
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to compute id at offset {instanceOffset} in {method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, out genericType);
|
||||
Logger.Debug($"Skipping constant at offset {instanceOffset} in {method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -61,11 +63,11 @@ namespace UnConfuserEx.Protections.Constants
|
||||
|
||||
try
|
||||
{
|
||||
if (type == stringId)
|
||||
if (IsStringType(genericType!))
|
||||
{
|
||||
FixStringConstant(method, instanceOffset, id);
|
||||
}
|
||||
else if (type == numId)
|
||||
else if (IsSupportedNumberType(genericType!))
|
||||
{
|
||||
FixNumberConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
@@ -73,15 +75,30 @@ namespace UnConfuserEx.Protections.Constants
|
||||
{
|
||||
FixObjectConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
else if (type == stringId)
|
||||
{
|
||||
FixStringConstant(method, instanceOffset, id);
|
||||
}
|
||||
else if (type == numId)
|
||||
{
|
||||
FixNumberConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
else
|
||||
{
|
||||
FixDefaultConstant(method, instanceOffset, genericType!);
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException ex) when (ex.Message == "Object constant not handled")
|
||||
{
|
||||
Logger.Debug($"Skipping unsupported object constant in method ${method.FullName}");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to remove constants obfuscation from method ${method.FullName} ({ex.Message})");
|
||||
return;
|
||||
Logger.Debug($"Skipping constant in method ${method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace UnConfuserEx.Protections.Constants
|
||||
new ConstantsCFG(method).RemoveFromMethod();
|
||||
}
|
||||
|
||||
SimplifyStatefulHelperCalls(method);
|
||||
|
||||
TypeSig? genericType;
|
||||
int instanceOffset = GetNextInstanceInMethod(getter, method, out genericType);
|
||||
|
||||
@@ -45,8 +47,8 @@ namespace UnConfuserEx.Protections.Constants
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to compute id at offset {instanceOffset} in {method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, out genericType);
|
||||
Logger.Debug($"Skipping constant at offset {instanceOffset} in {method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -59,11 +61,11 @@ namespace UnConfuserEx.Protections.Constants
|
||||
|
||||
try
|
||||
{
|
||||
if (type == stringId)
|
||||
if (IsStringType(genericType!))
|
||||
{
|
||||
FixStringConstant(method, instanceOffset, id);
|
||||
}
|
||||
else if (type == numId)
|
||||
else if (IsSupportedNumberType(genericType!))
|
||||
{
|
||||
FixNumberConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
@@ -71,15 +73,30 @@ namespace UnConfuserEx.Protections.Constants
|
||||
{
|
||||
FixObjectConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
else if (type == stringId)
|
||||
{
|
||||
FixStringConstant(method, instanceOffset, id);
|
||||
}
|
||||
else if (type == numId)
|
||||
{
|
||||
FixNumberConstant(method, instanceOffset, id, genericType!);
|
||||
}
|
||||
else
|
||||
{
|
||||
FixDefaultConstant(method, instanceOffset, genericType!);
|
||||
}
|
||||
}
|
||||
catch (NotImplementedException ex) when (ex.Message == "Object constant not handled")
|
||||
{
|
||||
Logger.Debug($"Skipping unsupported object constant in method ${method.FullName}");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to remove constants obfuscation from method ${method.FullName} ({ex.Message})");
|
||||
return;
|
||||
Logger.Debug($"Skipping constant in method ${method.FullName} ({ex.Message})");
|
||||
instanceOffset = GetNextInstanceInMethod(getter, method, instanceOffset + 2, out genericType);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Writer;
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -12,6 +13,8 @@ namespace UnConfuserEx.Protections
|
||||
{
|
||||
internal class UnicodeRemover : IProtection
|
||||
{
|
||||
private static readonly ILog Logger = LogManager.GetLogger("Renamer");
|
||||
|
||||
public string Name => "Renamer";
|
||||
|
||||
private ModuleDefMD? Module = null;
|
||||
@@ -79,24 +82,43 @@ namespace UnConfuserEx.Protections
|
||||
|
||||
private void RenameMemberRef(IMemberRef memberRef)
|
||||
{
|
||||
if (Utils.IsInvalidName(memberRef.Name))
|
||||
if (!Utils.IsInvalidName(memberRef.Name))
|
||||
return;
|
||||
|
||||
TypeDef? declaringType = null;
|
||||
try
|
||||
{
|
||||
var declaringType = memberRef.DeclaringType.ResolveTypeDefThrow();
|
||||
var typeInfo = NewTypeInfo[declaringType];
|
||||
declaringType = memberRef.DeclaringType.ResolveTypeDef();
|
||||
}
|
||||
catch (TypeResolveException ex)
|
||||
{
|
||||
Logger.Warn($"Skipping unresolved member ref {memberRef.FullName} ({ex.Message})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (memberRef.IsField)
|
||||
{
|
||||
memberRef.Name = typeInfo.FieldNames[memberRef.Name];
|
||||
}
|
||||
else if (memberRef.IsMethod)
|
||||
{
|
||||
memberRef.Name = typeInfo.MethodNames[memberRef.Name];
|
||||
}
|
||||
if (declaringType == null || !NewTypeInfo.TryGetValue(declaringType, out var typeInfo))
|
||||
{
|
||||
Logger.Warn($"Skipping member ref with unresolved declaring type {memberRef.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (memberRef.IsField)
|
||||
{
|
||||
if (typeInfo.FieldNames.TryGetValue(memberRef.Name, out var fieldName))
|
||||
memberRef.Name = fieldName;
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
Logger.Warn($"Skipping unknown field ref {memberRef.FullName}");
|
||||
}
|
||||
else if (memberRef.IsMethod)
|
||||
{
|
||||
if (typeInfo.MethodNames.TryGetValue(memberRef.Name, out var methodName))
|
||||
memberRef.Name = methodName;
|
||||
else
|
||||
Logger.Warn($"Skipping unknown method ref {memberRef.FullName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warn($"Skipping unsupported member ref {memberRef.FullName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Resources;
|
||||
using System.Resources.Extensions;
|
||||
using UnConfuserEx.Protections.Resources;
|
||||
|
||||
namespace UnConfuserEx.Protections
|
||||
@@ -337,9 +338,9 @@ namespace UnConfuserEx.Protections
|
||||
{
|
||||
bool changed = false;
|
||||
using var output = new MemoryStream();
|
||||
using var writer = new ResourceWriter(output);
|
||||
using var writer = new PreserializedResourceWriter(output);
|
||||
using var stream = new MemoryStream(bytes, writable: false);
|
||||
using var reader = new ResourceReader(stream);
|
||||
using var reader = new DeserializingResourceReader(stream);
|
||||
var enumerator = reader.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
@@ -443,6 +444,8 @@ namespace UnConfuserEx.Protections
|
||||
if (!TryDescribePortableExecutable(data, out description))
|
||||
description = module.IsILOnly ? "managed PE" : "mixed-mode PE";
|
||||
|
||||
UnConfuserEx.PrepareModuleForWrite(module);
|
||||
|
||||
if (module.IsILOnly)
|
||||
{
|
||||
var writerOptions = new ModuleWriterOptions(module);
|
||||
|
||||
@@ -3,5 +3,6 @@ namespace UnConfuserEx
|
||||
internal static class RuntimeOptions
|
||||
{
|
||||
public static bool RebuildEmbeddedPe { get; set; }
|
||||
public static bool EnableSerializedResourceDeserialization { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using UnConfuserEx.Protections.AntiDebug;
|
||||
using UnConfuserEx.Protections.AntiDump;
|
||||
using UnConfuserEx.Protections.AntiTamper;
|
||||
using UnConfuserEx.Protections.Compressor;
|
||||
using dnlib.DotNet.Emit;
|
||||
|
||||
namespace UnConfuserEx
|
||||
{
|
||||
@@ -19,6 +20,27 @@ namespace UnConfuserEx
|
||||
{
|
||||
static ILog Logger = LogManager.GetLogger("UnConfuserEx");
|
||||
|
||||
internal static void NormalizeMethodBodiesForWrite(ModuleDef module)
|
||||
{
|
||||
foreach (var type in module.GetTypes())
|
||||
{
|
||||
foreach (var method in type.Methods)
|
||||
{
|
||||
var body = method.Body;
|
||||
if (body == null)
|
||||
continue;
|
||||
|
||||
body.SimplifyBranches();
|
||||
body.OptimizeBranches();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PrepareModuleForWrite(ModuleDef module)
|
||||
{
|
||||
NormalizeMethodBodiesForWrite(module);
|
||||
}
|
||||
|
||||
sealed class FailureInfo
|
||||
{
|
||||
public required string ProtectionName { get; init; }
|
||||
@@ -60,6 +82,7 @@ namespace UnConfuserEx
|
||||
string? pathArg = null;
|
||||
string? outputArg = null;
|
||||
RuntimeOptions.RebuildEmbeddedPe = false;
|
||||
RuntimeOptions.EnableSerializedResourceDeserialization = false;
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
@@ -69,10 +92,16 @@ namespace UnConfuserEx
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.Equals("--enable-serialized-resources", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RuntimeOptions.EnableSerializedResourceDeserialization = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.StartsWith("-", StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Error($"Unknown option: {arg}");
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe]");
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -86,17 +115,20 @@ namespace UnConfuserEx
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe]");
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathArg is null)
|
||||
{
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe]");
|
||||
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (RuntimeOptions.EnableSerializedResourceDeserialization)
|
||||
AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true);
|
||||
|
||||
var path = Path.GetFullPath(pathArg);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
@@ -257,6 +289,8 @@ namespace UnConfuserEx
|
||||
|
||||
try
|
||||
{
|
||||
PrepareModuleForWrite(module);
|
||||
|
||||
if (module.IsILOnly)
|
||||
{
|
||||
ModuleWriterOptions writerOptions = new ModuleWriterOptions(module);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<PackageReference Include="AutomaticGraphLayout.GraphViewerGDI" Version="1.1.12" />
|
||||
<PackageReference Include="dnlib" Version="4.4.0" />
|
||||
<PackageReference Include="log4net" Version="3.0.3" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="9.0.0" />
|
||||
<PackageReference Include="LZMA-SDK" Version="22.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user