From efb98dbc2aba367ef105944fcd06e8f1b180ba8f Mon Sep 17 00:00:00 2001 From: Martin Karing Date: Sun, 28 Apr 2019 11:16:51 +0200 Subject: [PATCH] refs #39 Added analyzer for reflection The renaming protection now has an additional service for the usage of reflection. --- .../Analyzers/ReflectionAnalyzer.cs | 97 ++++++++++++++++ Confuser.Renamer/NameService.cs | 3 +- .../Analyzers/ReflectionAnalyzerTest.cs | 104 ++++++++++++++++++ .../Confuser.Renamer.Test.csproj | 1 + 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs create mode 100644 Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs diff --git a/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs b/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs new file mode 100644 index 0000000..657fa37 --- /dev/null +++ b/Confuser.Renamer/Analyzers/ReflectionAnalyzer.cs @@ -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 { + /// + /// This analyzer is looking for calls to the reflection API and blocks methods from being renamed if required. + /// + 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(), context.Modules.Cast().ToArray(), method); + } + + public void Analyze(INameService nameService, ITraceService traceService, IReadOnlyList 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> 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().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); + } + } + } + } + } + } + } + + /// + /// 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. + /// + private static IReadOnlyList 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() { refTypeDef }; + } + } + } + } + + return new List(); + } + + private static IReadOnlyList GetReferencedNames(Instruction instruction) { + if (instruction.OpCode.Code == Code.Ldstr && instruction.Operand is string str) { + return new List() { str }; + } + + return new List(); + } + + void IRenamer.PostRename(ConfuserContext context, INameService service, ProtectionParameters parameters, IDnlibDef def) { } + + void IRenamer.PreRename(ConfuserContext context, INameService service, ProtectionParameters parameters, IDnlibDef def) { } + } +} diff --git a/Confuser.Renamer/NameService.cs b/Confuser.Renamer/NameService.cs index 88ee516..0c6b245 100644 --- a/Confuser.Renamer/NameService.cs +++ b/Confuser.Renamer/NameService.cs @@ -67,7 +67,8 @@ namespace Confuser.Renamer { new VTableAnalyzer(), new TypeBlobAnalyzer(), new ResourceAnalyzer(), - new LdtokenEnumAnalyzer() + new LdtokenEnumAnalyzer(), + new ReflectionAnalyzer() }; } diff --git a/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs b/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs new file mode 100644 index 0000000..d4c7278 --- /dev/null +++ b/Tests/Confuser.Renamer.Test/Analyzers/ReflectionAnalyzerTest.cs @@ -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(); + 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 }, 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(); + 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 }, 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(); + 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 }, 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); + } + } +} diff --git a/Tests/Confuser.Renamer.Test/Confuser.Renamer.Test.csproj b/Tests/Confuser.Renamer.Test/Confuser.Renamer.Test.csproj index 4eaab72..70ea2d6 100644 --- a/Tests/Confuser.Renamer.Test/Confuser.Renamer.Test.csproj +++ b/Tests/Confuser.Renamer.Test/Confuser.Renamer.Test.csproj @@ -7,6 +7,7 @@ +