如果有人目前能够在您的私有方法上使用反射,那么他们已经拥有足够的访问权限来绕过您设置的任何其他障碍。运行时减少信任可能是一种选择,但这只是为了防止插件等东西获得过多的访问权限 - 它不会阻止具有(例如)管理员访问权限的用户提升访问权限。
如果您不希望代码运行,请不要将其放在恶意用户物理接触范围内; 将其保存在网络服务或类似地方。用户可以直接或间接地使用任何可用的代码,甚至可以对其进行反编译(如果需要还可以进行反混淆)。您可以使用一些技巧来阻止他们(例如通过堆栈跟踪检查调用者),但这不会阻止决心很强的人。
AssemblyInfo.cs
中标记该属性,即可禁用对该程序集的私有成员的反射。namespace DisablingPrivateReflection
{
public class Singleton
{
private Singleton()
{
}
}
}
AssemblyInfo.cs
中添加此行:[assembly: DisablePrivateReflection]
然后在引用上述程序集的客户端程序集中,此代码将在运行时失败:
var singleton = Activator.CreateInstance(typeof(Singleton), true);
方法“Program.Main(System.String[])”尝试访问方法“DisablingPrivateReflection.Singleton..ctor()”失败。
访问修饰符并非安全机制。
如果您可以防止函数被通过反射调用,用户仍然可以反编译程序,提取该函数,将其放入新的程序集中并执行它。
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);
}
}
}
没有办法做到那样。为什么你想防止执行你的私有函数?通常,如果有人使用反射,他知道自己在做什么。
你不能这样做,修饰符只是为了让开发人员拥有适当的封装。运行时可能会感到一切都处于同一级别。
反射机制通常被应用程序使用,需要调用一些预配置方法(旧 ORM)或显示它们(IDE)。如果这种机制无法做到这一点,那将非常困难。
虽然我完全同意访问修饰符不是安全功能的想法,但为了编程的缘故,我思考了一下这个问题,并得出了一个简单而不太有用的机制,使用反射来对抗反射 :-)
请注意,这只是一个愚蠢的概念证明,我没有考虑泛型方法,如果要使用泛型方法,需要进行更改...
这个想法是,在你想要保护免受“非法”调用的每个私有方法的开头,你只需通过反射检查你是否从该类的另一个方法中调用,而不是从外部调用。所以你会使用: new StackTrace().GetFrame(1).GetMethod(); 来获取你的调用者的MethodBase,并将其与你的类的MethodInfos列表进行比较。
你可以将它添加到一个帮助类中(无论如何,你都需要一个IEqualityComparer来比较MethodBases...
一个问题是,你也会阻止一些正确的调用,比如通过委托或通过反射从你的另一个方法中调用... 所以你在使用它时应该小心。
你可以在这里找到我的实现here
我使用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
[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!!");
}
}
输入代码
。