【问题标题】:How can I protect my private funcs against reflection executing?如何保护我的私有函数免受反射执行?
【发布时间】:2012-01-11 13:11:40
【问题描述】:

看到这个后:Do access modifiers affect reflection also?

我尝试使用它,但它不起作用:

如何防止反射执行我的私有方法?

【问题讨论】:

  • 访问修饰符是为了防止你出错,没有安全功能。
  • Reflection 是 .NET 的蜜獾。
  • 你不能。你为什么要这样做?
  • 这个问题提供了更多信息:stackoverflow.com/questions/2084353/… 注意:访问修饰符不是安全功能。
  • 查看 ReflectionPermissionAttribute 类。

标签: c# .net reflection


【解决方案1】:

我使用eazfuscator,它使方法虚拟化似乎可以很好地隐藏方法。

然后在方法中,我喜欢保护我将在发布模式下更改代码以提供空指针异常来启动一个任务,该任务将生成一个空指针并在没有调用堆栈的代码的情况下执行它。

没有多少开发人员可以在不调试的情况下使用您的生产代码...

#if !DEBUG
    #pragma warning disable CS8618 
    
    [System.Diagnostics.DebuggerStepThrough, System.Diagnostics.DebuggerNonUserCode]
    [System.ComponentModel.Browsable(false)]
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
#endif

namespace MyApp.Infrastructure.LicenseManager
{
    [Obfuscation(Feature = "apply to member * when method or constructor: virtualization", Exclude = false)]
    internal sealed class LicenseManager(ISomeClass parameter, ILoggerFactory? factory = null)
    {
       //hide the name space for reverse engineering the naming scheme
       _logger = factory?.CreateLogger("MyApp.LicenseManager") 

    #if !DEBUG
        if (System.Diagnostics.Debugger.IsAttached)
        {
            //send computer information for license violation and prosecution then crash
            System.Threading.ThreadPool.UnsafeQueueUserWorkItem(SystemCrasher.SendAbuseAndCrash);
            
            //send a log entry, of don't if you wish to give no warning
           _logger?.LogWarning(Walter.BOM.LogEvents.FireWall_Warning, "Debugging will cause the developer to generate null pointer exceptions in administration context");
           return;
           
           //all class initiolizers will stay null
         }
    #endif

        _myClass= parameter;

}

#if !DEBUG
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
#endif

如果您的代码被共享 DLL 中的其他代码使用(如 NuGet 包中),那么上述方法将阻止您受到同事的欢迎,您需要一个“调试”友好的方法,使用方法代理可能会与混淆相结合的诀窍。

[Obfuscation(Feature = "virtualization", Exclude = false)]
private void MyMethod(int parameter)
{
    MyMethodProxy();
    //do some code
}

private void MyMethodProxy([CallerMemberName] string? calledBy = null)
{
    if (!string.Equals(nameof(MyMethod), calledBy, StringComparison.Ordinal))
    {
        throw new LordOfTheRingsException("You shall not pass!!");
    }
}

通过使用 NameOf 方法,您可以避免混淆器重命名方案中的不可打印字符。

我使用了一些其他选项,但这会限制您自动化和使用面向方面的框架,例如 postsharp

希望对你有帮助enter code here

【讨论】:

    【解决方案2】:

    【讨论】:

      【解决方案3】:

      这是一个较晚的答案,但我认为这是一个更新,因为所有答案都是在 2015 年中期 .NET 4.6 发布之前编写的,该版本引入了一个名为 DisablePrivateReflection 的新程序集属性。

      使用在 AssemblyInfo.cs 中标记的该属性,您可以禁用对该程序集私有成员的反射。

      例子:

      namespace DisablingPrivateReflection
      {
          public class Singleton
          {
              private Singleton()
              {
      
              }
          }
      }
      

      在你的AssemblyInfo.cs 中添加这一行:

      [assembly: DisablePrivateReflection]
      

      然后在引用上述程序集的客户端程序集中,此代码将在运行时失败:

      var singleton = Activator.CreateInstance(typeof(Singleton), true);
      

      抛出的异常类型为MethodAccessException,带有消息:

      尝试通过方法“Program.Main(System.String[])”访问方法 'DisablingPrivateReflection.Singleton..ctor()' 失败。

      【讨论】:

      • 这由于某种原因使我的 exe 崩溃,当我将它与 ConfuserEx 一起使用时抛出 System.MethodAccessException
      • 很高兴知道这一点,我认为这可能是混淆策略的一部分,但正如文档中所述,不能 100% 依赖它docs.microsoft.com/en-us/dotnet/api/…
      【解决方案4】:
      using System;
      using System.Reflection;
      using System.Diagnostics;
      using System.Collections.Generic;
      using System.Linq;
      
      class Person
      {
          private string SayInternalSecure()
          {
               if (!PrivacyHelper.IsInvocationAllowed<Person>())
                  throw new Exception("you can't invoke this private method");
               return "Internal Secure";
          }
      
          private string SayInternal()
          {
               return "Internal";
          }
      
          public string SaySomething()
          {
               return "Hi " + this.SayInternal();
          }
      
          public string SaySomethingSecure()
          {
              return "Hi " + this.SayInternalSecure();
          }
      
          public void BeingCalledBy()
          {
                  Console.WriteLine("I'm being called by: " + new StackTrace().GetFrame(1).GetMethod().Name);
          }
      }
      
      public class MethodBaseComparer : IEqualityComparer<MethodBase> 
      {
           private string GetMethodIdentifier(MethodBase mb)
           {
            return mb.Name + ":" + String.Join(";", mb.GetParameters().Select(paramInfo=>paramInfo.Name).ToArray());
           }
      
           public bool Equals(MethodBase m1, MethodBase m2) 
           {
              //we need something more here, comparing just by name is not enough, need to take parameters into account
              return this.GetMethodIdentifier(m1) == this.GetMethodIdentifier(m2);
           }
      
           public int GetHashCode(MethodBase mb) 
           {
                   return this.GetMethodIdentifier(mb).GetHashCode();
           }
      }
      
      class PrivacyHelper
      {
      static Dictionary<Type, MethodBase[]> cache = new Dictionary<Type, MethodBase[]>();
      public static bool IsInvocationAllowed<T>()
      {
          Type curType = typeof(T);
          if (!cache.ContainsKey(curType))
          {
              cache[curType] = curType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToArray();
          }
          //StackTrace.GetFrame returns a MethodBase, not a MethodInfo, that's why we're falling back to MethodBody
          MethodBase invoker = new StackTrace().GetFrame(2).GetMethod();
          return cache[curType].Contains(invoker, new MethodBaseComparer());
      }
      }
      
      public class App
      {
      public static void CheckCaller()
      {
          Person p = new Person();
      
          Console.WriteLine("- calling via delegate");
          Action action = p.BeingCalledBy;
          action();
      
          Console.WriteLine("- calling via reflection");
          MethodInfo method = typeof(Person).GetMethod("BeingCalledBy", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
          Console.WriteLine(method.Invoke(p, null));
      
          Console.WriteLine("- calling via delegate again");
          action = (Action)(Delegate.CreateDelegate(typeof(Action), p, method));
          action();
      }
      
      public static void Main()
      {
          Console.WriteLine("Press key to run");
          Console.ReadLine();
      
          CheckCaller();
      
          Person p = new Person();
          Console.WriteLine(p.SaySomething());
          Console.WriteLine(p.SaySomethingSecure());
      
          MethodInfo privateMethod = typeof(Person).GetMethod("SayInternal", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
          Console.WriteLine("invoking private method via Reflection:");
          Console.WriteLine(privateMethod.Invoke(p, null));
      
          Console.WriteLine("----------------------");
      
          privateMethod = typeof(Person).GetMethod("SayInternalSecure", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
          Console.WriteLine("invoking secured private method via Reflection:");
          try
          {
              Console.WriteLine(privateMethod.Invoke(p, null));
          }
          catch(Exception ex)
          {
              Console.WriteLine(ex.Message);
          }
      }
      }
      

      【讨论】:

        【解决方案5】:

        虽然我完全同意访问修饰符不是安全功能的想法,只是为了编程我一直在思考这个问题,我有一个简单但没有多大用处的机制,对抗反射带反射:-)

        请注意,这只是一个愚蠢的概念证明,我没有考虑通用方法,它需要为此进行更改...

        这个想法是,在您希望防止“非法”调用的每个私有方法开始时,您只需通过反射检查您是从该类中的另一个方法调用的,而不是从外部调用。所以你会使用: 新的 StackTrace().GetFrame(1).GetMethod(); 获取调用者的 MethodBase 并将其与您的类的 MethodInfos 列表进行比较。

        您可以将其添加到帮助程序类中(无论如何您需要一个 IEqualityComparer 来比较 MethodBases...

        一个问题是您还会阻止一些正确的调用,例如通过委托或通过其他方法的反射来调用它......所以在使用它时应该小心。

        你可以我的实现here

        【讨论】:

          【解决方案6】:

          如何保护我的私有函数免受反射执行?

          您可以更改您的安全策略,使代码在您运行时无权执行“私有反射”。

          当然,这只会影响你的机器。如果您想影响其他人的机器,向他们的机器管理员发送电子邮件,并要求管理员更改用户的安全策略,使其无权进行“私人反射”。那是拥有机器及其运行网络的人;显然,您无法更改不属于您的网络的安全设置。

          当然请注意,比私有反射更强大的权限也必须受到限制。设置一个策略是没有好处的,例如“私有反射权限被拒绝,但更改安全策略的权限被授予”。然后,用户可以更改安全策略以重新授予他们自己的私有反射。

          您还必须限制访问磁盘的能力。可以访问磁盘的人可以简单地从程序集中读取代码,将私有元数据更改为公共元数据,然后正常加载程序集。

          因此,您的任务是说服世界上所有的机器管理员不允许他们的用户访问他们自己的磁盘。我怀疑你不会成功;我建议你找到一种不同的方法来保护你的函数不被滥用。

          【讨论】:

            【解决方案7】:

            如果有人目前可以在您的私有方法上使用反射,那么他们已经有足够的访问权限来回避您设置的任何其他内容。以较少的信任运行可能是一种选择,但这只是为了防止像插件这样的东西拥有过多的访问权限 - 它不会阻止具有(比如)管理员访问权限的用户,他们可以简单地提升访问权限。

            如果您不希望代码运行,请不要将其置于恶意用户的物理范围内;将其保存在网络服务或类似网站上。用户可用的任何代码都可以直接使用,也可以通过反编译间接使用(如果需要,还可以对其进行反混淆)。您可以使用一些技巧来阻止他们(通过堆栈跟踪等检查调用者),但这不会阻止某人确定。

            【讨论】:

              【解决方案8】:

              访问修饰符不是安全机制。

              如果if你可以阻止你的函数通过反射被调用,用户可以反编译你的程序,提取那个函数,把它放到一个新的程序集中并执行它。

              【讨论】:

                【解决方案9】:

                你不能,修饰符只是为了让开发者有适当的封装。在运行时,一切都在同一水平上可能会令人难过。

                反射机制,通常由需要调用一些预配置方法(旧 ORM)或显示它们(IDE)的应用程序使用。如果这个机制不能做到这一点,那真的很难。

                【讨论】:

                • 它在一定程度上依赖于运行时间; Silverlight 之类的东西在比完整 .NET 更紧凑的沙箱中运行,默认情况下可用的更少
                【解决方案10】:

                没有办法做到这一点。为什么要阻止执行您的私有函数?通常,如果有人使用反射,他就知道自己在做什么。

                【讨论】:

                • 我的私有方法是在从外部道具进行多次计算后从内部调用的。我不希望有人会激活 func 并绕过我的程序的常规流程
                • @RoyiNamir 为什么你关心这种情况,如果其他开发人员决定这样做并且它会导致问题,那么那是他的问题而不是你的问题。把你的代码想象成一台机器,你把这台机器交给另一个人使用。那个人打开机器开始搞乱工作,你不能阻止他们这样做,你所能做的就是贴上标签说这将使保修失效。如果他们破坏了它,那么忽略您的警告是他们自己的错,他们承担后果而不是您。
                • 这里的主要用例与安全性无关,而是代码可维护性...在大型单体系统中,您需要在有界上下文上建立良好的坚固墙,以便每个模块都可以独立开发模块而不破坏任何东西...如果另一个模块中的开发人员使用反射来调用我尚未公开的模块中的方法,然后我删除了该方法,那么另一个模块现在已损坏。通过设置这些强大/不可侵犯的边界,开发团队可以相互独立地移动,而不必担心损坏。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2015-08-11
                • 2011-03-15
                • 1970-01-01
                • 2013-07-21
                • 2011-01-29
                • 2020-11-17
                相关资源
                最近更新 更多