diff --git a/Confuser.Core/ObfAttrMarker.cs b/Confuser.Core/ObfAttrMarker.cs index 2bc5592f0..f1ca17c7f 100644 --- a/Confuser.Core/ObfAttrMarker.cs +++ b/Confuser.Core/ObfAttrMarker.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Text.RegularExpressions; +using System.Text; using Confuser.Core.Project; using Confuser.Core.Project.Patterns; using dnlib.DotNet; @@ -25,30 +25,100 @@ struct ObfuscationAttributeInfo { struct ProtectionSettingsInfo { public bool ApplyToMember; public bool Exclude; + + public PatternExpression Condition; public string Settings; } class ProtectionSettingsStack { - readonly Stack stack; + readonly ConfuserContext context; + readonly Stack> stack; + readonly ObfAttrParser parser; + ProtectionSettings settings; + + class PopHolder : IDisposable { + ProtectionSettingsStack parent; + + public PopHolder(ProtectionSettingsStack parent) { + this.parent = parent; + } + + public void Dispose() { + parent.Pop(); + } + } - public ProtectionSettingsStack() { - stack = new Stack(); + public ProtectionSettingsStack(ConfuserContext context, Dictionary protections) { + this.context = context; + stack = new Stack>(); + parser = new ObfAttrParser(protections); } public ProtectionSettingsStack(ProtectionSettingsStack copy) { - stack = new Stack(copy.stack); + context = copy.context; + stack = new Stack>(copy.stack); + parser = copy.parser; } - public void Push(IEnumerable infos) { - stack.Push(infos.ToArray()); + void Pop() { + settings = stack.Pop().Item1; } - public void Pop() { - stack.Pop(); + public IDisposable Apply(IDnlibDef target, IEnumerable infos) { + ProtectionSettings settings; + if (this.settings == null) + settings = new ProtectionSettings(); + else + settings = new ProtectionSettings(this.settings); + + var infoArray = infos.ToArray(); + + if (stack.Count > 0) { + foreach (var i in stack.Skip(1).Reverse()) + ApplyInfo(target, settings, i.Item2.Where(info => info.Condition != null), false); + ApplyInfo(target, settings, stack.Peek().Item2, false); + } + + IDisposable result; + if (infoArray.Length != 0) { + var originalSettings = this.settings; + + // the settings that would apply to members + ApplyInfo(target, settings, infoArray, false); + this.settings = new ProtectionSettings(settings); + + // the settings that would apply to itself + ApplyInfo(target, settings, infoArray, true); + stack.Push(Tuple.Create(originalSettings, infoArray)); + + result = new PopHolder(this); + } + else + result = null; + + ProtectionParameters.SetParameters(context, target, settings); + return result; } - public IEnumerable GetInfos() { - return stack.Reverse().SelectMany(infos => infos); + void ApplyInfo(IDnlibDef context, ProtectionSettings settings, IEnumerable infos, bool current) { + foreach (var info in infos) { + if (info.Condition != null && !(bool)info.Condition.Evaluate(context)) + continue; + + if (info.Exclude) { + if (info.ApplyToMember) + settings.Clear(); + continue; + } + + if ((info.ApplyToMember || current || info.Condition != null) && !string.IsNullOrEmpty(info.Settings)) { + parser.ParseProtectionString(settings, info.Settings); + } + } + } + + public ProtectionSettings GetCurrentSettings() { + return new ProtectionSettings(settings); } } @@ -105,98 +175,91 @@ static IEnumerable ReadObfuscationAttributes(IHasCusto return ret; } - IEnumerable ProcessAttributes(IEnumerable attrs) { - bool hasAttr = false; - ProtectionSettingsInfo info; - - foreach (var attr in attrs) { - info = new ProtectionSettingsInfo(); + bool ToInfo(ObfuscationAttributeInfo attr, out ProtectionSettingsInfo info) { + info = new ProtectionSettingsInfo(); - info.Exclude = (attr.Exclude ?? true); - info.ApplyToMember = (attr.ApplyToMembers ?? true); - info.Settings = attr.FeatureValue; + info.Condition = null; - bool ok = true; - try { - new ObfAttrParser(protections).ParseProtectionString(null, info.Settings); - } - catch { - ok = false; - } + info.Exclude = (attr.Exclude ?? true); + info.ApplyToMember = (attr.ApplyToMembers ?? true); + info.Settings = attr.FeatureValue; - if (!ok) { - context.Logger.WarnFormat("Ignoring rule '{0}'.", info.Settings); - continue; - } - - if (!string.IsNullOrEmpty(attr.FeatureName)) - throw new ArgumentException("Feature name must not be set."); - if (info.Exclude && (!string.IsNullOrEmpty(attr.FeatureName) || !string.IsNullOrEmpty(attr.FeatureValue))) { - throw new ArgumentException("Feature property cannot be set when Exclude is true."); - } - yield return info; - hasAttr = true; + bool ok = true; + try { + new ObfAttrParser(protections).ParseProtectionString(null, info.Settings); + } + catch { + ok = false; } - if (!hasAttr) { - info = new ProtectionSettingsInfo(); + if (!ok) { + context.Logger.WarnFormat("Ignoring rule '{0}'.", info.Settings); + return false; + } - info.Exclude = false; - info.ApplyToMember = false; - info.Settings = ""; - yield return info; + if (!string.IsNullOrEmpty(attr.FeatureName)) + throw new ArgumentException("Feature name must not be set."); + if (info.Exclude && (!string.IsNullOrEmpty(attr.FeatureName) || !string.IsNullOrEmpty(attr.FeatureValue))) { + throw new ArgumentException("Feature property cannot be set when Exclude is true."); } + return true; } - void ApplySettings(IDnlibDef def, Rules rules, IEnumerable infos, ProtectionSettings settings = null) { - if (settings == null) - settings = new ProtectionSettings(); - else - settings = new ProtectionSettings(settings); - - ApplyRules(context, def, rules, settings); - settings = ProtectionParameters.GetParameters(context, def); - - ProtectionSettingsInfo? last = null; - var parser = new ObfAttrParser(protections); - foreach (var info in infos) { - if (info.Exclude) { - if (info.ApplyToMember) - settings.Clear(); - continue; + ProtectionSettingsInfo ToInfo(Rule rule, PatternExpression expr) { + var info = new ProtectionSettingsInfo(); + + info.Condition = expr; + + info.Exclude = false; + info.ApplyToMember = false; + + var settings = new StringBuilder(); + foreach (var item in rule) { + settings.Append(item.Action == SettingItemAction.Add ? '+' : '-'); + settings.Append(item.Id); + if (item.Count > 0) { + settings.Append('('); + int i = 0; + foreach (var arg in item) { + if (i != 0) + settings.Append(','); + settings.AppendFormat("{0}='{1}'", arg.Key, arg.Value.Replace("'", "\\'")); + i++; + } + settings.Append(')'); } + settings.Append(';'); + } + info.Settings = settings.ToString(); - last = info; + return info; + } - if (info.ApplyToMember && !string.IsNullOrEmpty(info.Settings)) { - parser.ParseProtectionString(settings, info.Settings); - } - } - if (last != null && !last.Value.ApplyToMember && - !string.IsNullOrEmpty(last.Value.Settings)) { - parser.ParseProtectionString(settings, last.Value.Settings); + IEnumerable ReadInfos(IHasCustomAttribute item) { + foreach (var attr in ReadObfuscationAttributes(item)) { + ProtectionSettingsInfo info; + if (ToInfo(attr, out info)) + yield return info; } } - static readonly Regex NSPattern = new Regex("namespace '([^']*)'"); - static readonly Regex NSInModulePattern = new Regex("namespace '([^']*)' in module '([^']*)'"); - - Dictionary>> crossModuleAttrs; ConfuserContext context; ConfuserProject project; Packer packer; Dictionary packerParams; List extModules; + static readonly object ModuleSettingsKey = new object(); + /// protected internal override void MarkMember(IDnlibDef member, ConfuserContext context) { ModuleDef module = ((IMemberRef)member).Module; - ProtectionParameters.SetParameters(context, member, ProtectionParameters.GetParameters(context, module)); + var settings = context.Annotations.Get(module, ModuleSettingsKey); + ProtectionParameters.SetParameters(context, member, new ProtectionSettings(settings)); } /// protected internal override MarkerResult MarkProject(ConfuserProject proj, ConfuserContext context) { - crossModuleAttrs = new Dictionary>>(); this.context = context; project = proj; extModules = new List(); @@ -243,47 +306,10 @@ protected internal override MarkerResult MarkProject(ConfuserProject proj, Confu return new MarkerResult(modules.Select(module => module.Item2).ToList(), packer, extModules); } - class RuleAdaptor : ProtectionSettings, IDictionary> { - Rule rule; - public RuleAdaptor(string pattern) { - this.rule = new Rule(pattern, ProtectionPreset.None, true); - } - - public Rule Rule { get { return rule; } } - - bool IDictionary>.ContainsKey(ConfuserComponent key) { - return true; - } - - void IDictionary>.Add(ConfuserComponent key, Dictionary value) { - var item = new SettingItem(key.Id, SettingItemAction.Add); - foreach (var entry in value) - item.Add(entry.Key, entry.Value); - rule.Add(item); - } - - bool IDictionary>.Remove(ConfuserComponent key) { - var item = new SettingItem(key.Id, SettingItemAction.Remove); - rule.Add(item); - return true; - } - - Dictionary IDictionary>.this[ConfuserComponent key] { - get { return null; } - set { - rule.RemoveWhere(i => i.Id == key.Id); - var item = new SettingItem(key.Id, SettingItemAction.Add); - foreach (var entry in value) - item.Add(entry.Key, entry.Value); - rule.Add(item); - } - } - } - - void AddRule(ObfuscationAttributeInfo attr, Rules rules) { - Debug.Assert(attr.FeatureName != null && attr.FeatureName.StartsWith("@")); + void AddRule(ObfuscationAttributeInfo attr, List infos) { + Debug.Assert(attr.FeatureName != null); - var pattern = attr.FeatureName.Substring(1); + var pattern = attr.FeatureName; PatternExpression expr; try { expr = new PatternParser().Parse(pattern); @@ -292,28 +318,42 @@ void AddRule(ObfuscationAttributeInfo attr, Rules rules) { throw new Exception("Error when parsing pattern " + pattern + " in ObfuscationAttribute", ex); } - var ruleAdaptor = new RuleAdaptor(pattern); + var info = new ProtectionSettingsInfo(); + info.Condition = expr; + + info.Exclude = (attr.Exclude ?? true); + info.ApplyToMember = (attr.ApplyToMembers ?? true); + info.Settings = attr.FeatureValue; + + bool ok = true; try { - new ObfAttrParser(protections).ParseProtectionString(ruleAdaptor, attr.FeatureValue); + new ObfAttrParser(protections).ParseProtectionString(null, info.Settings); } - catch (Exception ex) { - throw new Exception("Error when parsing rule " + attr.FeatureValue + " in ObfuscationAttribute", ex); + catch { + ok = false; } - rules.Add(ruleAdaptor.Rule, expr); + if (!ok) + context.Logger.WarnFormat("Ignoring rule '{0}'.", info.Settings); + else + infos.Add(info); } void MarkModule(ProjectModule projModule, ModuleDefMD module, Rules rules, bool isMain) { - var settingAttrs = new List(); string snKeyPath = projModule.SNKeyPath, snKeyPass = projModule.SNKeyPassword; - Dictionary> namespaceAttrs; - if (!crossModuleAttrs.TryGetValue(module.Name, out namespaceAttrs)) { - namespaceAttrs = new Dictionary>(); - } + var stack = new ProtectionSettingsStack(context, protections); + + var layer = new List(); + // Add rules + foreach (var rule in rules) + layer.Add(ToInfo(rule.Key, rule.Value)); + // Add obfuscation attributes foreach (var attr in ReadObfuscationAttributes(module.Assembly)) { if (string.IsNullOrEmpty(attr.FeatureName)) { - settingAttrs.Add(attr); + ProtectionSettingsInfo info; + if (ToInfo(attr, out info)) + layer.Add(info); } else if (attr.FeatureName.Equals("generate debug symbol", StringComparison.OrdinalIgnoreCase)) { if (!isMain) @@ -343,35 +383,7 @@ void MarkModule(ProjectModule projModule, ModuleDefMD module, Rules rules, bool extModules.Add(rawModule); } else { - if (attr.FeatureName.StartsWith("@")) { - AddRule(attr, rules); - continue; - } - - var match = NSInModulePattern.Match(attr.FeatureName); - if (match.Success) { - if (!isMain) - throw new ArgumentException("Only main module can set cross module obfuscation."); - var ns = TranslateNamespaceRegex(match.Groups[1].Value); - string targetModule = match.Groups[2].Value; - var x = attr; - x.FeatureName = ""; - Dictionary> targetModuleAttrs; - if (!crossModuleAttrs.TryGetValue(targetModule, out targetModuleAttrs)) { - targetModuleAttrs = new Dictionary>(); - crossModuleAttrs[targetModule] = targetModuleAttrs; - } - targetModuleAttrs.AddListEntry(ns, x); - } - else { - match = NSPattern.Match(attr.FeatureName); - if (match.Success) { - var ns = TranslateNamespaceRegex(match.Groups[1].Value); - var x = attr; - x.FeatureName = ""; - namespaceAttrs.AddListEntry(ns, x); - } - } + AddRule(attr, layer); } } @@ -379,118 +391,73 @@ void MarkModule(ProjectModule projModule, ModuleDefMD module, Rules rules, bool module.LoadPdb(); } - ProcessModule(module, rules, snKeyPath, snKeyPass, settingAttrs, namespaceAttrs); - } + snKeyPath = snKeyPath == null ? null : Path.Combine(project.BaseDirectory, snKeyPath); + var snKey = LoadSNKey(context, snKeyPath, snKeyPass); + context.Annotations.Set(module, SNKey, snKey); - static Regex TranslateNamespaceRegex(string ns) { - if (ns == "*") - return new Regex(".*"); - if (ns.Length >= 2 && ns[0] == '*' && ns[ns.Length - 1] == '*') - return new Regex(Regex.Escape(ns.Substring(1, ns.Length - 2))); - if (ns.Length >= 1 && ns[0] == '*') - return new Regex(Regex.Escape(ns.Substring(1)) + "$"); - if (ns.Length >= 1 && ns[ns.Length - 1] == '*') - return new Regex(Regex.Escape("^" + ns.Substring(0, ns.Length - 1))); - return new Regex("^" + Regex.Escape(ns) + "$"); + using (stack.Apply(module, layer)) + ProcessModule(module, stack); } - static ProtectionSettingsStack MatchNamespace(Dictionary attrs, string ns) { - foreach (var nsStack in attrs) { - if (nsStack.Key.IsMatch(ns)) - return nsStack.Value; - } - return null; - } - - void ProcessModule(ModuleDefMD module, Rules rules, string snKeyPath, string snKeyPass, - List settingAttrs, - Dictionary> namespaceAttrs) { - context.Annotations.Set(module, SNKey, LoadSNKey(context, snKeyPath == null ? null : Path.Combine(project.BaseDirectory, snKeyPath), snKeyPass)); - - var moduleStack = new ProtectionSettingsStack(); - moduleStack.Push(ProcessAttributes(settingAttrs)); - ApplySettings(module, rules, moduleStack.GetInfos()); - - var nsSettings = namespaceAttrs.ToDictionary(kvp => kvp.Key, kvp => { - var nsStack = new ProtectionSettingsStack(moduleStack); - nsStack.Push(ProcessAttributes(kvp.Value)); - return nsStack; - }); - + void ProcessModule(ModuleDefMD module, ProtectionSettingsStack stack) { + context.Annotations.Set(module, ModuleSettingsKey, stack.GetCurrentSettings()); foreach (var type in module.Types) { - var typeStack = MatchNamespace(nsSettings, type.Namespace) ?? moduleStack; - typeStack.Push(ProcessAttributes(ReadObfuscationAttributes(type))); - - ApplySettings(type, rules, typeStack.GetInfos()); - ProcessTypeMembers(type, rules, typeStack); - - typeStack.Pop(); + using (stack.Apply(type, ReadInfos(type))) + ProcessTypeMembers(type, stack); } } - void ProcessTypeMembers(TypeDef type, Rules rules, ProtectionSettingsStack stack) { + void ProcessTypeMembers(TypeDef type, ProtectionSettingsStack stack) { foreach (var nestedType in type.NestedTypes) { - stack.Push(ProcessAttributes(ReadObfuscationAttributes(nestedType))); - - ApplySettings(nestedType, rules, stack.GetInfos()); - ProcessTypeMembers(nestedType, rules, stack); - - stack.Pop(); + using (stack.Apply(nestedType, ReadInfos(nestedType))) + ProcessTypeMembers(nestedType, stack); } - foreach (var prop in type.Properties) { - stack.Push(ProcessAttributes(ReadObfuscationAttributes(prop))); + foreach (var property in type.Properties) { + using (stack.Apply(property, ReadInfos(property))) { + if (property.GetMethod != null) + ProcessMember(property.GetMethod, stack); - ApplySettings(prop, rules, stack.GetInfos()); - if (prop.GetMethod != null) { - ProcessMember(prop.GetMethod, rules, stack); - } - if (prop.SetMethod != null) { - ProcessMember(prop.SetMethod, rules, stack); - } - foreach (var m in prop.OtherMethods) - ProcessMember(m, rules, stack); + if (property.SetMethod != null) + ProcessMember(property.SetMethod, stack); - stack.Pop(); + foreach (var m in property.OtherMethods) + ProcessMember(m, stack); + } } foreach (var evt in type.Events) { - stack.Push(ProcessAttributes(ReadObfuscationAttributes(evt))); + using (stack.Apply(evt, ReadInfos(evt))) { + if (evt.AddMethod != null) + ProcessMember(evt.AddMethod, stack); - ApplySettings(evt, rules, stack.GetInfos()); - if (evt.AddMethod != null) { - ProcessMember(evt.AddMethod, rules, stack); - } - if (evt.RemoveMethod != null) { - ProcessMember(evt.RemoveMethod, rules, stack); - } - if (evt.InvokeMethod != null) { - ProcessMember(evt.InvokeMethod, rules, stack); - } - foreach (var m in evt.OtherMethods) - ProcessMember(m, rules, stack); + if (evt.RemoveMethod != null) + ProcessMember(evt.RemoveMethod, stack); - stack.Pop(); + if (evt.InvokeMethod != null) + ProcessMember(evt.InvokeMethod, stack); + + foreach (var m in evt.OtherMethods) + ProcessMember(m, stack); + } } foreach (var method in type.Methods) { if (method.SemanticsAttributes == 0) - ProcessMember(method, rules, stack); + ProcessMember(method, stack); } foreach (var field in type.Fields) { - ProcessMember(field, rules, stack); + ProcessMember(field, stack); } } - void ProcessMember(IDnlibDef member, Rules rules, ProtectionSettingsStack stack) { - stack.Push(ProcessAttributes(ReadObfuscationAttributes(member))); - ApplySettings(member, rules, stack.GetInfos()); - ProcessBody(member as MethodDef, rules, stack); - stack.Pop(); + void ProcessMember(IDnlibDef member, ProtectionSettingsStack stack) { + using (stack.Apply(member, ReadInfos(member))) + ProcessBody(member as MethodDef, stack); } - void ProcessBody(MethodDef method, Rules rules, ProtectionSettingsStack stack) { + void ProcessBody(MethodDef method, ProtectionSettingsStack stack) { if (method == null || method.Body == null) return; @@ -499,8 +466,8 @@ void ProcessBody(MethodDef method, Rules rules, ProtectionSettingsStack stack) { if (instr.Operand is MethodDef) { var cgType = ((MethodDef)instr.Operand).DeclaringType; if (cgType.DeclaringType == declType && cgType.IsCompilerGenerated()) { - ApplySettings(cgType, rules, stack.GetInfos()); - ProcessTypeMembers(cgType, rules, stack); + using (stack.Apply(cgType, ReadInfos(cgType))) + ProcessTypeMembers(cgType, stack); } } }