diff --git a/Editor/Emit/BasicBlockCollection.cs b/Editor/Emit/BasicBlockCollection.cs index 2dda874..af10c0b 100644 --- a/Editor/Emit/BasicBlockCollection.cs +++ b/Editor/Emit/BasicBlockCollection.cs @@ -104,6 +104,7 @@ namespace Obfuz.Emit { splitPoints.Add(nextInst); } + splitPoints.Add((Instruction)curInst.Operand); break; } case FlowControl.Cond_Branch: diff --git a/Editor/Emit/DefaultMetadataImporter.cs b/Editor/Emit/DefaultMetadataImporter.cs index c36b2d1..d8a27fa 100644 --- a/Editor/Emit/DefaultMetadataImporter.cs +++ b/Editor/Emit/DefaultMetadataImporter.cs @@ -147,6 +147,95 @@ namespace Obfuz.Emit _obfuscationTypeMapperRegisterType = mod.Import(typeof(ObfuscationTypeMapper).GetMethod("RegisterType", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null)); Assert.IsNotNull(_obfuscationTypeMapperRegisterType, "ObfuscationTypeMapper.RegisterType not found"); + var exprUtilityType = typeof(ExprUtility); + _addInt = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_addInt, "ExprUtility.Add(int, int) not found"); + _addLong = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_addLong, "ExprUtility.Add(long, long) not found"); + _addFloat = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_addFloat, "ExprUtility.Add(float, float) not found"); + _addDouble = mod.Import(exprUtilityType.GetMethod("Add", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_addDouble, "ExprUtility.Add(double, double) not found"); + _subtractInt = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_subtractInt, "ExprUtility.Subtract(int, int) not found"); + _subtractLong = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_subtractLong, "ExprUtility.Subtract(long, long) not found"); + _subtractFloat = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_subtractFloat, "ExprUtility.Subtract(float, float) not found"); + _subtractDouble = mod.Import(exprUtilityType.GetMethod("Subtract", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_subtractDouble, "ExprUtility.Subtract(double, double) not found"); + _multiplyInt = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_multiplyInt, "ExprUtility.Multiply(int, int) not found"); + _multiplyLong = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_multiplyLong, "ExprUtility.Multiply(long, long) not found"); + _multiplyFloat = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_multiplyFloat, "ExprUtility.Multiply(float, float) not found"); + _multiplyDouble = mod.Import(exprUtilityType.GetMethod("Multiply", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_multiplyDouble, "ExprUtility.Multiply(double, double) not found"); + _divideInt = mod.Import(exprUtilityType.GetMethod("Divide", new[] {typeof(int), typeof(int) })); + Assert.IsNotNull(_divideInt, "ExprUtility.Divide(int, int) not found"); + _divideLong = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_divideLong); + _divideFloat = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_divideFloat, "ExprUtility.Divide(float, float) not found"); + _divideDouble = mod.Import(exprUtilityType.GetMethod("Divide", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_divideDouble, "ExprUtility.Divide(double, double) not found"); + _divideUnInt = mod.Import(exprUtilityType.GetMethod("DivideUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_divideUnInt, "ExprUtility.DivideUn(int, int) not found"); + _divideUnLong = mod.Import(exprUtilityType.GetMethod("DivideUn", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_divideUnLong, "ExprUtility.DivideUn(long, long) not found"); + _remInt = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_remInt, "ExprUtility.Rem(int, int) not found"); + _remLong = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_remLong, "ExprUtility.Rem(long, long) not found"); + _remFloat = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(float), typeof(float) })); + Assert.IsNotNull(_remFloat, "ExprUtility.Rem(float, float) not found"); + _remDouble = mod.Import(exprUtilityType.GetMethod("Rem", new[] { typeof(double), typeof(double) })); + Assert.IsNotNull(_remDouble, "ExprUtility.Rem(double, double) not found"); + _remUnInt = mod.Import(exprUtilityType.GetMethod("RemUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_remUnInt, "ExprUtility.RemUn(int, int) not found"); + _remUnLong = mod.Import(exprUtilityType.GetMethod("RemUn", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_remUnLong, "ExprUtility.RemUn(long, long) not found"); + _negInt = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(int) })); + Assert.IsNotNull(_negInt, "ExprUtility.Negate(int) not found"); + _negLong = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(long) })); + Assert.IsNotNull(_negLong, "ExprUtility.Negate(long) not found"); + _negFloat = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(float) })); + Assert.IsNotNull(_negFloat, "ExprUtility.Negate(float) not found"); + _negDouble = mod.Import(exprUtilityType.GetMethod("Negate", new[] { typeof(double) })); + Assert.IsNotNull(_negDouble, "ExprUtility.Negate(double) not found"); + + _andInt = mod.Import(exprUtilityType.GetMethod("And", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_andInt, "ExprUtility.And(int, int) not found"); + _andLong = mod.Import(exprUtilityType.GetMethod("And", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_andLong, "ExprUtility.And(long, long) not found"); + _orInt = mod.Import(exprUtilityType.GetMethod("Or", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_orInt, "ExprUtility.Or(int, int) not found"); + _orLong = mod.Import(exprUtilityType.GetMethod("Or", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_orLong, "ExprUtility.Or(long, long) not found"); + _xorInt = mod.Import(exprUtilityType.GetMethod("Xor", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_xorInt, "ExprUtility.Xor(int, int) not found"); + _xorLong = mod.Import(exprUtilityType.GetMethod("Xor", new[] { typeof(long), typeof(long) })); + Assert.IsNotNull(_xorLong, "ExprUtility.Xor(long, long) not found"); + _notInt = mod.Import(exprUtilityType.GetMethod("Not", new[] { typeof(int) })); + Assert.IsNotNull(_notInt, "ExprUtility.Not(int) not found"); + _notLong = mod.Import(exprUtilityType.GetMethod("Not", new[] { typeof(long) })); + Assert.IsNotNull(_notLong, "ExprUtility.Not(long) not found"); + + _shlInt = mod.Import(exprUtilityType.GetMethod("ShiftLeft", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shlInt, "ExprUtility.ShiftLeft(int, int) not found"); + _shlLong = mod.Import(exprUtilityType.GetMethod("ShiftLeft", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shlLong, "ExprUtility.ShiftLeft(long, int) not found"); + _shrInt = mod.Import(exprUtilityType.GetMethod("ShiftRight", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shrInt, "ExprUtility.ShiftRight(int, int) not found"); + _shrLong = mod.Import(exprUtilityType.GetMethod("ShiftRight", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shrLong, "ExprUtility.ShiftRight(long, int) not found"); + _shrUnInt = mod.Import(exprUtilityType.GetMethod("ShiftRightUn", new[] { typeof(int), typeof(int) })); + Assert.IsNotNull(_shrUnInt, "ExprUtility.ShiftRightUn(int, int) not found"); + _shrUnLong = mod.Import(exprUtilityType.GetMethod("ShiftRightUn", new[] { typeof(long), typeof(int) })); + Assert.IsNotNull(_shrUnLong, "ExprUtility.ShiftRightUn(long, int) not found"); + + _staticDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); _dynamicDefaultEncryptionServiceMetadataImporter = new EncryptionServiceMetadataImporter(mod, typeof(EncryptionService)); if (_encryptionScopeProvider.IsDynamicSecretAssembly(mod)) @@ -174,6 +263,51 @@ namespace Obfuz.Emit private IMethod _obfuscationTypeMapperRegisterType; + private IMethod _addInt; + private IMethod _addLong; + private IMethod _addFloat; + private IMethod _addDouble; + private IMethod _subtractInt; + private IMethod _subtractLong; + private IMethod _subtractFloat; + private IMethod _subtractDouble; + private IMethod _multiplyInt; + private IMethod _multiplyLong; + private IMethod _multiplyFloat; + private IMethod _multiplyDouble; + private IMethod _divideInt; + private IMethod _divideLong; + private IMethod _divideFloat; + private IMethod _divideDouble; + private IMethod _divideUnInt; + private IMethod _divideUnLong; + private IMethod _remInt; + private IMethod _remLong; + private IMethod _remFloat; + private IMethod _remDouble; + private IMethod _remUnInt; + private IMethod _remUnLong; + private IMethod _negInt; + private IMethod _negLong; + private IMethod _negFloat; + private IMethod _negDouble; + + private IMethod _andInt; + private IMethod _andLong; + private IMethod _orInt; + private IMethod _orLong; + private IMethod _xorInt; + private IMethod _xorLong; + private IMethod _notInt; + private IMethod _notLong; + + private IMethod _shlInt; + private IMethod _shlLong; + private IMethod _shrInt; + private IMethod _shrLong; + private IMethod _shrUnInt; + private IMethod _shrUnLong; + public IMethod CastIntAsFloat => _castIntAsFloat; public IMethod CastLongAsDouble => _castLongAsDouble; public IMethod CastFloatAsInt => _castFloatAsInt; @@ -209,5 +343,50 @@ namespace Obfuz.Emit public IMethod DecryptFromRvaString => _defaultEncryptionServiceMetadataImporter.DecryptFromRvaString; public IMethod DecryptInitializeArray => _defaultEncryptionServiceMetadataImporter.DecryptInitializeArray; + + public IMethod AddInt => _addInt; + public IMethod AddLong => _addLong; + public IMethod AddFloat => _addFloat; + public IMethod AddDouble => _addDouble; + public IMethod SubtractInt => _subtractInt; + public IMethod SubtractLong => _subtractLong; + public IMethod SubtractFloat => _subtractFloat; + public IMethod SubtractDouble => _subtractDouble; + public IMethod MultiplyInt => _multiplyInt; + public IMethod MultiplyLong => _multiplyLong; + public IMethod MultiplyFloat => _multiplyFloat; + public IMethod MultiplyDouble => _multiplyDouble; + public IMethod DivideInt => _divideInt; + public IMethod DivideLong => _divideLong; + public IMethod DivideFloat => _divideFloat; + public IMethod DivideDouble => _divideDouble; + public IMethod DivideUnInt => _divideUnInt; + public IMethod DivideUnLong => _divideUnLong; + public IMethod RemInt => _remInt; + public IMethod RemLong => _remLong; + public IMethod RemFloat => _remFloat; + public IMethod RemDouble => _remDouble; + public IMethod RemUnInt => _remUnInt; + public IMethod RemUnLong => _remUnLong; + public IMethod NegInt => _negInt; + public IMethod NegLong => _negLong; + public IMethod NegFloat => _negFloat; + public IMethod NegDouble => _negDouble; + public IMethod AndInt => _andInt; + public IMethod AndLong => _andLong; + public IMethod OrInt => _orInt; + public IMethod OrLong => _orLong; + public IMethod XorInt => _xorInt; + public IMethod XorLong => _xorLong; + public IMethod NotInt => _notInt; + public IMethod NotLong => _notLong; + public IMethod ShlInt => _shlInt; + public IMethod ShlLong => _shlLong; + public IMethod ShrInt => _shrInt; + public IMethod ShrLong => _shrLong; + public IMethod ShrUnInt => _shrUnInt; + public IMethod ShrUnLong => _shrUnLong; + + } } diff --git a/Editor/Emit/EvalStackCalculator.cs b/Editor/Emit/EvalStackCalculator.cs new file mode 100644 index 0000000..aafe558 --- /dev/null +++ b/Editor/Emit/EvalStackCalculator.cs @@ -0,0 +1,888 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using dnlib.DotNet.Writer; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Properties; +using UnityEngine.Assertions; + +namespace Obfuz.Emit +{ + enum EvalDataType + { + None, + Int32, + Int64, + Float, + Double, + I, + Ref, + Unknown, + } + + class InstructionParameterInfo + { + public readonly EvalDataType op1; + public readonly EvalDataType op2; + public readonly EvalDataType retType; + public InstructionParameterInfo(EvalDataType op1, EvalDataType op2, EvalDataType retType) + { + this.op1 = op1; + this.op2 = op2; + this.retType = retType; + } + } + + class EvalStackCalculator + { + private readonly MethodDef _method; + private readonly BasicBlockCollection _basicBlocks; + private readonly Dictionary _instructionParameterInfos = new Dictionary(); + + public EvalStackCalculator(MethodDef method) + { + _method = method; + _basicBlocks = new BasicBlockCollection(method, false); + + SimulateRunAllBlocks(); + } + + public bool TryGetParameterInfo(Instruction inst, out InstructionParameterInfo info) + { + return _instructionParameterInfos.TryGetValue(inst, out info); + } + + class EvalStackState + { + public bool visited; + + public readonly List inputStackDatas = new List(); + public readonly List runStackDatas = new List(); + } + + private void PushStack(List datas, TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: break; + case ElementType.Boolean: + case ElementType.Char: + case ElementType.I1: + case ElementType.U1: + case ElementType.I2: + case ElementType.U2: + case ElementType.I4: + case ElementType.U4: + datas.Add(EvalDataType.Int32); + break; + case ElementType.I8: + case ElementType.U8: + datas.Add(EvalDataType.Int64); + break; + case ElementType.R4: + datas.Add(EvalDataType.Float); + break; + case ElementType.R8: + datas.Add(EvalDataType.Double); + break; + case ElementType.I: + case ElementType.U: + case ElementType.Ptr: + case ElementType.FnPtr: + case ElementType.ByRef: + datas.Add(EvalDataType.I); + break; + case ElementType.String: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.Object: + datas.Add(EvalDataType.Ref); + break; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + if (typeDef.IsEnum) + { + PushStack(datas, typeDef.GetEnumUnderlyingType()); + } + else + { + PushStack(datas, EvalDataType.Unknown); + } + break; + } + case ElementType.GenericInst: + { + GenericInstSig genericInstSig = (GenericInstSig)type; + PushStack(datas, genericInstSig.GenericType); + break; + } + case ElementType.Var: + case ElementType.MVar: + case ElementType.ValueArray: + case ElementType.TypedByRef: + case ElementType.R: + case ElementType.CModOpt: + case ElementType.CModReqd: + case ElementType.Internal: + case ElementType.Module: + case ElementType.Sentinel: + PushStack(datas, EvalDataType.Unknown); + break; + + default: throw new Exception($"Unsupported type: {type} in method: {_method.FullName}."); + } + } + + private void PushStack(List datas, ITypeDefOrRef type) + { + PushStack(datas, type.ToTypeSig()); + } + + private void PushStack(List datas, EvalDataType type) + { + datas.Add(type); + } + + private EvalDataType CalcBasicBinOpRetType(List datas, EvalDataType op1, EvalDataType op2) + { + switch (op1) + { + case EvalDataType.Int32: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.Int32; + case EvalDataType.Int64: return EvalDataType.Int64; + case EvalDataType.I: return EvalDataType.I; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Int64: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.Int64; + case EvalDataType.Int64: + case EvalDataType.I: + return EvalDataType.Int64; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.I: + { + switch (op2) + { + case EvalDataType.Int32: return EvalDataType.I; + case EvalDataType.Int64: return EvalDataType.Int64; + case EvalDataType.I: return EvalDataType.I; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Float: + { + switch (op2) + { + case EvalDataType.Float: return EvalDataType.Float; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + case EvalDataType.Double: + { + switch (op2) + { + case EvalDataType.Double: return EvalDataType.Double; + default: throw new Exception($"Unsupported operand type: {op2} for {op1} in binary operation."); + } + } + default: throw new Exception($"Unsupported operand type: {op1} in binary operation."); + } + } + + private void SimulateRunAllBlocks() + { + Dictionary blockEvalStackStates = _basicBlocks.Blocks.ToDictionary(b => b, b => new EvalStackState()); + bool methodHasReturnValue = _method.ReturnType.ElementType != ElementType.Void; + + CilBody body = _method.Body; + if (body.HasExceptionHandlers) + { + foreach (ExceptionHandler handler in body.ExceptionHandlers) + { + if (handler.IsCatch) + { + BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.HandlerStart); + blockEvalStackStates[bb].runStackDatas.Add(EvalDataType.Ref); // Exception object is pushed onto the stack. + } + else if (handler.IsFilter) + { + BasicBlock bb = _basicBlocks.GetBasicBlockByInstruction(handler.FilterStart); + blockEvalStackStates[bb].runStackDatas.Add(EvalDataType.Ref); // Exception object is pushed onto the stack. + } + } + } + + var newPushedDatas = new List(); + IList methodTypeGenericArgument = _method.DeclaringType.GenericParameters.Count > 0 + ? (IList)_method.DeclaringType.GenericParameters.Select(p => (TypeSig)new GenericVar(p.Number)).ToList() + : null; + IList methodMethodGenericArgument = _method.GenericParameters.Count > 0 + ? (IList)_method.GenericParameters.Select(p => (TypeSig)new GenericMVar(p.Number)).ToList() + : null; + var gac = new GenericArgumentContext(methodTypeGenericArgument, methodMethodGenericArgument); + + var blockWalkStack = new Stack(_basicBlocks.Blocks.Reverse()); + while (blockWalkStack.TryPop(out BasicBlock block)) + { + EvalStackState state = blockEvalStackStates[block]; + if (state.visited) + continue; + state.visited = true; + state.runStackDatas.AddRange(state.inputStackDatas); + List stackDatas = state.runStackDatas; + foreach (var inst in block.instructions) + { + int stackSize = stackDatas.Count; + newPushedDatas.Clear(); + switch (inst.OpCode.Code) + { + case Code.Nop: break; + case Code.Break: break; + case Code.Ldarg_0: + case Code.Ldarg_1: + case Code.Ldarg_2: + case Code.Ldarg_3: + case Code.Ldarg: + case Code.Ldarg_S: + { + PushStack(newPushedDatas, inst.GetParameter(_method.Parameters).Type); + break; + } + case Code.Ldarga: + case Code.Ldarga_S: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc: + case Code.Ldloc_S: + { + PushStack(newPushedDatas, inst.GetLocal(body.Variables).Type); + break; + } + case Code.Ldloca: + case Code.Ldloca_S: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Stloc_0: + case Code.Stloc_1: + case Code.Stloc_2: + case Code.Stloc_3: + case Code.Stloc: + case Code.Stloc_S: + { + Assert.IsTrue(stackSize > 0); + break; + } + case Code.Starg: + case Code.Starg_S: + { + Assert.IsTrue(stackSize > 0); + break; + } + case Code.Ldnull: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldc_I4_M1: + 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: + case Code.Ldc_I4_S: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldc_I8: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldc_R4: + { + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldc_R8: + { + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Dup: + { + Assert.IsTrue(stackSize > 0); + EvalDataType type = stackDatas[stackSize - 1]; + PushStack(newPushedDatas, type); + PushStack(newPushedDatas, type); + break; + } + case Code.Pop: + { + break; + } + case Code.Jmp: + { + break; + } + case Code.Call: + case Code.Callvirt: + { + IMethod calledMethod = (IMethod)inst.Operand; + MethodSig methodSig = MetaUtil.GetInflatedMethodSig(calledMethod, gac); + PushStack(newPushedDatas, methodSig.RetType); + break; + } + case Code.Calli: + { + MethodSig methodSig = (MethodSig)inst.Operand; + PushStack(newPushedDatas, methodSig.RetType); + break; + } + case Code.Ret: + { + break; + } + case Code.Br: + case Code.Br_S: + case Code.Brfalse: + case Code.Brfalse_S: + case Code.Brtrue: + case Code.Brtrue_S: + case Code.Beq: + case Code.Beq_S: + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: + case Code.Bne_Un: + case Code.Bne_Un_S: + { + // Branch instructions do not change the stack. + break; + } + case Code.Ceq: + case Code.Cgt: + case Code.Cgt_Un: + case Code.Clt: + case Code.Clt_Un: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1]; + EvalDataType op1 = stackDatas[stackSize - 2]; + EvalDataType ret = EvalDataType.Int32; + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Switch: + { + // Switch instruction does not change the stack. + break; + } + case Code.Ldind_I1: + case Code.Ldind_U1: + case Code.Ldind_I2: + case Code.Ldind_U2: + case Code.Ldind_I4: + case Code.Ldind_U4: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldind_I8: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldind_I: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldind_Ref: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Ref); + break; + } + case Code.Ldind_R4: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldind_R8: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Stind_I1: + case Code.Stind_I2: + case Code.Stind_I4: + case Code.Stind_I8: + case Code.Stind_I: + case Code.Stind_R4: + case Code.Stind_R8: + case Code.Stind_Ref: + { + Assert.IsTrue(stackSize >= 2); + break; + } + + case Code.Add: + case Code.Add_Ovf: + case Code.Add_Ovf_Un: + case Code.Sub: + case Code.Sub_Ovf: + case Code.Sub_Ovf_Un: + case Code.Mul: + case Code.Mul_Ovf: + case Code.Mul_Ovf_Un: + case Code.Div: + case Code.Div_Un: + case Code.Rem: + case Code.Rem_Un: + + case Code.And: + case Code.Or: + case Code.Xor: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1]; + EvalDataType op1 = stackDatas[stackSize - 2]; + EvalDataType ret = CalcBasicBinOpRetType(stackDatas, op1, op2); + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Shl: + case Code.Shr: + case Code.Shr_Un: + { + Assert.IsTrue(stackSize >= 2); + EvalDataType op2 = stackDatas[stackSize - 1]; + EvalDataType op1 = stackDatas[stackSize - 2]; + if (op1 != EvalDataType.Int32 && op1 != EvalDataType.Int64 && op1 != EvalDataType.I) + throw new Exception($"Unsupported operand type: {op1} in shift operation."); + if (op2 != EvalDataType.Int32 && op2 != EvalDataType.Int64) + throw new Exception($"Unsupported operand type: {op2} for {op1} in shift operation."); + EvalDataType ret = op1; + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op1, op2, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Neg: + { + Assert.IsTrue(stackSize > 0); + EvalDataType op = stackDatas[stackSize - 1]; + EvalDataType ret = op; + switch (op) + { + case EvalDataType.Int32: + case EvalDataType.Int64: + case EvalDataType.I: + case EvalDataType.Float: + case EvalDataType.Double: + break; + default: + throw new Exception($"Unsupported operand type: {op} in unary operation."); + } + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Not: + { + Assert.IsTrue(stackSize > 0); + EvalDataType op = stackDatas[stackSize - 1]; + EvalDataType ret = op; + if (op != EvalDataType.Int32 && op != EvalDataType.Int64 && op != EvalDataType.I) + throw new Exception($"Unsupported operand type: {op} in unary operation."); + _instructionParameterInfos.Add(inst, new InstructionParameterInfo(op, EvalDataType.None, ret)); + PushStack(newPushedDatas, ret); + break; + } + case Code.Conv_I1: + case Code.Conv_U1: + case Code.Conv_I2: + case Code.Conv_U2: + case Code.Conv_I4: + case Code.Conv_U4: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Conv_I8: + case Code.Conv_U8: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Conv_I: + case Code.Conv_U: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Conv_R4: + { + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Conv_R8: + { + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Conv_Ovf_I1: + case Code.Conv_Ovf_I1_Un: + case Code.Conv_Ovf_U1: + case Code.Conv_Ovf_U1_Un: + case Code.Conv_Ovf_I2: + case Code.Conv_Ovf_I2_Un: + case Code.Conv_Ovf_U2: + case Code.Conv_Ovf_U2_Un: + case Code.Conv_Ovf_I4: + case Code.Conv_Ovf_I4_Un: + case Code.Conv_Ovf_U4: + case Code.Conv_Ovf_U4_Un: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Conv_Ovf_I8: + case Code.Conv_Ovf_I8_Un: + case Code.Conv_Ovf_U8: + case Code.Conv_Ovf_U8_Un: + { + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Conv_Ovf_I: + case Code.Conv_Ovf_I_Un: + case Code.Conv_Ovf_U: + case Code.Conv_Ovf_U_Un: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Conv_R_Un: + { + //PushStack(newDataTack, EvalDataType.Double); + //break; + throw new Exception($"unsupported opcode:{inst}"); + } + case Code.Cpobj: + case Code.Initobj: + case Code.Stobj: + { + break; + } + case Code.Ldobj: + { + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Ldstr: + { + PushStack(newPushedDatas, EvalDataType.Ref); + break; + } + case Code.Newobj: + { + IMethod ctor = (IMethod)inst.Operand; + PushStack(newPushedDatas, ctor.DeclaringType); + break; + } + case Code.Castclass: + { + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Isinst: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Unbox: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Unbox_Any: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Box: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Ref); + break; + } + case Code.Throw: + { + // Throw instruction does not change the stack. + break; + } + case Code.Rethrow: + { + // Rethrow instruction does not change the stack. + break; + } + case Code.Ldfld: + case Code.Ldsfld: + { + IField field = (IField)inst.Operand; + TypeSig fieldType = MetaUtil.InflateFieldSig(field, gac); + PushStack(newPushedDatas, field.FieldSig.GetFieldType()); + break; + } + case Code.Ldflda: + case Code.Ldsflda: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Stfld: + case Code.Stsfld: + { + break; + } + case Code.Newarr: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.Ref); + break; + } + case Code.Ldlen: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelema: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelem_I1: + case Code.Ldelem_U1: + case Code.Ldelem_I2: + case Code.Ldelem_U2: + case Code.Ldelem_I4: + case Code.Ldelem_U4: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + case Code.Ldelem_I8: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Int64); + break; + } + case Code.Ldelem_I: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Ldelem_R4: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Float); + break; + } + case Code.Ldelem_R8: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Double); + break; + } + case Code.Ldelem_Ref: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, EvalDataType.Ref); + break; + } + case Code.Ldelem: + { + Assert.IsTrue(stackSize >= 2); + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Stelem_I1: + case Code.Stelem_I2: + case Code.Stelem_I4: + case Code.Stelem_I8: + case Code.Stelem_I: + case Code.Stelem_R4: + case Code.Stelem_R8: + case Code.Stelem_Ref: + case Code.Stelem: + { + Assert.IsTrue(stackSize >= 3); + break; + } + case Code.Mkrefany: + { + PushStack(newPushedDatas, EvalDataType.Unknown); + break; + } + case Code.Refanytype: + { + PushStack(newPushedDatas, EvalDataType.Unknown); + break; + } + case Code.Refanyval: + { + Assert.IsTrue(stackSize > 0); + PushStack(newPushedDatas, (ITypeDefOrRef)inst.Operand); + break; + } + case Code.Ldtoken: + { + PushStack(newPushedDatas, EvalDataType.Unknown); + break; + } + case Code.Endfinally: + case Code.Leave: + case Code.Leave_S: + { + break; + } + case Code.Endfilter: + { + break; + } + case Code.Arglist: + { + break; + } + case Code.Ldftn: + case Code.Ldvirtftn: + { + PushStack(newPushedDatas, EvalDataType.Unknown); + break; + } + case Code.Localloc: + { + PushStack(newPushedDatas, EvalDataType.I); + break; + } + case Code.Unaligned: + case Code.Volatile: + case Code.Tailcall: + case Code.No: + case Code.Readonly: + case Code.Constrained: + { + break; + } + case Code.Cpblk: + case Code.Initblk: + { + break; + } + case Code.Sizeof: + { + PushStack(newPushedDatas, EvalDataType.Int32); + break; + } + default: throw new Exception($"not supported opcode: {inst} in method: {_method.FullName}."); + } + + inst.CalculateStackUsage(methodHasReturnValue, out var pushed, out var pops); + if (pushed != newPushedDatas.Count) + { + throw new Exception($"Instruction {inst} in method {_method.FullName} pushed {newPushedDatas.Count} items, but expected {pushed} items."); + } + if (pops == -1) + { + stackDatas.Clear(); + } + else + { + if (stackSize < pops) + { + throw new Exception($"Instruction {inst} in method {_method.FullName} pops {pops} items, but only {stackSize} items are available on the stack."); + } + stackDatas.RemoveRange(stackDatas.Count - pops, pops); + stackDatas.AddRange(newPushedDatas); + Assert.AreEqual(stackSize + pushed - pops, stackDatas.Count); + } + } + foreach (BasicBlock outBb in block.outBlocks) + { + EvalStackState outState = blockEvalStackStates[outBb]; + if (outState.visited) + { + if (stackDatas.Count != outState.inputStackDatas.Count) + { + throw new Exception($"Block {block} in method {_method.FullName} has inconsistent stack data. Expected {outState.inputStackDatas.Count}, but got {stackDatas.Count}."); + } + } + else if (outState.inputStackDatas.Count != stackDatas.Count) + { + if (outState.inputStackDatas.Count > 0) + { + throw new Exception($"Block {outBb} in method {_method.FullName} has inconsistent stack data. Expected {outState.inputStackDatas.Count}, but got {stackDatas.Count}."); + } + outState.inputStackDatas.AddRange(stackDatas); + blockWalkStack.Push(outBb); + } + } + } + } + } +} diff --git a/Editor/Emit/EvalStackCalculator.cs.meta b/Editor/Emit/EvalStackCalculator.cs.meta new file mode 100644 index 0000000..0e3c9de --- /dev/null +++ b/Editor/Emit/EvalStackCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f25425a3077f6db41873dee4223d0abc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Emit/LocalVariableAllocator.cs b/Editor/Emit/LocalVariableAllocator.cs new file mode 100644 index 0000000..60eec70 --- /dev/null +++ b/Editor/Emit/LocalVariableAllocator.cs @@ -0,0 +1,67 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; + +namespace Obfuz.Emit +{ + class ScopeLocalVariables : IDisposable + { + private readonly LocalVariableAllocator _localVariableAllocator; + + private readonly List _allocatedVars = new List(); + + + public ScopeLocalVariables(LocalVariableAllocator localVariableAllocator) + { + _localVariableAllocator = localVariableAllocator; + } + + public Local AllocateLocal(TypeSig type) + { + var local = _localVariableAllocator.AllocateLocal(type); + _allocatedVars.Add(local); + return local; + } + + public void Dispose() + { + foreach (var local in _allocatedVars) + { + _localVariableAllocator.ReturnLocal(local); + } + } + } + + class LocalVariableAllocator + { + private readonly MethodDef _method; + private readonly List _freeLocals = new List(); + + public LocalVariableAllocator(MethodDef method) + { + _method = method; + } + + public Local AllocateLocal(TypeSig type) + { + foreach (var local in _freeLocals) + { + if (TypeEqualityComparer.Instance.Equals(local.Type, type)) + { + _freeLocals.Remove(local); + return local; + } + } + var newLocal = new Local(type); + // _freeLocals.Add(newLocal); + _method.Body.Variables.Add(newLocal); + return newLocal; + } + + public void ReturnLocal(Local local) + { + _freeLocals.Add(local); + } + } +} diff --git a/Editor/Emit/LocalVariableAllocator.cs.meta b/Editor/Emit/LocalVariableAllocator.cs.meta new file mode 100644 index 0000000..6561078 --- /dev/null +++ b/Editor/Emit/LocalVariableAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 955da34fbde179641a94108ec53405ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs index 3ce89ee..5d56cad 100644 --- a/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs +++ b/Editor/ObfusPasses/BasicBlockObfuscationPassBase.cs @@ -6,49 +6,14 @@ using System.Linq; namespace Obfuz.ObfusPasses { - public abstract class BasicBlockObfuscationPassBase : ObfuscationPassBase + public abstract class BasicBlockObfuscationPassBase : ObfuscationMethodPassBase { - protected abstract bool NeedObfuscateMethod(MethodDef method); - protected virtual bool ComputeBlockInLoop => true; - public override void Process() - { - var ctx = ObfuscationPassContext.Current; - ObfuscationMethodWhitelist whiteList = ctx.whiteList; - ConfigurablePassPolicy passPolicy = ctx.passPolicy; - foreach (ModuleDef mod in ctx.modulesToObfuscate) - { - if (whiteList.IsInWhiteList(mod)) - { - continue; - } - // ToArray to avoid modify list exception - foreach (TypeDef type in mod.GetTypes().ToArray()) - { - if (whiteList.IsInWhiteList(type)) - { - continue; - } - // ToArray to avoid modify list exception - foreach (MethodDef method in type.Methods.ToArray()) - { - if (!method.HasBody || ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)) - { - continue; - } - // TODO if isGeneratedBy Obfuscator, continue - ObfuscateData(method); - } - } - } - } - - protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, BasicBlock block, int instructionIndex, IList globalInstructions, List outputInstructions, List totalFinalInstructions); - private void ObfuscateData(MethodDef method) + protected override void ObfuscateData(MethodDef method) { BasicBlockCollection bbc = new BasicBlockCollection(method, ComputeBlockInLoop); diff --git a/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs b/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs index 5cd0c75..8d76844 100644 --- a/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs +++ b/Editor/ObfusPasses/CallObfus/CallProxyAllocator.cs @@ -157,7 +157,7 @@ namespace Obfuz.ObfusPasses.CallObfus private MethodSig CreateDispatchMethodSig(IMethod method) { - MethodSig methodSig = MetaUtil.ToSharedMethodSig(_module.CorLibTypes, MetaUtil.GetInflatedMethodSig(method)); + MethodSig methodSig = MetaUtil.ToSharedMethodSig(_module.CorLibTypes, MetaUtil.GetInflatedMethodSig(method, null)); //MethodSig methodSig = MetaUtil.GetInflatedMethodSig(method).Clone(); //methodSig.Params switch (MetaUtil.GetThisArgType(method)) diff --git a/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs b/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs index fd86d98..8714fce 100644 --- a/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs +++ b/Editor/ObfusPasses/CallObfus/DefaultCallProxyObfuscator.cs @@ -31,7 +31,7 @@ namespace Obfuz.ObfusPasses.CallObfus public override void Obfuscate(MethodDef callerMethod, IMethod calledMethod, bool callVir, bool needCacheCall, List obfuscatedInstructions) { - MethodSig sharedMethodSig = MetaUtil.ToSharedMethodSig(calledMethod.Module.CorLibTypes, MetaUtil.GetInflatedMethodSig(calledMethod)); + MethodSig sharedMethodSig = MetaUtil.ToSharedMethodSig(calledMethod.Module.CorLibTypes, MetaUtil.GetInflatedMethodSig(calledMethod, null)); ProxyCallMethodData proxyCallMethodData = _proxyCallAllocator.Allocate(callerMethod.Module, calledMethod, callVir); DefaultMetadataImporter importer = _moduleEntityManager.GetDefaultModuleMetadataImporter(callerMethod.Module, _encryptionScopeProvider); diff --git a/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs b/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs new file mode 100644 index 0000000..917c035 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs @@ -0,0 +1,95 @@ +using dnlib.DotNet; +using Obfuz.Conf; +using Obfuz.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + + public interface IObfuscationPolicy + { + bool NeedObfuscate(MethodDef method); + } + + public abstract class ObfuscationPolicyBase : IObfuscationPolicy + { + public abstract bool NeedObfuscate(MethodDef method); + } + + public class ConfigurableObfuscationPolicy : ObfuscationPolicyBase + { + class ObfuscationRule : IRule + { + public bool? obfuscate; + + public void InheritParent(ObfuscationRule parentRule) + { + if (obfuscate == null) + obfuscate = parentRule.obfuscate; + } + } + + class MethodSpec : MethodRuleBase + { + } + + class TypeSpec : TypeRuleBase + { + } + + class AssemblySpec : AssemblyRuleBase + { + } + + private static readonly ObfuscationRule s_default = new ObfuscationRule() + { + obfuscate = false, + }; + + private readonly XmlAssemblyTypeMethodRuleParser _xmlParser; + + private readonly Dictionary _methodRuleCache = new Dictionary(); + + public ConfigurableObfuscationPolicy(List toObfuscatedAssemblyNames, List xmlConfigFiles) + { + _xmlParser = new XmlAssemblyTypeMethodRuleParser( + toObfuscatedAssemblyNames, ParseObfuscationRule, null); + LoadConfigs(xmlConfigFiles); + } + + private void LoadConfigs(List configFiles) + { + _xmlParser.LoadConfigs(configFiles); + _xmlParser.InheritParentRules(s_default); + } + + private ObfuscationRule ParseObfuscationRule(string configFile, XmlElement ele) + { + var rule = new ObfuscationRule(); + if (ele.HasAttribute("obfuscate")) + { + rule.obfuscate = ConfigUtil.ParseBool(ele.GetAttribute("obfuscate")); + } + return rule; + } + + private ObfuscationRule GetMethodObfuscationRule(MethodDef method) + { + if (!_methodRuleCache.TryGetValue(method, out var rule)) + { + rule = _xmlParser.GetMethodRule(method, s_default); + _methodRuleCache[method] = rule; + } + return rule; + } + + public override bool NeedObfuscate(MethodDef method) + { + ObfuscationRule rule = GetMethodObfuscationRule(method); + return rule.obfuscate == true; + } + } +} diff --git a/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta b/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta new file mode 100644 index 0000000..2e67a4f --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/ConfigurableObfuscationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f820a225c981b8499016958e6c69747 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs b/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs index c0b7b91..4e83e7f 100644 --- a/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs +++ b/Editor/ObfusPasses/ExprObfus/ExprObfusPass.cs @@ -1,16 +1,47 @@ using dnlib.DotNet; using dnlib.DotNet.Emit; +using Obfuz.Emit; +using Obfuz.ObfusPasses.ExprObfus.Obfuscators; +using Obfuz.Settings; +using Obfuz.Utils; using System.Collections.Generic; +using System.Linq; +using UnityEngine; namespace Obfuz.ObfusPasses.ExprObfus { - public class ExprObfusPass : InstructionObfuscationPassBase + class ExprObfusPass : ObfuscationMethodPassBase { + private readonly ExprObfuscationSettingsFacade _settings; + private IObfuscationPolicy _obfuscationPolicy; + private IObfuscator _obfuscator; + + public ExprObfusPass(ExprObfuscationSettingsFacade settings) + { + _settings = settings; + } + public override ObfuscationPassType Type => ObfuscationPassType.ExprObfus; public override void Start() { + ObfuscationPassContext ctx = ObfuscationPassContext.Current; + _obfuscationPolicy = new ConfigurableObfuscationPolicy( + ctx.coreSettings.assembliesToObfuscate, + _settings.ruleFiles); + _obfuscator = CreateObfuscator(ctx.encryptionScopeProvider, ctx.moduleEntityManager, _settings.obfuscationLevel); + } + private IObfuscator CreateObfuscator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager, ObfuscationLevel level) + { + switch (level) + { + case ObfuscationLevel.None: return new NoneObfuscator(); + case ObfuscationLevel.Basic:return new BasicObfuscator(encryptionScopeProvider, moduleEntityManager); + case ObfuscationLevel.Advanced: return new AdvancedObfuscator(encryptionScopeProvider, moduleEntityManager); + case ObfuscationLevel.MostAdvanced: return new MostAdvancedObfuscator(encryptionScopeProvider, moduleEntityManager); + default: throw new System.ArgumentOutOfRangeException(nameof(level), level, "Unknown obfuscation level"); + } } public override void Stop() @@ -20,12 +51,90 @@ namespace Obfuz.ObfusPasses.ExprObfus protected override bool NeedObfuscateMethod(MethodDef method) { + return _settings.obfuscationLevel != ObfuscationLevel.None && _obfuscationPolicy.NeedObfuscate(method); + } + + protected bool TryObfuscateInstruction(MethodDef callingMethod, InstructionParameterInfo pi, LocalVariableAllocator localVariableAllocator, IRandom localRandom, Instruction inst, List outputInstructions) + { + Debug.Log($"Obfuscating instruction: {inst} in method: {callingMethod.FullName}"); + switch (inst.OpCode.Code) + { + case Code.Neg: + { + return localRandom.NextInPercentage(_settings.obfuscationPercentage) && _obfuscator.ObfuscateBasicUnaryOp(callingMethod, inst, pi.op1, pi.retType, localVariableAllocator, outputInstructions); + } + case Code.Add: + case Code.Sub: + case Code.Mul: + case Code.Div: + case Code.Div_Un: + case Code.Rem: + case Code.Rem_Un: + { + return localRandom.NextInPercentage(_settings.obfuscationPercentage) && _obfuscator.ObfuscateBasicBinOp(callingMethod, inst, pi.op1, pi.op2, pi.retType, localVariableAllocator, outputInstructions); + } + case Code.And: + case Code.Or: + case Code.Xor: + { + return localRandom.NextInPercentage(_settings.obfuscationPercentage) && _obfuscator.ObfuscateBinBitwiseOp(callingMethod, inst, pi.op1, pi.op2, pi.retType, localVariableAllocator, outputInstructions); + } + case Code.Not: + { + return localRandom.NextInPercentage(_settings.obfuscationPercentage) && _obfuscator.ObfuscateUnaryBitwiseOp(callingMethod, inst, pi.op1, pi.retType, localVariableAllocator, outputInstructions); + } + case Code.Shl: + case Code.Shr: + case Code.Shr_Un: + { + return localRandom.NextInPercentage(_settings.obfuscationPercentage) && _obfuscator.ObfuscateBitShiftOp(callingMethod, inst, pi.op1, pi.op2, pi.retType, localVariableAllocator, outputInstructions); + } + } return false; } - protected override bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions) + protected override void ObfuscateData(MethodDef method) { - return false; + Debug.Log($"Obfuscating method: {method.FullName} with ExprObfusPass"); + var calc = new EvalStackCalculator(method); + var localVarAllocator = new LocalVariableAllocator(method); + IList instructions = method.Body.Instructions; + var outputInstructions = new List(); + var totalFinalInstructions = new List(); + var encryptionScope = ObfuscationPassContext.Current.encryptionScopeProvider.GetScope(method.Module); + var localRandom = encryptionScope.localRandomCreator(MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(method)); + for (int i = 0; i < instructions.Count; i++) + { + Instruction inst = instructions[i]; + bool add = false; + if (calc.TryGetParameterInfo(inst, out InstructionParameterInfo pi)) + { + outputInstructions.Clear(); + if (TryObfuscateInstruction(method, pi, localVarAllocator, localRandom, inst, outputInstructions)) + { + // current instruction may be the target of control flow instruction, so we can't remove it directly. + // we replace it with nop now, then remove it in CleanUpInstructionPass + inst.OpCode = outputInstructions[0].OpCode; + inst.Operand = outputInstructions[0].Operand; + totalFinalInstructions.Add(inst); + for (int k = 1; k < outputInstructions.Count; k++) + { + totalFinalInstructions.Add(outputInstructions[k]); + } + add = true; + } + } + if (!add) + { + totalFinalInstructions.Add(inst); + } + } + + instructions.Clear(); + foreach (var obInst in totalFinalInstructions) + { + instructions.Add(obInst); + } } } } diff --git a/Editor/ObfusPasses/ExprObfus/IObfuscator.cs b/Editor/ObfusPasses/ExprObfus/IObfuscator.cs new file mode 100644 index 0000000..0a377b2 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/IObfuscator.cs @@ -0,0 +1,29 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ExprObfus +{ + interface IObfuscator + { + bool ObfuscateBasicUnaryOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + + bool ObfuscateBasicBinOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + + bool ObfuscateUnaryBitwiseOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + + bool ObfuscateBinBitwiseOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + + bool ObfuscateBitShiftOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + } + + abstract class ObfuscatorBase : IObfuscator + { + public abstract bool ObfuscateBasicUnaryOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + public abstract bool ObfuscateBasicBinOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + public abstract bool ObfuscateUnaryBitwiseOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + public abstract bool ObfuscateBinBitwiseOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + public abstract bool ObfuscateBitShiftOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts); + } +} diff --git a/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta b/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta new file mode 100644 index 0000000..a72c864 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/IObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a88981a87bcd9e84b883e39c81cfbf44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators.meta b/Editor/ObfusPasses/ExprObfus/Obfuscators.meta new file mode 100644 index 0000000..dde5f43 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4c5dc8736831c9f4b934c69f7894a412 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs b/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs new file mode 100644 index 0000000..968da37 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs @@ -0,0 +1,12 @@ +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + class AdvancedObfuscator : BasicObfuscator + { + public AdvancedObfuscator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager) + : base(encryptionScopeProvider, moduleEntityManager) + { + } + } +} diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta b/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta new file mode 100644 index 0000000..06cf2c5 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/AdvancedObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef717515402ca2f41a52db7ea1300f32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs b/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs new file mode 100644 index 0000000..19ed618 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs @@ -0,0 +1,261 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + + class BasicObfuscator : ObfuscatorBase + { + private readonly EncryptionScopeProvider _encryptionScopeProvider; + private readonly GroupByModuleEntityManager _moduleEntityManager; + + public BasicObfuscator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager) + { + _encryptionScopeProvider = encryptionScopeProvider; + _moduleEntityManager = moduleEntityManager; + } + + private DefaultMetadataImporter GetModuleMetadataImporter(ModuleDef module) + { + return _moduleEntityManager.GetDefaultModuleMetadataImporter(module, _encryptionScopeProvider); + } + + private IMethod GetMethod(DefaultMetadataImporter importer, Code code, EvalDataType op1) + { + switch (code) + { + case Code.Add: + { + switch (op1) + { + case EvalDataType.Int32: return importer.AddInt; + case EvalDataType.Int64: return importer.AddLong; + case EvalDataType.Float: return importer.AddFloat; + case EvalDataType.Double: return importer.AddDouble; + default: return null; + } + } + case Code.Sub: + { + switch (op1) + { + case EvalDataType.Int32: return importer.SubtractInt; + case EvalDataType.Int64: return importer.SubtractLong; + case EvalDataType.Float: return importer.SubtractFloat; + case EvalDataType.Double: return importer.SubtractDouble; + default: return null; + } + } + case Code.Mul: + { + switch (op1) + { + case EvalDataType.Int32: return importer.MultiplyInt; + case EvalDataType.Int64: return importer.MultiplyLong; + case EvalDataType.Float: return importer.MultiplyFloat; + case EvalDataType.Double: return importer.MultiplyDouble; + default: return null; + } + } + case Code.Div: + { + switch (op1) + { + case EvalDataType.Int32: return importer.DivideInt; + case EvalDataType.Int64: return importer.DivideLong; + case EvalDataType.Float: return importer.DivideFloat; + case EvalDataType.Double: return importer.DivideDouble; + default: return null; + } + } + case Code.Div_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.DivideUnInt; + case EvalDataType.Int64: return importer.DivideUnLong; + default: return null; + } + } + case Code.Rem: + { + switch (op1) + { + case EvalDataType.Int32: return importer.RemInt; + case EvalDataType.Int64: return importer.RemLong; + case EvalDataType.Float: return importer.RemFloat; + case EvalDataType.Double: return importer.RemDouble; + default: return null; + } + } + case Code.Rem_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.RemUnInt; + case EvalDataType.Int64: return importer.RemUnLong; + default: return null; + } + } + case Code.Neg: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NegInt; + case EvalDataType.Int64: return importer.NegLong; + case EvalDataType.Float: return importer.NegFloat; + case EvalDataType.Double: return importer.NegDouble; + default: return null; + } + } + case Code.And: + { + switch (op1) + { + case EvalDataType.Int32: return importer.AndInt; + case EvalDataType.Int64: return importer.AndLong; + default: return null; + } + } + case Code.Or: + { + switch (op1) + { + case EvalDataType.Int32: return importer.OrInt; + case EvalDataType.Int64: return importer.OrLong; + default: return null; + } + } + case Code.Xor: + { + switch (op1) + { + case EvalDataType.Int32: return importer.XorInt; + case EvalDataType.Int64: return importer.XorLong; + default: return null; + } + } + case Code.Not: + { + switch (op1) + { + case EvalDataType.Int32: return importer.NotInt; + case EvalDataType.Int64: return importer.NotLong; + default: return null; + } + } + case Code.Shl: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShlInt; + case EvalDataType.Int64: return importer.ShlLong; + default: return null; + } + } + case Code.Shr: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShrInt; + case EvalDataType.Int64: return importer.ShrLong; + default: return null; + } + } + case Code.Shr_Un: + { + switch (op1) + { + case EvalDataType.Int32: return importer.ShrUnInt; + case EvalDataType.Int64: return importer.ShrUnLong; + default: return null; + } + } + default: return null; + } + } + + public override bool ObfuscateBasicUnaryOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + DefaultMetadataImporter importer = GetModuleMetadataImporter(method.Module); + IMethod opMethod = GetMethod(importer, inst.OpCode.Code, op); + if (opMethod == null) + { + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBasicBinOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + if (op1 != op2) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with different operand types: op1={op1}, op2={op2}, ret={ret}. This is a limitation of the BasicObfuscator."); + return false; + } + DefaultMetadataImporter importer = GetModuleMetadataImporter(method.Module); + IMethod opMethod = GetMethod(importer, inst.OpCode.Code, op1); + if (opMethod == null) + { + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateUnaryBitwiseOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + DefaultMetadataImporter importer = GetModuleMetadataImporter(method.Module); + IMethod opMethod = GetMethod(importer, inst.OpCode.Code, op); + if (opMethod == null) + { + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBinBitwiseOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + if (op1 != op2) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with different operand types: op1={op1}, op2={op2}, ret={ret}. This is a limitation of the BasicObfuscator."); + return false; + } + DefaultMetadataImporter importer = GetModuleMetadataImporter(method.Module); + IMethod opMethod = GetMethod(importer, inst.OpCode.Code, op1); + if (opMethod == null) + { + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + + public override bool ObfuscateBitShiftOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + if (op2 != EvalDataType.Int32) + { + Debug.LogWarning($"BasicObfuscator: Cannot obfuscate binary operation {inst.OpCode.Code} with operand type {op2}. This is a limitation of the BasicObfuscator."); + return false; + } + DefaultMetadataImporter importer = GetModuleMetadataImporter(method.Module); + IMethod opMethod = GetMethod(importer, inst.OpCode.Code, op1); + if (opMethod == null) + { + return false; + } + outputInsts.Add(Instruction.Create(OpCodes.Call, opMethod)); + return true; + } + } +} diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta b/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta new file mode 100644 index 0000000..dd49d80 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/BasicObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 578caeae17526b54c9ff1979d897feb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs b/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs new file mode 100644 index 0000000..0b0febf --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs @@ -0,0 +1,12 @@ +using Obfuz.Emit; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + class MostAdvancedObfuscator : AdvancedObfuscator + { + public MostAdvancedObfuscator(EncryptionScopeProvider encryptionScopeProvider, GroupByModuleEntityManager moduleEntityManager) + : base(encryptionScopeProvider, moduleEntityManager) + { + } + } +} diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta b/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta new file mode 100644 index 0000000..fb1660a --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/MostAdvancedObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af5946ac6cb0a8b4fa75321439785133 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs b/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs new file mode 100644 index 0000000..abd0167 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs @@ -0,0 +1,35 @@ +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Obfuz.Emit; +using System.Collections.Generic; + +namespace Obfuz.ObfusPasses.ExprObfus.Obfuscators +{ + class NoneObfuscator : ObfuscatorBase + { + public override bool ObfuscateBasicUnaryOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + return false; + } + + public override bool ObfuscateBasicBinOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + return false; + } + + public override bool ObfuscateUnaryBitwiseOp(MethodDef method, Instruction inst, EvalDataType op, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + return false; + } + + public override bool ObfuscateBinBitwiseOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + return false; + } + + public override bool ObfuscateBitShiftOp(MethodDef method, Instruction inst, EvalDataType op1, EvalDataType op2, EvalDataType ret, LocalVariableAllocator localVariableAllocator, List outputInsts) + { + return false; + } + } +} diff --git a/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs.meta b/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs.meta new file mode 100644 index 0000000..bf7a5d7 --- /dev/null +++ b/Editor/ObfusPasses/ExprObfus/Obfuscators/NoneObfuscator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca85b81393732984a9019bdbe5d9e9a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfusPasses/InstructionObfuscationPassBase.cs b/Editor/ObfusPasses/InstructionObfuscationPassBase.cs index 2722a2a..0121c93 100644 --- a/Editor/ObfusPasses/InstructionObfuscationPassBase.cs +++ b/Editor/ObfusPasses/InstructionObfuscationPassBase.cs @@ -5,50 +5,12 @@ using System.Linq; namespace Obfuz.ObfusPasses { - public abstract class InstructionObfuscationPassBase : ObfuscationPassBase + public abstract class InstructionObfuscationPassBase : ObfuscationMethodPassBase { - protected virtual bool ForceProcessAllAssembliesAndIgnoreAllPolicy => false; - - protected abstract bool NeedObfuscateMethod(MethodDef method); - - public override void Process() - { - var ctx = ObfuscationPassContext.Current; - var modules = ForceProcessAllAssembliesAndIgnoreAllPolicy ? ctx.allObfuscationRelativeModules : ctx.modulesToObfuscate; - ObfuscationMethodWhitelist whiteList = ctx.whiteList; - ConfigurablePassPolicy passPolicy = ctx.passPolicy; - foreach (ModuleDef mod in modules) - { - if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(mod)) - { - continue; - } - // ToArray to avoid modify list exception - foreach (TypeDef type in mod.GetTypes().ToArray()) - { - if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(type)) - { - continue; - } - // ToArray to avoid modify list exception - foreach (MethodDef method in type.Methods.ToArray()) - { - if (!method.HasBody || (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)))) - { - continue; - } - // TODO if isGeneratedBy Obfuscator, continue - ObfuscateData(method); - } - } - } - } - - protected abstract bool TryObfuscateInstruction(MethodDef callingMethod, Instruction inst, IList instructions, int instructionIndex, List outputInstructions, List totalFinalInstructions); - private void ObfuscateData(MethodDef method) + protected override void ObfuscateData(MethodDef method) { IList instructions = method.Body.Instructions; var outputInstructions = new List(); diff --git a/Editor/ObfusPasses/ObfuscationMethodPassBase.cs b/Editor/ObfusPasses/ObfuscationMethodPassBase.cs new file mode 100644 index 0000000..7a2e094 --- /dev/null +++ b/Editor/ObfusPasses/ObfuscationMethodPassBase.cs @@ -0,0 +1,47 @@ +using dnlib.DotNet; +using System.Linq; + +namespace Obfuz.ObfusPasses +{ + public abstract class ObfuscationMethodPassBase : ObfuscationPassBase + { + protected virtual bool ForceProcessAllAssembliesAndIgnoreAllPolicy => false; + + protected abstract bool NeedObfuscateMethod(MethodDef method); + + protected abstract void ObfuscateData(MethodDef method); + + public override void Process() + { + var ctx = ObfuscationPassContext.Current; + var modules = ForceProcessAllAssembliesAndIgnoreAllPolicy ? ctx.allObfuscationRelativeModules : ctx.modulesToObfuscate; + ObfuscationMethodWhitelist whiteList = ctx.whiteList; + ConfigurablePassPolicy passPolicy = ctx.passPolicy; + foreach (ModuleDef mod in modules) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(mod)) + { + continue; + } + // ToArray to avoid modify list exception + foreach (TypeDef type in mod.GetTypes().ToArray()) + { + if (!ForceProcessAllAssembliesAndIgnoreAllPolicy && whiteList.IsInWhiteList(type)) + { + continue; + } + // ToArray to avoid modify list exception + foreach (MethodDef method in type.Methods.ToArray()) + { + if (!method.HasBody || (!ForceProcessAllAssembliesAndIgnoreAllPolicy && (ctx.whiteList.IsInWhiteList(method) || !Support(passPolicy.GetMethodObfuscationPasses(method)) || !NeedObfuscateMethod(method)))) + { + continue; + } + // TODO if isGeneratedBy Obfuscator, continue + ObfuscateData(method); + } + } + } + } + } +} diff --git a/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta b/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta new file mode 100644 index 0000000..0be4d9f --- /dev/null +++ b/Editor/ObfusPasses/ObfuscationMethodPassBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84b0592af70b0cc41b546cf8ac39f889 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ObfuscatorBuilder.cs b/Editor/ObfuscatorBuilder.cs index fc19cb9..fc983e2 100644 --- a/Editor/ObfuscatorBuilder.cs +++ b/Editor/ObfuscatorBuilder.cs @@ -173,14 +173,14 @@ namespace Obfuz { builder.AddPass(new FieldEncryptPass(settings.fieldEncryptSettings.ToFacade())); } + if (obfuscationPasses.HasFlag(ObfuscationPassType.ExprObfus)) + { + builder.AddPass(new ExprObfusPass(settings.exprObfusSettings.ToFacade())); + } if (obfuscationPasses.HasFlag(ObfuscationPassType.CallObfus)) { builder.AddPass(new CallObfusPass(settings.callObfusSettings.ToFacade())); } - if (obfuscationPasses.HasFlag(ObfuscationPassType.ExprObfus)) - { - builder.AddPass(new ExprObfusPass()); - } if (obfuscationPasses.HasFlag(ObfuscationPassType.SymbolObfus)) { builder.AddPass(new SymbolObfusPass(settings.symbolObfusSettings.ToFacade())); diff --git a/Editor/Settings/ExprObfuscationSettings.cs b/Editor/Settings/ExprObfuscationSettings.cs new file mode 100644 index 0000000..ec827d5 --- /dev/null +++ b/Editor/Settings/ExprObfuscationSettings.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Obfuz.Settings +{ + public enum ObfuscationLevel + { + None = 0, + Basic = 1, + Advanced = 2, + MostAdvanced = 3 + } + + public class ExprObfuscationSettingsFacade + { + public ObfuscationLevel obfuscationLevel; + public float obfuscationPercentage; + public List ruleFiles; + } + + [Serializable] + public class ExprObfuscationSettings + { + [Tooltip("Obfuscation level")] + public ObfuscationLevel obfuscationLevel = ObfuscationLevel.Basic; + + [Tooltip("percentage of obfuscation, 0.0 - 1.0, 0.5 means 50% of expressions will be obfuscated")] + [Range(0.1f, 1.0f)] + public float obfuscationPercentage = 0.5f; + + [Tooltip("rule config xml files")] + public string[] ruleFiles; + + public ExprObfuscationSettingsFacade ToFacade() + { + return new ExprObfuscationSettingsFacade + { + obfuscationLevel = obfuscationLevel, + obfuscationPercentage = obfuscationPercentage, + ruleFiles = new List(ruleFiles), + }; + } + } +} diff --git a/Editor/Settings/ExprObfuscationSettings.cs.meta b/Editor/Settings/ExprObfuscationSettings.cs.meta new file mode 100644 index 0000000..55fb66c --- /dev/null +++ b/Editor/Settings/ExprObfuscationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d63d19f2b1a9f7c4b9812577d215c367 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Settings/ObfuzSettings.cs b/Editor/Settings/ObfuzSettings.cs index cfbb5dc..ba3e3fb 100644 --- a/Editor/Settings/ObfuzSettings.cs +++ b/Editor/Settings/ObfuzSettings.cs @@ -34,6 +34,9 @@ namespace Obfuz.Settings [Tooltip("call obfuscation settings")] public CallObfuscationSettings callObfusSettings; + [Tooltip("expression obfuscation settings")] + public ExprObfuscationSettings exprObfusSettings; + public string ObfuzRootDir => $"Library/Obfuz"; public string GetObfuscatedAssemblyOutputPath(BuildTarget target) diff --git a/Editor/Settings/ObfuzSettingsProvider.cs b/Editor/Settings/ObfuzSettingsProvider.cs index e8f81b7..470bc14 100644 --- a/Editor/Settings/ObfuzSettingsProvider.cs +++ b/Editor/Settings/ObfuzSettingsProvider.cs @@ -34,6 +34,7 @@ namespace Obfuz.Settings private SerializedProperty _constEncryptSettings; private SerializedProperty _fieldEncryptSettings; private SerializedProperty _callObfusSettings; + private SerializedProperty _exprObfusSettings; public ObfuzSettingsProvider() : base("Project/Obfuz", SettingsScope.Project) { @@ -64,6 +65,7 @@ namespace Obfuz.Settings _symbolObfusSettings = _serializedObject.FindProperty("symbolObfusSettings"); _constEncryptSettings = _serializedObject.FindProperty("constEncryptSettings"); + _exprObfusSettings = _serializedObject.FindProperty("exprObfusSettings"); _fieldEncryptSettings = _serializedObject.FindProperty("fieldEncryptSettings"); _callObfusSettings = _serializedObject.FindProperty("callObfusSettings"); } @@ -86,6 +88,7 @@ namespace Obfuz.Settings EditorGUILayout.PropertyField(_symbolObfusSettings); EditorGUILayout.PropertyField(_constEncryptSettings); + EditorGUILayout.PropertyField(_exprObfusSettings); EditorGUILayout.PropertyField(_fieldEncryptSettings); EditorGUILayout.PropertyField(_callObfusSettings); diff --git a/Editor/Utils/GenericArgumentContext.cs b/Editor/Utils/GenericArgumentContext.cs index fadd1dc..52328ad 100644 --- a/Editor/Utils/GenericArgumentContext.cs +++ b/Editor/Utils/GenericArgumentContext.cs @@ -7,8 +7,8 @@ namespace Obfuz.Utils { public sealed class GenericArgumentContext { - List typeArgsStack; - List methodArgsStack; + public readonly List typeArgsStack; + public readonly List methodArgsStack; public GenericArgumentContext(IList typeArgsStack, IList methodArgsStack) { @@ -100,6 +100,10 @@ namespace Obfuz.Utils private TypeSig Resolve(List args, uint number) { + if (args == null) + { + throw new ArgumentNullException(nameof(args)); + } return args[(int)number]; } } diff --git a/Editor/Utils/IRandom.cs b/Editor/Utils/IRandom.cs index e3a68b8..e37ca14 100644 --- a/Editor/Utils/IRandom.cs +++ b/Editor/Utils/IRandom.cs @@ -9,5 +9,9 @@ int NextInt(); long NextLong(); + + float NextFloat(); + + bool NextInPercentage(float percentage); } } diff --git a/Editor/Utils/MetaUtil.cs b/Editor/Utils/MetaUtil.cs index 0556272..1212da4 100644 --- a/Editor/Utils/MetaUtil.cs +++ b/Editor/Utils/MetaUtil.cs @@ -518,13 +518,22 @@ namespace Obfuz.Utils public static TypeSig Inflate(TypeSig sig, GenericArgumentContext ctx) { - if (!sig.ContainsGenericParameter) + if (ctx == null || !sig.ContainsGenericParameter) { return sig; } return ctx.Resolve(sig); } + public static IList TryInflate(IList sig, GenericArgumentContext ctx) + { + if (sig == null || ctx == null) + { + return sig; + } + return sig.Select(s => Inflate(s, ctx)).ToList() ?? null; + } + public static MethodSig InflateMethodSig(MethodSig methodSig, GenericArgumentContext genericArgumentContext) { @@ -564,7 +573,29 @@ namespace Obfuz.Utils throw new NotSupportedException($"type:{type}"); } - public static MethodSig GetInflatedMethodSig(IMethod method) + public static GenericArgumentContext GetInflatedMemberRefGenericArgument(IMemberRefParent type, GenericArgumentContext ctx) + { + if (type is TypeDef typeDef) + { + return null; + } + if (type is TypeRef typeRef) + { + return null; + } + if (type is TypeSpec typeSpec) + { + GenericInstSig genericInstSig = typeSpec.TypeSig.ToGenericInstSig(); + if (genericInstSig == null) + { + return ctx; + } + return new GenericArgumentContext(TryInflate(genericInstSig.GenericArguments, ctx), null); + } + throw new NotSupportedException($"type:{type}"); + } + + public static MethodSig GetInflatedMethodSig(IMethod method, GenericArgumentContext ctx) { if (method is MethodDef methodDef) { @@ -572,24 +603,40 @@ namespace Obfuz.Utils } if (method is MemberRef memberRef) { - return InflateMethodSig(memberRef.MethodSig, new GenericArgumentContext(GetGenericArguments(memberRef.Class), null)); + return InflateMethodSig(memberRef.MethodSig, GetInflatedMemberRefGenericArgument(memberRef.Class, ctx)); } if (method is MethodSpec methodSpec) { var genericInstMethodSig = methodSpec.GenericInstMethodSig; if (methodSpec.Method is MethodDef methodDef2) { - return InflateMethodSig(methodDef2.MethodSig, new GenericArgumentContext(null, genericInstMethodSig.GenericArguments)); + return InflateMethodSig(methodDef2.MethodSig, new GenericArgumentContext(null, TryInflate(genericInstMethodSig.GenericArguments, ctx))); } if (methodSpec.Method is MemberRef memberRef2) { - return InflateMethodSig(memberRef2.MethodSig, new GenericArgumentContext(GetGenericArguments(memberRef2.Class), genericInstMethodSig.GenericArguments)); + return InflateMethodSig(memberRef2.MethodSig, new GenericArgumentContext( + GetInflatedMemberRefGenericArgument(memberRef2.Class, ctx)?.typeArgsStack, + TryInflate(genericInstMethodSig.GenericArguments, ctx))); } } throw new NotSupportedException($" method: {method}"); } + public static TypeSig InflateFieldSig(IField field, GenericArgumentContext ctx) + { + if (field is FieldDef fieldDef) + { + return fieldDef.FieldType; + } + if (field is MemberRef memberRef) + { + return Inflate(memberRef.FieldSig.Type, new GenericArgumentContext(TryInflate(GetGenericArguments(memberRef.Class), ctx), null)); + } + + throw new Exception($"unknown field:{field}"); + } + public static ThisArgType GetThisArgType(IMethod method) { if (!method.MethodSig.HasThis) diff --git a/Editor/Utils/RandomWithKey.cs b/Editor/Utils/RandomWithKey.cs index b94115a..c3f9679 100644 --- a/Editor/Utils/RandomWithKey.cs +++ b/Editor/Utils/RandomWithKey.cs @@ -47,5 +47,15 @@ { return ((long)NextInt() << 32) | (uint)NextInt(); } + + public float NextFloat() + { + return (float)((double)NextInt() / int.MaxValue); + } + + public bool NextInPercentage(float percentage) + { + return NextFloat() < percentage; + } } } diff --git a/Runtime/EncryptionService.cs b/Runtime/EncryptionService.cs index 62128d6..546b05e 100644 --- a/Runtime/EncryptionService.cs +++ b/Runtime/EncryptionService.cs @@ -1,4 +1,6 @@ -namespace Obfuz +using System; + +namespace Obfuz { public static class EncryptionService where T : IEncryptionScope @@ -85,25 +87,25 @@ public static int DecryptFromRvaInt(byte[] data, int offset, int ops, int salt) { - int encryptedValue = ConstUtility.GetInt(data, offset); + int encryptedValue = BitConverter.ToInt32(data, offset); return Decrypt(encryptedValue, ops, salt); } public static long DecryptFromRvaLong(byte[] data, int offset, int ops, int salt) { - long encryptedValue = ConstUtility.GetLong(data, offset); + long encryptedValue = BitConverter.ToInt64(data, offset); return Decrypt(encryptedValue, ops, salt); } public static float DecryptFromRvaFloat(byte[] data, int offset, int ops, int salt) { - float encryptedValue = ConstUtility.GetFloat(data, offset); + float encryptedValue = BitConverter.ToSingle(data, offset); return Decrypt(encryptedValue, ops, salt); } public static double DecryptFromRvaDouble(byte[] data, int offset, int ops, int salt) { - double encryptedValue = ConstUtility.GetDouble(data, offset); + double encryptedValue = BitConverter.ToDouble(data, offset); return Decrypt(encryptedValue, ops, salt); } diff --git a/Runtime/ExprUtility.cs b/Runtime/ExprUtility.cs new file mode 100644 index 0000000..5192664 --- /dev/null +++ b/Runtime/ExprUtility.cs @@ -0,0 +1,215 @@ +namespace Obfuz +{ + public static class ExprUtility + { + public static int Add(int a, int b) + { + return a + b; + } + + public static long Add(long a, long b) + { + return a + b; + } + + public static float Add(float a, float b) + { + return a + b; + } + + public static double Add(double a, double b) + { + return a + b; + } + + public static int Subtract(int a, int b) + { + return a - b; + } + + public static long Subtract(long a, long b) + { + return a - b; + } + + public static float Subtract(float a, float b) + { + return a - b; + } + + public static double Subtract(double a, double b) + { + return a - b; + } + + public static int Multiply(int a, int b) + { + return a * b; + } + + public static long Multiply(long a, long b) + { + return a * b; + } + + public static float Multiply(float a, float b) + { + return a * b; + } + + public static double Multiply(double a, double b) + { + return a * b; + } + + public static int Divide(int a, int b) + { + return a / b; + } + + public static long Divide(long a, long b) + { + return a / b; + } + + public static float Divide(float a, float b) + { + return a / b; + } + + public static double Divide(double a, double b) + { + return a / b; + } + + public static int DivideUn(int a, int b) + { + return (int)((uint)a / (uint)b); + } + + public static long DivideUn(long a, long b) + { + return (long)((ulong)a / (ulong)b); + } + + public static int Rem(int a, int b) + { + return a % b; + } + + public static long Rem(long a, long b) + { + return a % b; + } + + public static float Rem(float a, float b) + { + return a % b; + } + + public static double Rem(double a, double b) + { + return a % b; + } + + public static int RemUn(int a, int b) + { + return (int)((uint)a % (uint)b); + } + + public static long RemUn(long a, long b) + { + return (long)((ulong)a % (ulong)b); + } + + public static int Negate(int a) + { + return -a; + } + + public static long Negate(long a) + { + return -a; + } + + public static float Negate(float a) + { + return -a; + } + + public static double Negate(double a) + { + return -a; + } + + public static int And(int a, int b) + { + return a & b; + } + + public static long And(long a, long b) + { + return a & b; + } + + public static int Or(int a, int b) + { + return a | b; + } + + public static long Or(long a, long b) + { + return a | b; + } + + public static int Xor(int a, int b) + { + return a ^ b; + } + + public static long Xor(long a, long b) + { + return a ^ b; + } + + public static int Not(int a) + { + return ~a; + } + + public static long Not(long a) + { + return ~a; + } + + public static int ShiftLeft(int a, int b) + { + return a << b; + } + + public static long ShiftLeft(long a, int b) + { + return a << b; + } + + public static int ShiftRight(int a, int b) + { + return a >> b; + } + + public static long ShiftRight(long a, int b) + { + return a >> b; + } + + public static int ShiftRightUn(int a, int b) + { + return (int)((uint)a >> b); + } + + public static long ShiftRightUn(long a, int b) + { + return (long)((ulong)a >> b); + } + } +} diff --git a/Runtime/ExprUtility.cs.meta b/Runtime/ExprUtility.cs.meta new file mode 100644 index 0000000..4f7c95b --- /dev/null +++ b/Runtime/ExprUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9aba5050818a0224696fcf73752fb225 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: