refs #39 Added analyzer for reflection
The renaming protection now has an additional service for the usage of reflection.
This commit is contained in:
Родитель
af847d7dda
Коммит
efb98dbc2a
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Confuser.Core;
|
||||
using Confuser.Core.Services;
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Emit;
|
||||
|
||||
namespace Confuser.Renamer.Analyzers {
|
||||
/// <summary>
|
||||
/// This analyzer is looking for calls to the reflection API and blocks methods from being renamed if required.
|
||||
/// </summary>
|
||||
public sealed class ReflectionAnalyzer : IRenamer {
|
||||
void IRenamer.Analyze(ConfuserContext context, INameService service, ProtectionParameters parameters, IDnlibDef def) {
|
||||
if (!(def is MethodDef method) || !method.HasBody) return;
|
||||
|
||||
Analyze(service, context.Registry.GetService<ITraceService>(), context.Modules.Cast<ModuleDef>().ToArray(), method);
|
||||
}
|
||||
|
||||
public void Analyze(INameService nameService, ITraceService traceService, IReadOnlyList<ModuleDef> moduleDefs, MethodDef method) {
|
||||
MethodTrace methodTrace = null;
|
||||
MethodTrace GetMethodTrace() {
|
||||
if (methodTrace == null)
|
||||
methodTrace = traceService.Trace(method);
|
||||
return methodTrace;
|
||||
}
|
||||
|
||||
foreach (var instr in method.Body.Instructions) {
|
||||
if (instr.OpCode.Code == Code.Call && instr.Operand is IMethodDefOrRef calledMethod) {
|
||||
if (calledMethod.DeclaringType.FullName == "System.Type") {
|
||||
Func<TypeDef, IEnumerable<IMemberDef>> getMember = null;
|
||||
if (calledMethod.Name == nameof(Type.GetMethod))
|
||||
getMember = t => t.Methods;
|
||||
else if (calledMethod.Name == nameof(Type.GetField))
|
||||
getMember = t => t.Fields;
|
||||
else if (calledMethod.Name == nameof(Type.GetProperty))
|
||||
getMember = t => t.Properties;
|
||||
else if (calledMethod.Name == nameof(Type.GetEvent))
|
||||
getMember = t => t.Events;
|
||||
else if (calledMethod.Name == nameof(Type.GetMember))
|
||||
getMember = t => Enumerable.Empty<IMemberDef>().Concat(t.Methods).Concat(t.Fields).Concat(t.Properties).Concat(t.Events);
|
||||
|
||||
if (getMember != null) {
|
||||
var trace = GetMethodTrace();
|
||||
var arguments = trace.TraceArguments(instr);
|
||||
if (arguments.Length >= 2) {
|
||||
var types = GetReferencedTypes(method.Body.Instructions[arguments[0]], method, trace);
|
||||
var names = GetReferencedNames(method.Body.Instructions[arguments[1]]);
|
||||
|
||||
if (!types.Any())
|
||||
types = moduleDefs.SelectMany(m => m.GetTypes()).ToArray();
|
||||
|
||||
foreach (var possibleMethod in types.SelectMany(getMember).Where(m => names.Contains(m.Name))) {
|
||||
nameService.SetCanRename(possibleMethod, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is used to determine the types that are load onto the stack at the referenced instruction.
|
||||
/// In case the method is unable to determine all the types reliable, it will return a empty list.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<TypeDef> GetReferencedTypes(Instruction instruction, MethodDef method, MethodTrace trace) {
|
||||
if (instruction.OpCode.Code == Code.Call && instruction.Operand is IMethodDefOrRef calledMethod) {
|
||||
if (calledMethod.DeclaringType.FullName == "System.Type" && calledMethod.Name == "GetTypeFromHandle") {
|
||||
var arguments = trace.TraceArguments(instruction);
|
||||
if (arguments.Length == 1) {
|
||||
var ldTokenInstr = method.Body.Instructions[arguments[0]];
|
||||
if (ldTokenInstr.OpCode.Code == Code.Ldtoken && ldTokenInstr.Operand is TypeDef refTypeDef) {
|
||||
return new List<TypeDef>() { refTypeDef };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new List<TypeDef>();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<UTF8String> GetReferencedNames(Instruction instruction) {
|
||||
if (instruction.OpCode.Code == Code.Ldstr && instruction.Operand is string str) {
|
||||
return new List<UTF8String>() { str };
|
||||
}
|
||||
|
||||
return new List<UTF8String>();
|
||||
}
|
||||
|
||||
void IRenamer.PostRename(ConfuserContext context, INameService service, ProtectionParameters parameters, IDnlibDef def) { }
|
||||
|
||||
void IRenamer.PreRename(ConfuserContext context, INameService service, ProtectionParameters parameters, IDnlibDef def) { }
|
||||
}
|
||||
}
|
|
@ -67,7 +67,8 @@ namespace Confuser.Renamer {
|
|||
new VTableAnalyzer(),
|
||||
new TypeBlobAnalyzer(),
|
||||
new ResourceAnalyzer(),
|
||||
new LdtokenEnumAnalyzer()
|
||||
new LdtokenEnumAnalyzer(),
|
||||
new ReflectionAnalyzer()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Confuser.Core.Services;
|
||||
using Confuser.Renamer.Analyzers;
|
||||
using dnlib.DotNet;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Confuser.Renamer.Test.Analyzers {
|
||||
public sealed class ReflectionAnalyzerTest {
|
||||
private string _referenceField;
|
||||
|
||||
private string ReferenceProperty { get; }
|
||||
|
||||
private void TestReferenceMethod1() {
|
||||
var method1 = typeof(ReflectionAnalyzerTest).GetMethod(nameof(TestReferenceMethod1));
|
||||
Assert.Null(method1);
|
||||
var method2 = typeof(ReflectionAnalyzerTest).GetMethod(nameof(TestReferenceMethod1), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
Assert.NotNull(method2);
|
||||
}
|
||||
|
||||
public void TestReferenceField1() {
|
||||
var field1 = typeof(ReflectionAnalyzerTest).GetField(nameof(_referenceField));
|
||||
Assert.Null(field1);
|
||||
var field2 = typeof(ReflectionAnalyzerTest).GetField(nameof(_referenceField), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
Assert.NotNull(field2);
|
||||
}
|
||||
|
||||
public void TestReferenceProperty1() {
|
||||
var prop1 = typeof(ReflectionAnalyzerTest).GetProperty(nameof(ReferenceProperty));
|
||||
Assert.Null(prop1);
|
||||
var prop2 = typeof(ReflectionAnalyzerTest).GetProperty(nameof(ReferenceProperty), BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
Assert.NotNull(prop2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReferenceMethod1Test() {
|
||||
TestReferenceMethod1();
|
||||
|
||||
var moduleDef = LoadTestModuleDef();
|
||||
var thisTypeDef = moduleDef.Find("Confuser.Renamer.Test.Analyzers.ReflectionAnalyzerTest", false);
|
||||
var refMethod = thisTypeDef.FindMethod(nameof(TestReferenceMethod1));
|
||||
|
||||
var nameService = Mock.Of<INameService>();
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refMethod, false));
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refMethod, false));
|
||||
|
||||
var traceService = new TraceService();
|
||||
var analyzer = new ReflectionAnalyzer();
|
||||
analyzer.Analyze(nameService, traceService, new List<ModuleDef>() { moduleDef }, refMethod);
|
||||
|
||||
Mock.Get(nameService).VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReferenceField1Test() {
|
||||
TestReferenceField1();
|
||||
|
||||
var moduleDef = LoadTestModuleDef();
|
||||
var thisTypeDef = moduleDef.Find("Confuser.Renamer.Test.Analyzers.ReflectionAnalyzerTest", false);
|
||||
var refMethod = thisTypeDef.FindMethod(nameof(TestReferenceField1));
|
||||
var refField = thisTypeDef.FindField(nameof(_referenceField));
|
||||
|
||||
var nameService = Mock.Of<INameService>();
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refField, false));
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refField, false));
|
||||
|
||||
var traceService = new TraceService();
|
||||
var analyzer = new ReflectionAnalyzer();
|
||||
analyzer.Analyze(nameService, traceService, new List<ModuleDef>() { moduleDef }, refMethod);
|
||||
|
||||
Mock.Get(nameService).VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestReferenceProperty1Test() {
|
||||
TestReferenceProperty1();
|
||||
|
||||
var moduleDef = LoadTestModuleDef();
|
||||
var thisTypeDef = moduleDef.Find("Confuser.Renamer.Test.Analyzers.ReflectionAnalyzerTest", false);
|
||||
var refMethod = thisTypeDef.FindMethod(nameof(TestReferenceProperty1));
|
||||
var refProp = thisTypeDef.FindProperty(nameof(ReferenceProperty));
|
||||
|
||||
var nameService = Mock.Of<INameService>();
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refProp, false));
|
||||
Mock.Get(nameService).Setup(s => s.SetCanRename(refProp, false));
|
||||
|
||||
var traceService = new TraceService();
|
||||
var analyzer = new ReflectionAnalyzer();
|
||||
analyzer.Analyze(nameService, traceService, new List<ModuleDef>() { moduleDef }, refMethod);
|
||||
|
||||
Mock.Get(nameService).VerifyAll();
|
||||
}
|
||||
|
||||
private static ModuleDef LoadTestModuleDef() {
|
||||
var asmResolver = new AssemblyResolver { EnableTypeDefCache = true };
|
||||
asmResolver.DefaultModuleContext = new ModuleContext(asmResolver);
|
||||
var options = new ModuleCreationOptions(asmResolver.DefaultModuleContext) {
|
||||
TryToLoadPdbFromDisk = false
|
||||
};
|
||||
return ModuleDefMD.Load(typeof(VTableTest).Module, options);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.1" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче