受限 AppDomain 中的 C# 类继承自位于主 AppDomain 中的其他类

Posted

技术标签:

【中文标题】受限 AppDomain 中的 C# 类继承自位于主 AppDomain 中的其他类【英文标题】:C# Class in a restricted AppDomain inherit from an other class located in the main AppDomain 【发布时间】:2019-12-24 16:19:42 【问题描述】:

我尝试在 C# 中创建一个简单的控制台改装项目,其中我的程序包含一个名为 ElementInGame抽象类列表。我希望能够创建从 .txt 文件继承 ElementInGame 的其他类。 ElementInGame 类将包含一些基本方法(虚拟的和非虚拟的)。但我不希望这些其他修改的类执行恶意代码,我希望它们只能访问继承类的方法/属性。这是我的 ElementInGame 代码:

(我的 C# 程序 #1)

using System;

namespace Modding

    //The class itself inherit from MarshalByRefObject to be available in 2 differents Domains
    public abstract class ElementInGame : MarshalByRefObject
    
        public ElementInGame()
        
            Console.WriteLine("ElementInGame class created");
        

        public virtual int GetNumber()
        
            return 10;
        

        public void CountToTen()
        
            for (int i = 0; i <= 10; i++)
            
                Console.WriteLine(i);
            
        
    

然后我的 .txt 文件存储在“C:\program.txt”

(我原来的 .txt 文件)

using System;

namespace Test
   
    public class HelloWorld
    
        public HelloWorld()
        
            Console.WriteLine("Called Constructor() !");
        

        public static int TestMethod()
        
            Console.WriteLine("Called TestMethod() !");
            return 11;
        
    

所以我编写了主程序来读取.txt文件,有限制地编译它,然后执行它:

(我的 C# 程序 #2 在第二个 .cs 文件中,长代码警告)

using System;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
using System.Security.Permissions;
using System.Security;
using System.Security.Policy;
using System.Runtime.Remoting;
using System.Collections.Generic;

namespace Modding

    public class Program : MarshalByRefObject
    
        public static void Main(string[] args)
        

            string assemblyPath = @"C:\program.txt"; // Where the .txt file is stored
            string code = File.ReadAllText(assemblyPath); //The code to compile

            CompilerResults compile = CompileFromCode(code); //Compile the code in the temporary files

            string fullPath = compile.PathToAssembly;                           //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp\5v2p3qki.dll
            string pathWithoutFile = Path.GetDirectoryName(fullPath);           //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp
            string pathNameOnly = Path.GetFileNameWithoutExtension(fullPath);   //sample : 5v2p3qki

            Program newDomainInstance = GetOtherProtectedDomainInstance(pathWithoutFile);

            newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
            newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld");

            List<ElementInGame> allElement = new List<ElementInGame>();
            //allElement.Add ***?***

            Console.ReadKey();
        

        public static Program GetOtherProtectedDomainInstance(string pathWithoutFile)
        
            AppDomainSetup adSetup = new AppDomainSetup();
            adSetup.ApplicationBase = pathWithoutFile;

            //Set some permissions to avoid malicious code
            PermissionSet permSet = new PermissionSet(PermissionState.None);
            permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

            StrongName fullTrustAssembly = new StrongName(
                new StrongNamePublicKeyBlob(typeof(Program).Assembly.GetName().GetPublicKey()),
                typeof(Program).Assembly.GetName().Name,
                typeof(Program).Assembly.GetName().Version);

            AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);

            ObjectHandle handle = Activator.CreateInstanceFrom(
                newDomain, typeof(Program).Assembly.ManifestModule.FullyQualifiedName,
                typeof(Program).FullName
                );

            Program newDomainInstance = (Program)handle.Unwrap();
            return newDomainInstance;
        

        public static CompilerResults CompileFromCode(string code)
        
            //Compile the code in a .dll locate in the temporary files
            //The following code is based on https://***.com/questions/10314815/trying-to-compile-and-execute-c-sharp-code-programmatically
            CompilerParameters CompilerParams = new CompilerParameters();
            string outputDirectory = Directory.GetCurrentDirectory();

            CompilerParams.GenerateInMemory = false;
            CompilerParams.TreatWarningsAsErrors = false;
            CompilerParams.GenerateExecutable = false;
            CompilerParams.CompilerOptions = "/optimize";

            //Adding a reference to the current project to allow the .txt file to inherit the class "ElementInGame" later
            string[] references =  "System.dll", Assembly.GetEntryAssembly().Location ;
            CompilerParams.ReferencedAssemblies.AddRange(references);

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);

            if (compile.Errors.HasErrors)
            
                string text = "Compile error: ";
                foreach (CompilerError ce in compile.Errors)
                
                    text += "rn" + ce.ToString();
                
                throw new Exception(text);
            

            return compile;
        

        public static void DisplaySomething()//Useful for later
        
            Console.WriteLine("This isn't supposed to be display");
        

        //Calling a method from the restricted Domain
        public void CallMethod(string assemblyName, string typeName, string entryPoint, object objectToExecute = null, object[] parameters = null)
        
            MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
            try
            
                target.Invoke(objectToExecute, parameters);
            
            catch
            
                Console.WriteLine("Security Error with Method " + assemblyName + " namespace : " + typeName + " method : " + entryPoint);
            
        

        //Create an instance from the restricted Domain
        public void CreateObject(string assemblyName, string typeName)
        
            try
            
                object o = Assembly.Load(assemblyName).CreateInstance(typeName);
            
            catch
            
                Console.WriteLine("Security Error with Constructor " + assemblyName + " namespace : " + typeName);
            
        
    

目前 .txt 文件没有任何链接我的 C# 程序代码工作正常,我得到以下输出:

Called TestMethod() !
Called Constructor() !

然后我在 .txt 文件中编辑我的代码以从 Modding.ElementInGame 继承:

(我的编辑 .txt 文件)

using System;

namespace Test
   
    public class HelloWorld : Modding.ElementInGame
    
        public HelloWorld() : base()
        
            Console.WriteLine("Called Constructor() !");
        

        public static int TestMethod()
        
            Console.WriteLine("Called TestMethod() !");
            return 11;
        
    

所以我希望输出如下:

Called TestMethod() !
ElementInGame class created
Called Constructor() !

但在此更改之后,程序崩溃在调用 TestMethod 时出现System.NullReferenceExceptionnewDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);

但是创建一个 HelloWorld 的实例(.txt 文件):newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld"); 似乎可以工作(没有崩溃,在执行 try/catch 时代码保留在 try 部分),但我的我的控制台中没有打印任何内容,所以我猜它不起作用?

更改 AppDomain 的权限不会改变任何事情。

PermissionSet permSet = new PermissionSet(PermissionState.Unrestricted);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags));

所以我的问题是:我怎样才能在我的程序中创建存储一个继承的.txt文件的实例来自 ElementInGame (并将其添加到 ElementInGame 列表中)?

这样我就可以从我的程序中使用虚拟方法 GetNumber()。我不希望 .txt 文件访问程序本身(例如调用 DisplaySomething() 方法),只需与 ElementInGame 通信

【问题讨论】:

【参考方案1】:

您正在从不同位置生成和加载参考程序集。您确实为输出设置了当前目录,但忘记将其分配给编译器参数。

string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.OutputAssembly = Path.Combine(outputDirectory, "Test.dll");

这应该可以解决问题。

【讨论】:

感谢它的工作!我尝试使用“ElementInGame o = (ElementInGame)Assembly.Load(assemblyName).CreateInstance(typeName);”来投射创建的对象在 CreateObject 方法中,但它不起作用。你知道为什么吗? 使用“Assembly.LoadFrom()”从给定路径加载程序集。 我有一个“System.Reflection.TargetInvocationException”。这是截图:ibb.co/YWW4Xjr(注意我的视觉是法语) 您需要更改您的应用域权限设置才能访问IO资源。 我做了一些测试,现在它可以与“Assembly.Load()'”一起正常工作,我不知道为什么它第一次不起作用。再次感谢@vendettamit!

以上是关于受限 AppDomain 中的 C# 类继承自位于主 AppDomain 中的其他类的主要内容,如果未能解决你的问题,请参考以下文章

AppDomain 中的对象被垃圾收集

C# AppDomain 类

列出存储在 AppDomain 中的所有自定义数据

受限 AppDomain 中的加载项应如何访问升级服务

在 C# 中的自定义 appdomain 中加载 dll

.Net AppDomain详解