Exemplo n.º 1
0
        private void PatchAssembly(Action <byte[]> callback)
        {
            if (isPatching)
            {
                Interface.Oxide.LogWarning("Already patching plugin assembly: {0} (ignoring)", PluginNames.ToSentence());
                RemoteLogger.Warning($"Already patching plugin assembly: {PluginNames.ToSentence()}");
                return;
            }

            var startedAt = Interface.Oxide.Now;

            isPatching = true;
            ThreadPool.QueueUserWorkItem(_ =>
            {
                try
                {
                    AssemblyDefinition definition;
                    using (var stream = new MemoryStream(RawAssembly))
                        definition = AssemblyDefinition.ReadAssembly(stream);

                    var exceptionConstructor = typeof(UnauthorizedAccessException).GetConstructor(new[] { typeof(string) });
                    var securityException    = definition.MainModule.Import(exceptionConstructor);

                    Action <TypeDefinition> patchModuleType = null;
                    patchModuleType = type =>
                    {
                        foreach (var method in type.Methods)
                        {
                            var changedMethod = false;

                            if (method.Body == null)
                            {
                                if (method.HasPInvokeInfo)
                                {
                                    method.Attributes &= ~MethodAttributes.PInvokeImpl;
                                    var body           = new MethodBody(method);
                                    body.Instructions.Add(Instruction.Create(OpCodes.Ldstr, "PInvoke access is restricted, you are not allowed to use PInvoke"));
                                    body.Instructions.Add(Instruction.Create(OpCodes.Newobj, securityException));
                                    body.Instructions.Add(Instruction.Create(OpCodes.Throw));
                                    method.Body = body;
                                }
                            }
                            else
                            {
                                var replacedMethod = false;
                                foreach (var variable in method.Body.Variables)
                                {
                                    if (!IsNamespaceBlacklisted(variable.VariableType.FullName))
                                    {
                                        continue;
                                    }

                                    var body = new MethodBody(method);
                                    body.Instructions.Add(Instruction.Create(OpCodes.Ldstr, $"System access is restricted, you are not allowed to use {variable.VariableType.FullName}"));
                                    body.Instructions.Add(Instruction.Create(OpCodes.Newobj, securityException));
                                    body.Instructions.Add(Instruction.Create(OpCodes.Throw));
                                    method.Body    = body;
                                    replacedMethod = true;
                                    break;
                                }
                                if (replacedMethod)
                                {
                                    continue;
                                }

                                var instructions = method.Body.Instructions;
                                var ilProcessor  = method.Body.GetILProcessor();
                                var first        = instructions.First();

                                var i = 0;
                                while (i < instructions.Count)
                                {
                                    if (changedMethod)
                                    {
                                        break;
                                    }
                                    var instruction = instructions[i];
                                    if (instruction.OpCode == OpCodes.Ldtoken)
                                    {
                                        var operand = instruction.Operand as IMetadataTokenProvider;
                                        var token   = operand?.ToString();
                                        if (IsNamespaceBlacklisted(token))
                                        {
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, $"System access is restricted, you are not allowed to use {token}"));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Newobj, securityException));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Throw));
                                            changedMethod = true;
                                        }
                                    }
                                    else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt)
                                    {
                                        var methodCall    = instruction.Operand as MethodReference;
                                        var fullNamespace = methodCall?.DeclaringType.FullName;

                                        if ((fullNamespace == "System.Type" && methodCall.Name == "GetType") || IsNamespaceBlacklisted(fullNamespace))
                                        {
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, $"System access is restricted, you are not allowed to use {fullNamespace}"));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Newobj, securityException));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Throw));
                                            changedMethod = true;
                                        }
                                    }
                                    else if (instruction.OpCode == OpCodes.Ldfld)
                                    {
                                        var fieldType     = instruction.Operand as FieldReference;
                                        var fullNamespace = fieldType?.FieldType.FullName;
                                        if (IsNamespaceBlacklisted(fullNamespace))
                                        {
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Ldstr, $"System access is restricted, you are not allowed to use {fullNamespace}"));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Newobj, securityException));
                                            ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Throw));
                                            changedMethod = true;
                                        }
                                    }
                                    i++;
                                }
                            }

                            if (changedMethod)
                            {
                                method.Body?.OptimizeMacros();

                                /*//Interface.Oxide.LogDebug("Updating {0} instruction offsets: {1}", instructions.Count, method.FullName);
                                 * int curoffset = 0;
                                 * for (var i = 0; i < instructions.Count; i++)
                                 * {
                                 *  var instruction = instructions[i];
                                 *  instruction.Previous = (i == 0) ? null : instructions[i - 1];
                                 *  instruction.Next = (i == instructions.Count - 1) ? null : instructions[i + 1];
                                 *  instruction.Offset = curoffset;
                                 *  curoffset += instruction.GetSize();
                                 *  //Interface.Oxide.LogDebug("    {0}", instruction.ToString());
                                 * }*/
                            }
                        }
                        foreach (var nestedType in type.NestedTypes)
                        {
                            patchModuleType(nestedType);
                        }
                    };

                    foreach (var type in definition.MainModule.Types)
                    {
                        patchModuleType(type);

                        if (type.Namespace == "Oxide.Plugins")
                        {
                            if (PluginNames.Contains(type.Name))
                            {
                                var constructor = type.Methods.FirstOrDefault(m => !m.IsStatic && m.IsConstructor && !m.HasParameters && !m.IsPublic);
                                if (constructor != null)
                                {
                                    var plugin = CompilablePlugins.SingleOrDefault(p => p.Name == type.Name);
                                    if (plugin != null)
                                    {
                                        plugin.CompilerErrors = "Primary constructor in main class must be public";
                                    }
                                }
                                else
                                {
                                    new DirectCallMethod(definition.MainModule, type);
                                }
                            }
                            else
                            {
                                Interface.Oxide.LogWarning(
                                    $"A plugin has polluted the global namespace by defining {type.Name}: {PluginNames.ToSentence()}");
                                RemoteLogger.Info(
                                    $"A plugin has polluted the global namespace by defining {type.Name}: {PluginNames.ToSentence()}");
                            }
                        }
                    }

                    // TODO: Why is there no error on boot using this?
                    foreach (var type in definition.MainModule.Types)
                    {
                        if (type.Namespace != "Oxide.Plugins" || !PluginNames.Contains(type.Name))
                        {
                            continue;
                        }
                        foreach (var m in type.Methods.Where(m => !m.IsStatic && !m.HasGenericParameters && !m.ReturnType.IsGenericParameter && !m.IsSetter && !m.IsGetter))
                        {
                            foreach (var parameter in m.Parameters)
                            {
                                foreach (var attribute in parameter.CustomAttributes)
                                {
                                    //Interface.Oxide.LogInfo($"{m.FullName} - {parameter.Name} - {attribute.Constructor.FullName}");
                                }
                            }
                        }
                    }

                    using (var stream = new MemoryStream())
                    {
                        definition.Write(stream);
                        PatchedAssembly = stream.ToArray();
                    }

                    Interface.Oxide.NextTick(() =>
                    {
                        isPatching = false;
                        //Interface.Oxide.LogDebug("Patching {0} assembly took {1:0.00} ms", ScriptName, Interface.Oxide.Now - startedAt);
                        callback(PatchedAssembly);
                    });
                }
                catch (Exception ex)
                {
                    Interface.Oxide.NextTick(() =>
                    {
                        isPatching = false;
                        Interface.Oxide.LogException($"Exception while patching: {PluginNames.ToSentence()}", ex);
                        RemoteLogger.Exception($"Exception while patching: {PluginNames.ToSentence()}", ex);
                        callback(null);
                    });
                }
            });
        }