private void ProcessMethodDefinition(MethodDefinition method) { MethodBody body = method.Body; body.SimplifyMacros(); ILProcessor ilProcessor = body.GetILProcessor(); if (body.HasExceptionHandlers) { for (int i = 0; i < body.ExceptionHandlers.Count; i++) { //This doesn't rethrow per se, but it might be the path to more fine grained control //var exceptionType = typeof(Exception); //var exceptionCtor = exceptionType.GetConstructor(new Type[] {}); //var constructorReference = ModuleDefinition.ImportReference(exceptionCtor); //ilProcessor.InsertBefore(body.ExceptionHandlers[i].HandlerEnd.Previous, Instruction.Create(OpCodes.Newobj, constructorReference)); //ilProcessor.InsertBefore(body.ExceptionHandlers[i].HandlerEnd.Previous, Instruction.Create(OpCodes.Throw)); ilProcessor.Replace(body.ExceptionHandlers[i].HandlerEnd.Previous, Instruction.Create(OpCodes.Rethrow)); } } body.InitLocals = true; body.OptimizeMacros(); }
private static void injectAssemblyLoad(MethodBody methodBody, string sourceAssemblyPath, Func <MethodDefinition, MethodReference> resolveMethod) { methodBody.SimplifyMacros(); var returnInstructions = methodBody.Instructions .Where(x => x.OpCode == OpCodes.Ret) .ToList(); using var sourceAssembly = AssemblyPathUtils.ReadAssemblyFromPath(sourceAssemblyPath, false); var sourceAssemblyMainModule = sourceAssembly.MainModule; var foundMethodInitializerMethods = ModuleDefinitionUtils.FindModuleInitializerMethods(sourceAssemblyMainModule) .Select(resolveMethod); foreach (var instruction in returnInstructions) { var instructions = new List <Instruction>(foundMethodInitializerMethods.Select(x => Instruction.Create(OpCodes.Call, x))) { Instruction.Create(OpCodes.Ret) }; methodBody.Instructions.Replace(instruction, instructions); } methodBody.OptimizeMacros(); }
// The idea behind this method is to change method call from Method(obj1, obj2, obj3) to Method(param Object[] objs) // To do that, we need to first pack all the objects to an array, then push the array onto the stack before calling the method. // Creating an array is achieved by pushing number of elements on the stack, and using newarr instruction. // Lastly, we need to pop the array back from the stack and store it in a local variable. // // Putting object to array is done by: // Loading array to the stack, loading index to the stack, loading the reference to value on the stack // and using stelem.ref to insert it to the array. stelem.ref instruction pops all three inserted values // from the stack // // For example, we need to convert something like this: // // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() // IL_0005: stloc.0 // IL_0006: ldloc.0 // IL_0007: ldstr "{0}, {1}" // IL_000c: ldstr "one" // IL_0011: ldstr "two" // IL_0016: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) // IL_001b: pop // // To this: // // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() // IL_0005: stloc.0 // IL_0006: ldloc.0 // IL_0007: ldstr "{0}, {1}\r\n" // IL_000c: ldstr "one" // IL_0011: ldstr "two" // IL_0016: ldc.i4 2 // IL_001b: newarr [mscorlib]System.Object // IL_0020: stloc 1 // IL_0024: stloc 2 // IL_0028: stloc 3 // IL_002c: ldloc 1 // IL_0030: ldc.i4 0 // IL_0035: ldloc 2 // IL_0039: stelem.ref // IL_003a: ldloc 1 // IL_003e: ldc.i4 1 // IL_0043: ldloc 3 // IL_0047: stelem.ref // IL_0048: ldloc 1 // IL_004c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) // IL_0051: pop // // Basically, just before the invalid function call we pack all the arguments (that are already on the stack, // ready to be passed to invalid function) to a newly created array and call a valid function instead passing // the array as argument // public void RewriteObjectListToParamsCall(MethodBody methodBody, int instructionIndex) { methodBody.SimplifyMacros(); var parameterType = ParamsMethod.Parameters.Last().ParameterType; var arrayInfo = new VariableDefinition(parameterType); methodBody.InitLocals = true; methodBody.Variables.Add(arrayInfo); var instruction = methodBody.Instructions[instructionIndex]; int numberOfObjects = (instruction.Operand as MethodReference).Parameters.Count - ParamsMethod.Parameters.Count + 1; // Firstly, let's create the object array // Push number of objects to the stack var instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects); var firstInstruction = instr; methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Create a new array instr = Instruction.Create(OpCodes.Newarr, ParamsMethod.Parameters.Last().ParameterType.GetElementType()); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Store the newly created array to first variable slot instr = Instruction.Create(OpCodes.Stloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // At this point, all the references to objects that need to be packed to the array are on the stack. var objectInfo = new VariableDefinition[numberOfObjects]; for (int i = 0; i < numberOfObjects; i++) { objectInfo[i] = new VariableDefinition(parameterType.GetElementType()); methodBody.Variables.Add(objectInfo[i]); instr = Instruction.Create(OpCodes.Stloc, objectInfo[i]); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } // Now that we got the references to objects in local variables rather than the stack, it's high time we insert them to the array // We need to load them in backwards order, because the last argument was taken off the stack first. for (int i = 0; i < numberOfObjects; i++) { // Load reference to the array to the stack instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Load object index to the stack instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects - i - 1); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Load reference to the object to the stack instr = Instruction.Create(OpCodes.Ldloc, objectInfo[i]); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Insert the object to the array if (parameterType.GetElementType().IsValueType) { instr = Instruction.Create(OpCodes.Stelem_Any, parameterType.GetElementType()); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } else { instr = Instruction.Create(OpCodes.Stelem_Ref); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } } // Finally, load reference to the array to the stack so it can be inserted to the array instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instruction.Operand = ParamsMethod; ParamsMethod = null; MethodChanged = false; methodBody.OptimizeMacros(); // This, together with SimplifyMacros() before touching IL code, recalculates IL instruction offsets // If any other instruction is referencing the illegal call, we need to rewrite it to reference beginning of object packing instead // For example, there's a branch jump to call the method. We need to pack the objects anyway before calling the method foreach (var changeableInstruction in methodBody.Instructions) { if (changeableInstruction.Operand is Instruction && (changeableInstruction as Instruction).Operand == instruction) { changeableInstruction.Operand = firstInstruction; } } }
private void Inject() { body = Method.Body; body.SimplifyMacros(); var ilProcessor = body.GetILProcessor(); var returnFixer = new ReturnFixer { Method = Method }; returnFixer.MakeLastStatementReturn(); var methodBodyFirstInstruction = GetMethodBodyFirstInstruction(); var startInstructions = new List <Instruction>(); startInstructions.AddRange(GetStartInstructions()); foreach (var instruction in startInstructions) { ilProcessor.InsertBefore(methodBodyFirstInstruction, instruction); } var paramInstructions = GetParamInstructions(); paramInstructions.Reverse(); if (paramInstructions.Any()) { foreach (var instruction in paramInstructions) { ilProcessor.InsertAfter(startInstructions.Last(), instruction); } } ilProcessor.InsertBefore(returnFixer.NopBeforeReturn, GetReturnValueInstructions(returnFixer.ReturnVariable)); var tryCatchLeaveInstructions = Instruction.Create(OpCodes.Leave, returnFixer.NopBeforeReturn); var catchInstructions = new List <Instruction>(); catchInstructions.AddRange(GetCatchInstructions()); ilProcessor.InsertBefore(returnFixer.NopBeforeReturn, tryCatchLeaveInstructions); ilProcessor.InsertBefore(returnFixer.NopBeforeReturn, catchInstructions); var endInstructions = new List <Instruction>(); endInstructions.AddRange(GetEndInstructions()); var finallyInstruction = Instruction.Create(OpCodes.Endfinally); endInstructions.Add(finallyInstruction); endInstructions.Reverse(); foreach (var instruction in endInstructions) { ilProcessor.InsertAfter(catchInstructions.Last(), instruction); } var handler = new ExceptionHandler(ExceptionHandlerType.Catch) { CatchType = ExceptionType, TryStart = methodBodyFirstInstruction, TryEnd = tryCatchLeaveInstructions.Next, HandlerStart = catchInstructions.First(), HandlerEnd = catchInstructions.Last().Next }; body.ExceptionHandlers.Add(handler); handler = new ExceptionHandler(ExceptionHandlerType.Finally) { TryStart = methodBodyFirstInstruction, TryEnd = catchInstructions.Last().Next, HandlerStart = catchInstructions.Last().Next, HandlerEnd = finallyInstruction.Next }; body.ExceptionHandlers.Add(handler); var instructions = body.Instructions; Instruction doubleDupInstruction = null; for (var index = 0; index < instructions.Count; index++) { var instruction = instructions[index]; if (instruction.OpCode == OpCodes.Dup && instructions[index + 1].OpCode == OpCodes.Dup) { doubleDupInstruction = instructions[index + 1]; } if (instruction.OpCode == OpCodes.Pop && doubleDupInstruction != null) { var extraPopInstruction = instructions[index]; ilProcessor.Remove(extraPopInstruction); ilProcessor.InsertAfter(doubleDupInstruction, extraPopInstruction); doubleDupInstruction = null; } } body.InitLocals = true; body.OptimizeMacros(); }
public DirectCallMethod(ModuleDefinition module, TypeDefinition type) { this.module = module; this.type = type; getLength = module.Import(typeof(string).GetMethod("get_Length", new Type[0])); getChars = module.Import(typeof(string).GetMethod("get_Chars", new[] { typeof(int) })); isNullOrEmpty = module.Import(typeof(string).GetMethod("IsNullOrEmpty", new[] { typeof(string) })); stringEquals = module.Import(typeof(string).GetMethod("Equals", new[] { typeof(string) })); // Copy method definition from base class var base_assembly = AssemblyDefinition.ReadAssembly(Path.Combine(Interface.Oxide.ExtensionDirectory, "Oxide.Ext.CSharp.dll")); var base_module = base_assembly.MainModule; var base_type = module.Import(base_assembly.MainModule.GetType("Oxide.Plugins.CSharpPlugin")).Resolve(); var base_method = module.Import(base_type.Methods.First(method => method.Name == "DirectCallHook")).Resolve(); // Create method override based on virtual method signature method = new MethodDefinition(base_method.Name, base_method.Attributes, base_module.Import(base_method.ReturnType)) { DeclaringType = type }; foreach (var parameter in base_method.Parameters) { var new_param = new ParameterDefinition(parameter.Name, parameter.Attributes, base_module.Import(parameter.ParameterType)) { IsOut = parameter.IsOut, Constant = parameter.Constant, MarshalInfo = parameter.MarshalInfo, IsReturnValue = parameter.IsReturnValue }; foreach (var attribute in parameter.CustomAttributes) new_param.CustomAttributes.Add(new CustomAttribute(module.Import(attribute.Constructor))); method.Parameters.Add(new_param); } foreach (var attribute in base_method.CustomAttributes) method.CustomAttributes.Add(new CustomAttribute(module.Import(attribute.Constructor))); method.ImplAttributes = base_method.ImplAttributes; method.SemanticsAttributes = base_method.SemanticsAttributes; // Replace the NewSlot attribute with ReuseSlot method.Attributes &= ~MethodAttributes.NewSlot; method.Attributes |= MethodAttributes.ReuseSlot; // Create new method body body = new MethodBody(method); body.SimplifyMacros(); method.Body = body; type.Methods.Add(method); // Create variables body.Variables.Add(new VariableDefinition("name_size", module.TypeSystem.Int32)); body.Variables.Add(new VariableDefinition("i", module.TypeSystem.Int32)); // Initialize return value to null AddInstruction(OpCodes.Ldarg_2); AddInstruction(OpCodes.Ldnull); AddInstruction(OpCodes.Stind_Ref); // Check for name null or empty AddInstruction(OpCodes.Ldarg_1); AddInstruction(OpCodes.Call, isNullOrEmpty); var empty = AddInstruction(OpCodes.Brfalse, body.Instructions[0]); Return(false); // Get method name length empty.Operand = AddInstruction(OpCodes.Ldarg_1); AddInstruction(OpCodes.Callvirt, getLength); AddInstruction(OpCodes.Stloc_0); // Initialize i counter variable to 0 AddInstruction(OpCodes.Ldc_I4_0); AddInstruction(OpCodes.Stloc_1); // Find all hook methods defined by the plugin foreach (var m in type.Methods.Where(m => !m.IsStatic && m.IsPrivate && !m.HasGenericParameters && !m.ReturnType.IsGenericParameter && m.DeclaringType == type && !m.IsSetter && !m.IsGetter)) { //ignore compiler generated if (m.Name.Contains("<")) continue; if (!hookMethods.ContainsKey(m.Name)) hookMethods[m.Name] = m; } // Build a hook method name trie var root_node = new Node(); foreach (var method_name in hookMethods.Keys) { var current_node = root_node; for (var i = 1; i <= method_name.Length; i++) { var letter = method_name[i - 1]; Node next_node; if (!current_node.Edges.TryGetValue(letter, out next_node)) { next_node = new Node { Parent = current_node, Char = letter }; current_node.Edges[letter] = next_node; } if (i == method_name.Length) next_node.Name = method_name; current_node = next_node; } } // Build conditional method call logic from trie nodes var n = 1; foreach (var edge in root_node.Edges.Keys) BuildNode(root_node.Edges[edge], n++); // No valid method was found endInstruction = Return(false); foreach (var instruction in jumpToEdgePlaceholderTargets.Keys) { instruction.Operand = jumpToEdgePlaceholderTargets[instruction].FirstInstruction; } foreach (var instruction in jumpToEndPlaceholders) { instruction.Operand = endInstruction; } body.OptimizeMacros(); }
public DirectCallMethod(ModuleDefinition module, TypeDefinition type) { this.module = module; this.type = type; getLength = module.Import(typeof(string).GetMethod("get_Length", new Type[0])); getChars = module.Import(typeof(string).GetMethod("get_Chars", new[] { typeof(int) })); isNullOrEmpty = module.Import(typeof(string).GetMethod("IsNullOrEmpty")); // Copy method definition from base class var base_assembly = AssemblyDefinition.ReadAssembly("HookDispatch.exe"); var base_module = base_assembly.MainModule; var base_type = base_assembly.MainModule.GetType("Oxide.Plugins.Plugin"); var base_method = base_type.Methods.First(method => method.Name == "DirectCallHook"); // Create method override based on virtual method signature method = new MethodDefinition(base_method.Name, base_method.Attributes, base_module.Import(base_method.ReturnType)); foreach (var parameter in base_method.Parameters) method.Parameters.Add(new ParameterDefinition(base_module.Import(parameter.ParameterType)) {Name = parameter.Name, IsOut = parameter.IsOut}); method.ImplAttributes = base_method.ImplAttributes; method.SemanticsAttributes = base_method.SemanticsAttributes; // Replace the NewSlot attribute with ReuseSlot method.Attributes &= ~Mono.Cecil.MethodAttributes.NewSlot; method.Attributes |= Mono.Cecil.MethodAttributes.ReuseSlot; // Create new method body body = new Mono.Cecil.Cil.MethodBody(method); body.SimplifyMacros(); method.Body = body; type.Methods.Add(method); // Create variables body.Variables.Add(new VariableDefinition("name_size", module.TypeSystem.Int32)); body.Variables.Add(new VariableDefinition("i", module.TypeSystem.Int32)); // Initialize return value to null AddInstruction(OpCodes.Ldarg_2); AddInstruction(OpCodes.Ldnull); AddInstruction(OpCodes.Stind_Ref); // Check for name null or empty AddInstruction(OpCodes.Ldarg_1); AddInstruction(OpCodes.Call, isNullOrEmpty); var empty = AddInstruction(OpCodes.Brfalse, body.Instructions[0]); Return(false); // Get method name length empty.Operand = AddInstruction(OpCodes.Ldarg_1); AddInstruction(OpCodes.Callvirt, getLength); AddInstruction(OpCodes.Stloc_0); // Initialize i counter variable to 0 AddInstruction(OpCodes.Ldc_I4_0); AddInstruction(OpCodes.Stloc_1); // Find all hook methods defined by the plugin hookMethods = type.Methods.Where(m => !m.IsStatic && m.IsPrivate).ToDictionary(m => m.Name, m => m); // Build a hook method name trie var root_node = new Node(); foreach (var method_name in hookMethods.Keys) { var current_node = root_node; for (var i = 1; i <= method_name.Length; i++) { var letter = method_name[i - 1]; Node next_node; if (!current_node.Edges.TryGetValue(letter, out next_node)) { next_node = new Node { Parent = current_node, Char = letter }; if (i == method_name.Length) next_node.Name = method_name; current_node.Edges[letter] = next_node; } current_node = next_node; } } // Build conditional method call logic from trie nodes var n = 1; foreach (var edge in root_node.Edges.Keys) BuildNode(root_node.Edges[edge], n++); // No valid method was found endInstruction = Return(false); foreach (var instruction in jumpToEdgePlaceholderTargets.Keys) { instruction.Operand = jumpToEdgePlaceholderTargets[instruction].FirstInstruction; } foreach (var instruction in jumpToEndPlaceholders) { instruction.Operand = endInstruction; } //UpdateInstructions(); body.OptimizeMacros(); foreach (var instruction in jumpToEdgePlaceholderTargets.Keys) { Puts($"Jump: {instruction}"); } foreach (var instruction in jumpToEndPlaceholders) { Puts($"Jump to end: {instruction}"); } foreach (var instruction in body.Instructions) { var line = instruction.ToString(); Puts(line); if (line.Contains(" bne.un") || line.Contains(" ret")) Puts(""); } }
// The idea behind this method is to change method call from Method(obj1, obj2, obj3) to Method(param Object[] objs) // To do that, we need to first pack all the objects to an array, then push the array onto the stack before calling the method. // Creating an array is achieved by pushing number of elements on the stack, and using newarr instruction. // Lastly, we need to pop the array back from the stack and store it in a local variable. // // Putting object to array is done by: // Loading array to the stack, loading index to the stack, loading the reference to value on the stack // and using stelem.ref to insert it to the array. stelem.ref instruction pops all three inserted values // from the stack // // For example, we need to convert something like this: // // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() // IL_0005: stloc.0 // IL_0006: ldloc.0 // IL_0007: ldstr "{0}, {1}" // IL_000c: ldstr "one" // IL_0011: ldstr "two" // IL_0016: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) // IL_001b: pop // // To this: // // IL_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() // IL_0005: stloc.0 // IL_0006: ldloc.0 // IL_0007: ldstr "{0}, {1}\r\n" // IL_000c: ldstr "one" // IL_0011: ldstr "two" // IL_0016: ldc.i4 2 // IL_001b: newarr [mscorlib]System.Object // IL_0020: stloc 1 // IL_0024: stloc 2 // IL_0028: stloc 3 // IL_002c: ldloc 1 // IL_0030: ldc.i4 0 // IL_0035: ldloc 2 // IL_0039: stelem.ref // IL_003a: ldloc 1 // IL_003e: ldc.i4 1 // IL_0043: ldloc 3 // IL_0047: stelem.ref // IL_0048: ldloc 1 // IL_004c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object[]) // IL_0051: pop // // Basically, just before the invalid function call we pack all the arguments (that are already on the stack, // ready to be passed to invalid function) to a newly created array and call a valid function instead passing // the array as argument // public void RewriteObjectListToParamsCall(MethodBody methodBody, int instructionIndex) { methodBody.SimplifyMacros(); var parameterType = ParamsMethod.Parameters.Last().ParameterType; var arrayInfo = new VariableDefinition(parameterType); methodBody.InitLocals = true; methodBody.Variables.Add(arrayInfo); var instruction = methodBody.Instructions[instructionIndex]; int numberOfObjects = (instruction.Operand as MethodReference).Parameters.Count - ParamsMethod.Parameters.Count + 1; // Firstly, let's create the object array // Push number of objects to the stack var instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects); var firstInstruction = instr; methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Create a new array instr = Instruction.Create(OpCodes.Newarr, ParamsMethod.Parameters.Last().ParameterType.GetElementType()); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Store the newly created array to first variable slot instr = Instruction.Create(OpCodes.Stloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // At this point, all the references to objects that need to be packed to the array are on the stack. var objectInfo = new VariableDefinition[numberOfObjects]; for (int i = 0; i < numberOfObjects; i++) { objectInfo[i] = new VariableDefinition(parameterType.GetElementType()); methodBody.Variables.Add(objectInfo[i]); instr = Instruction.Create(OpCodes.Stloc, objectInfo[i]); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } // Now that we got the references to objects in local variables rather than the stack, it's high time we insert them to the array // We need to load them in backwards order, because the last argument was taken off the stack first. for (int i = 0; i < numberOfObjects; i++) { // Load reference to the array to the stack instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Load object index to the stack instr = Instruction.Create(OpCodes.Ldc_I4, numberOfObjects - i - 1); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Load reference to the object to the stack instr = Instruction.Create(OpCodes.Ldloc, objectInfo[i]); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; // Insert the object to the array if (parameterType.GetElementType().IsValueType) { instr = Instruction.Create(OpCodes.Stelem_Any, parameterType.GetElementType()); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } else { instr = Instruction.Create(OpCodes.Stelem_Ref); methodBody.Instructions.Insert(instructionIndex, instr); instructionIndex++; } } // Finally, load reference to the array to the stack so it can be inserted to the array instr = Instruction.Create(OpCodes.Ldloc, arrayInfo); methodBody.Instructions.Insert(instructionIndex, instr); instruction.Operand = ParamsMethod; ParamsMethod = null; MethodChanged = false; methodBody.OptimizeMacros(); // This, together with SimplifyMacros() before touching IL code, recalculates IL instruction offsets // If any other instruction is referencing the illegal call, we need to rewrite it to reference beginning of object packing instead // For example, there's a branch jump to call the method. We need to pack the objects anyway before calling the method foreach (var changeableInstruction in methodBody.Instructions) { if (changeableInstruction.Operand is Instruction && (changeableInstruction as Instruction).Operand == instruction) { changeableInstruction.Operand = firstInstruction; } } }
public DirectCallMethod(ModuleDefinition module, TypeDefinition type) { this.module = module; this.type = type; getLength = module.Import(typeof(string).GetMethod("get_Length", new Type[0])); getChars = module.Import(typeof(string).GetMethod("get_Chars", new[] { typeof(int) })); // Copy method definition from base class var base_assembly = AssemblyDefinition.ReadAssembly("HookDispatch.exe"); var base_module = base_assembly.MainModule; var base_type = base_assembly.MainModule.GetType("Oxide.Plugins.Plugin"); var base_method = base_type.Methods.First(method => method.Name == "DirectCallHook"); // Create method override based on virtual method signature method = new MethodDefinition(base_method.Name, base_method.Attributes, base_module.Import(base_method.ReturnType)); foreach (var parameter in base_method.Parameters) { method.Parameters.Add(new ParameterDefinition(base_module.Import(parameter.ParameterType))); } method.ImplAttributes = base_method.ImplAttributes; method.SemanticsAttributes = base_method.SemanticsAttributes; // Replace the NewSlot attribute with ReuseSlot method.Attributes &= ~Mono.Cecil.MethodAttributes.NewSlot; method.Attributes |= Mono.Cecil.MethodAttributes.ReuseSlot; // Create new method body body = new Mono.Cecil.Cil.MethodBody(method); body.SimplifyMacros(); method.Body = body; type.Methods.Add(method); // Create variables body.Variables.Add(new VariableDefinition("name_size", module.TypeSystem.Int32)); body.Variables.Add(new VariableDefinition("i", module.TypeSystem.Int32)); // Initialize return value to null AddInstruction(OpCodes.Ldarg_2); AddInstruction(OpCodes.Ldnull); AddInstruction(OpCodes.Stind_Ref); // Get method name length AddInstruction(OpCodes.Ldarg_1); AddInstruction(OpCodes.Callvirt, getLength); AddInstruction(OpCodes.Stloc_0); // Initialize i counter variable to 0 AddInstruction(OpCodes.Ldc_I4_0); AddInstruction(OpCodes.Stloc_1); // Find all hook methods defined by the plugin hookMethods = type.Methods.Where(m => !m.IsStatic && m.IsPrivate).ToDictionary(m => m.Name, m => m); // Build a hook method name trie var root_node = new Node(); foreach (var method_name in hookMethods.Keys) { var current_node = root_node; for (var i = 1; i <= method_name.Length; i++) { var letter = method_name[i - 1]; Node next_node; if (!current_node.Edges.TryGetValue(letter, out next_node)) { next_node = new Node { Parent = current_node, Char = letter }; if (i == method_name.Length) { next_node.Name = method_name; } current_node.Edges[letter] = next_node; } current_node = next_node; } } // Build conditional method call logic from trie nodes var n = 1; foreach (var edge in root_node.Edges.Keys) { BuildNode(root_node.Edges[edge], n++); } // No valid method was found endInstruction = Return(false); foreach (var instruction in jumpToEdgePlaceholderTargets.Keys) { instruction.Operand = jumpToEdgePlaceholderTargets[instruction].FirstInstruction; } foreach (var instruction in jumpToEndPlaceholders) { instruction.Operand = endInstruction; } //UpdateInstructions(); body.OptimizeMacros(); foreach (var instruction in jumpToEdgePlaceholderTargets.Keys) { Puts($"Jump: {instruction}"); } foreach (var instruction in jumpToEndPlaceholders) { Puts($"Jump to end: {instruction}"); } foreach (var instruction in body.Instructions) { var line = instruction.ToString(); Puts(line); if (line.Contains(" bne.un.s") || line.Contains(" ret")) { Puts(""); } } }
private void InnerProcess(MethodDefinition method, bool returnVoid, ReferenceContainer references) { body = method.Body; body.SimplifyMacros(); if (body.Instructions.Count <= 3) return; // nothing to do (empty method) HandleReturnType(method, returnVoid, references); var ilProcessor = body.GetILProcessor(); var firstInstruction = FirstInstructionSkipCtor(method); firstInstruction = RejigFirstInstruction(ilProcessor, firstInstruction); InjectContext(ilProcessor, firstInstruction, method, references); var returnInstruction = FixReturns(); var beforeReturn = Instruction.Create(OpCodes.Nop); ilProcessor.InsertBefore(returnInstruction, beforeReturn); // exclude try-catch from constructors (lot's of pain otherwise) if (!method.IsConstructor) { var beginCatch = InjectIlForCatch(ilProcessor, beforeReturn, references); var beginFinally = InjectIlForFinaly(ilProcessor, beforeReturn, references); var catchHandler = new ExceptionHandler(ExceptionHandlerType.Catch) { TryStart = firstInstruction, TryEnd = beginCatch, CatchType = references.ExceptionType, HandlerStart = beginCatch, HandlerEnd = beginFinally, }; body.ExceptionHandlers.Add(catchHandler); var finallyHandler = new ExceptionHandler(ExceptionHandlerType.Finally) { TryStart = firstInstruction, TryEnd = beginFinally, HandlerStart = beginFinally, HandlerEnd = beforeReturn, }; body.ExceptionHandlers.Add(finallyHandler); } else { InjectIlForDispose(ilProcessor, returnInstruction, references); } body.InitLocals = true; body.OptimizeMacros(); }
private void PatchAssembly(Action <byte[]> callback) { Action action1 = null; if (this.isPatching) { Interface.Oxide.LogWarning("Already patching plugin assembly: {0} (ignoring)", new object[] { this.PluginNames.ToSentence <string>() }); return; } float now = Interface.Oxide.Now; this.isPatching = true; ThreadPool.QueueUserWorkItem((object _) => { AssemblyDefinition assemblyDefinition; try { using (MemoryStream memoryStream = new MemoryStream(this.RawAssembly)) { assemblyDefinition = AssemblyDefinition.ReadAssembly(memoryStream); } ConstructorInfo constructor = typeof(UnauthorizedAccessException).GetConstructor(new Type[] { typeof(string) }); MethodReference methodReference1 = assemblyDefinition.MainModule.Import(constructor); Action <TypeDefinition> methods = null; methods = (TypeDefinition type) => { string fullName; string str; foreach (MethodDefinition method in type.Methods) { bool flag = false; if (method.Body != null) { bool flag1 = false; foreach (VariableDefinition variable in method.Body.Variables) { if (!CompiledAssembly.IsNamespaceBlacklisted(variable.VariableType.FullName)) { continue; } Mono.Cecil.Cil.MethodBody methodBody = new Mono.Cecil.Cil.MethodBody(method); methodBody.Instructions.Add(Instruction.Create(OpCodes.Ldstr, string.Concat("System access is restricted, you are not allowed to use ", variable.VariableType.FullName))); methodBody.Instructions.Add(Instruction.Create(OpCodes.Newobj, methodReference1)); methodBody.Instructions.Add(Instruction.Create(OpCodes.Throw)); method.Body = methodBody; flag1 = true; break; } if (flag1) { continue; } Collection <Instruction> instructions = method.Body.Instructions; ILProcessor lProcessor = method.Body.GetILProcessor(); Instruction instruction = instructions.First <Instruction>(); for (int i = 0; i < instructions.Count && !flag; i++) { Instruction item = instructions[i]; if (item.OpCode == OpCodes.Ldtoken) { IMetadataTokenProvider operand = item.Operand as IMetadataTokenProvider; str = (operand != null ? operand.ToString() : null); string str1 = str; if (CompiledAssembly.IsNamespaceBlacklisted(str1)) { lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, string.Concat("System access is restricted, you are not allowed to use ", str1))); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Newobj, methodReference1)); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Throw)); flag = true; } } else if (item.OpCode == OpCodes.Call || item.OpCode == OpCodes.Calli || item.OpCode == OpCodes.Callvirt || item.OpCode == OpCodes.Ldftn) { MethodReference methodReference = item.Operand as MethodReference; string str2 = (methodReference != null ? methodReference.DeclaringType.FullName : null); if (str2 == "System.Type" && methodReference.Name == "GetType" || CompiledAssembly.IsNamespaceBlacklisted(str2)) { lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, string.Concat("System access is restricted, you are not allowed to use ", str2))); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Newobj, methodReference1)); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Throw)); flag = true; } } else if (item.OpCode == OpCodes.Ldfld) { FieldReference fieldReference = item.Operand as FieldReference; fullName = (fieldReference != null ? fieldReference.FieldType.FullName : null); string str3 = fullName; if (CompiledAssembly.IsNamespaceBlacklisted(str3)) { lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, string.Concat("System access is restricted, you are not allowed to use ", str3))); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Newobj, methodReference1)); lProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Throw)); flag = true; } } } } else if (method.HasPInvokeInfo) { MethodDefinition attributes = method; attributes.Attributes = attributes.Attributes & (Mono.Cecil.MethodAttributes.MemberAccessMask | Mono.Cecil.MethodAttributes.Private | Mono.Cecil.MethodAttributes.FamANDAssem | Mono.Cecil.MethodAttributes.Assembly | Mono.Cecil.MethodAttributes.Family | Mono.Cecil.MethodAttributes.FamORAssem | Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static | Mono.Cecil.MethodAttributes.Final | Mono.Cecil.MethodAttributes.Virtual | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.VtableLayoutMask | Mono.Cecil.MethodAttributes.NewSlot | Mono.Cecil.MethodAttributes.CheckAccessOnOverride | Mono.Cecil.MethodAttributes.Abstract | Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.UnmanagedExport | Mono.Cecil.MethodAttributes.RTSpecialName | Mono.Cecil.MethodAttributes.HasSecurity | Mono.Cecil.MethodAttributes.RequireSecObject); Mono.Cecil.Cil.MethodBody methodBody1 = new Mono.Cecil.Cil.MethodBody(method); methodBody1.Instructions.Add(Instruction.Create(OpCodes.Ldstr, "PInvoke access is restricted, you are not allowed to use PInvoke")); methodBody1.Instructions.Add(Instruction.Create(OpCodes.Newobj, methodReference1)); methodBody1.Instructions.Add(Instruction.Create(OpCodes.Throw)); method.Body = methodBody1; } if (!flag) { continue; } Mono.Cecil.Cil.MethodBody body = method.Body; if (body != null) { body.OptimizeMacros(); } else { } } foreach (TypeDefinition nestedType in type.NestedTypes) { methods(nestedType); } }; foreach (TypeDefinition typeDefinition in assemblyDefinition.MainModule.Types) { methods(typeDefinition); if (this.IsCompilerGenerated(typeDefinition)) { continue; } if (typeDefinition.Namespace != "Oxide.Plugins") { if (!(typeDefinition.FullName != "<Module>") || this.PluginNames.Any <string>((string plugin) => typeDefinition.FullName.StartsWith(string.Concat("Oxide.Plugins.", plugin)))) { continue; } Interface.Oxide.LogWarning(((int)this.PluginNames.Length == 1 ? string.Concat(this.PluginNames[0], " has polluted the global namespace by defining ", typeDefinition.FullName) : string.Concat("A plugin has polluted the global namespace by defining ", typeDefinition.FullName)), Array.Empty <object>()); } else if (!this.PluginNames.Contains <string>(typeDefinition.Name)) { Interface.Oxide.LogWarning(((int)this.PluginNames.Length == 1 ? string.Concat(this.PluginNames[0], " has polluted the global namespace by defining ", typeDefinition.Name) : string.Concat("A plugin has polluted the global namespace by defining ", typeDefinition.Name)), Array.Empty <object>()); } else { Collection <MethodDefinition> methodDefinitions = typeDefinition.Methods; Func <MethodDefinition, bool> u003cu003e9_204 = CompiledAssembly.< > c.< > 9__20_4; if (u003cu003e9_204 == null) { u003cu003e9_204 = (MethodDefinition m) => { if (m.IsStatic || !m.IsConstructor || m.HasParameters) { return(false); } return(!m.IsPublic); }; CompiledAssembly.< > c.< > 9__20_4 = u003cu003e9_204; } if (methodDefinitions.FirstOrDefault <MethodDefinition>(u003cu003e9_204) == null) { DirectCallMethod directCallMethod = new DirectCallMethod(assemblyDefinition.MainModule, typeDefinition); } else { CompilablePlugin compilablePlugin = this.CompilablePlugins.SingleOrDefault <CompilablePlugin>((CompilablePlugin p) => p.Name == typeDefinition.Name); if (compilablePlugin == null) { continue; } compilablePlugin.CompilerErrors = "Primary constructor in main class must be public"; } } } foreach (TypeDefinition typeDefinition1 in assemblyDefinition.MainModule.Types) { if (typeDefinition1.Namespace != "Oxide.Plugins" || !this.PluginNames.Contains <string>(typeDefinition1.Name)) { continue; } Collection <MethodDefinition> methods1 = typeDefinition1.Methods; Func <MethodDefinition, bool> u003cu003e9_206 = CompiledAssembly.< > c.< > 9__20_6; if (u003cu003e9_206 == null) { u003cu003e9_206 = (MethodDefinition m) => { if (m.IsStatic || m.HasGenericParameters || m.ReturnType.IsGenericParameter || m.IsSetter) { return(false); } return(!m.IsGetter); }; CompiledAssembly.< > c.< > 9__20_6 = u003cu003e9_206; } foreach (MethodDefinition methodDefinition in methods1.Where <MethodDefinition>(u003cu003e9_206)) { foreach (ParameterDefinition parameter in methodDefinition.Parameters) { foreach (CustomAttribute customAttribute in parameter.CustomAttributes) { } } } } using (MemoryStream memoryStream1 = new MemoryStream()) { assemblyDefinition.Write(memoryStream1); this.PatchedAssembly = memoryStream1.ToArray(); } OxideMod oxide = Interface.Oxide; Action u003cu003e9_2 = action1; if (u003cu003e9_2 == null) { Action u003cu003e4_this = () => { this.isPatching = false; callback(this.PatchedAssembly); }; Action action = u003cu003e4_this; action1 = u003cu003e4_this; u003cu003e9_2 = action; } oxide.NextTick(u003cu003e9_2); } catch (Exception exception1) { Exception exception = exception1; Interface.Oxide.NextTick(() => { this.isPatching = false; Interface.Oxide.LogException(string.Concat("Exception while patching: ", this.PluginNames.ToSentence <string>()), exception); callback(null); }); } }); }