531 строка
17 KiB
C#
531 строка
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Confuser.Core.Project;
|
|
using Confuser.Core.Project.Patterns;
|
|
using dnlib.DotNet;
|
|
|
|
namespace Confuser.Core {
|
|
using Rules = Dictionary<Rule, PatternExpression>;
|
|
|
|
/// <summary>
|
|
/// Obfuscation Attribute Marker
|
|
/// </summary>
|
|
public class ObfAttrMarker : Marker {
|
|
struct ObfuscationAttributeInfo {
|
|
public IHasCustomAttribute Owner;
|
|
public bool? ApplyToMembers;
|
|
public bool? Exclude;
|
|
public string FeatureName;
|
|
public string FeatureValue;
|
|
}
|
|
|
|
struct ProtectionSettingsInfo {
|
|
public bool ApplyToMember;
|
|
public bool Exclude;
|
|
|
|
public PatternExpression Condition;
|
|
public string Settings;
|
|
}
|
|
|
|
class ProtectionSettingsStack {
|
|
readonly ConfuserContext context;
|
|
readonly Stack<Tuple<ProtectionSettings, ProtectionSettingsInfo[]>> stack;
|
|
readonly ObfAttrParser parser;
|
|
ProtectionSettings settings;
|
|
|
|
enum ApplyInfoType {
|
|
CurrentInfoOnly,
|
|
CurrentInfoInherits,
|
|
ParentInfo
|
|
}
|
|
|
|
class PopHolder : IDisposable {
|
|
ProtectionSettingsStack parent;
|
|
|
|
public PopHolder(ProtectionSettingsStack parent) {
|
|
this.parent = parent;
|
|
}
|
|
|
|
public void Dispose() {
|
|
parent.Pop();
|
|
}
|
|
}
|
|
|
|
public ProtectionSettingsStack(ConfuserContext context, Dictionary<string, Protection> protections) {
|
|
this.context = context;
|
|
stack = new Stack<Tuple<ProtectionSettings, ProtectionSettingsInfo[]>>();
|
|
parser = new ObfAttrParser(protections);
|
|
}
|
|
|
|
public ProtectionSettingsStack(ProtectionSettingsStack copy) {
|
|
context = copy.context;
|
|
stack = new Stack<Tuple<ProtectionSettings, ProtectionSettingsInfo[]>>(copy.stack);
|
|
parser = copy.parser;
|
|
}
|
|
|
|
void Pop() {
|
|
settings = stack.Pop().Item1;
|
|
}
|
|
|
|
public IDisposable Apply(IDnlibDef target, IEnumerable<ProtectionSettingsInfo> 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.Reverse())
|
|
ApplyInfo(target, settings, i.Item2, ApplyInfoType.ParentInfo);
|
|
}
|
|
|
|
IDisposable result;
|
|
if (infoArray.Length != 0) {
|
|
var originalSettings = this.settings;
|
|
|
|
// the settings that would apply to members
|
|
ApplyInfo(target, settings, infoArray, ApplyInfoType.CurrentInfoInherits);
|
|
this.settings = new ProtectionSettings(settings);
|
|
|
|
// the settings that would apply to itself
|
|
ApplyInfo(target, settings, infoArray, ApplyInfoType.CurrentInfoOnly);
|
|
stack.Push(Tuple.Create(originalSettings, infoArray));
|
|
|
|
result = new PopHolder(this);
|
|
}
|
|
else
|
|
result = null;
|
|
|
|
ProtectionParameters.SetParameters(context, target, settings);
|
|
return result;
|
|
}
|
|
|
|
void ApplyInfo(IDnlibDef context, ProtectionSettings settings, IEnumerable<ProtectionSettingsInfo> infos, ApplyInfoType type) {
|
|
foreach (var info in infos) {
|
|
if (info.Condition != null && !(bool)info.Condition.Evaluate(context))
|
|
continue;
|
|
|
|
if (info.Condition == null && info.Exclude) {
|
|
if (type == ApplyInfoType.CurrentInfoOnly ||
|
|
(type == ApplyInfoType.CurrentInfoInherits && info.ApplyToMember)) {
|
|
settings.Clear();
|
|
}
|
|
}
|
|
if (!string.IsNullOrEmpty(info.Settings)) {
|
|
if ((type == ApplyInfoType.ParentInfo && info.ApplyToMember) ||
|
|
type == ApplyInfoType.CurrentInfoOnly ||
|
|
(type == ApplyInfoType.CurrentInfoInherits && info.Condition == null && info.ApplyToMember)) {
|
|
parser.ParseProtectionString(settings, info.Settings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static readonly Regex OrderPattern = new Regex("^(\\d+)\\. (.+)$");
|
|
|
|
static IEnumerable<ObfuscationAttributeInfo> ReadObfuscationAttributes(IHasCustomAttribute item) {
|
|
var ret = new List<Tuple<int?, ObfuscationAttributeInfo>>();
|
|
for (int i = item.CustomAttributes.Count - 1; i >= 0; i--) {
|
|
var ca = item.CustomAttributes[i];
|
|
if (ca.TypeFullName != "System.Reflection.ObfuscationAttribute")
|
|
continue;
|
|
|
|
var info = new ObfuscationAttributeInfo();
|
|
int? order = null;
|
|
|
|
info.Owner = item;
|
|
bool strip = true;
|
|
foreach (var prop in ca.Properties) {
|
|
switch (prop.Name) {
|
|
case "ApplyToMembers":
|
|
Debug.Assert(prop.Type.ElementType == ElementType.Boolean);
|
|
info.ApplyToMembers = (bool)prop.Value;
|
|
break;
|
|
|
|
case "Exclude":
|
|
Debug.Assert(prop.Type.ElementType == ElementType.Boolean);
|
|
info.Exclude = (bool)prop.Value;
|
|
break;
|
|
|
|
case "StripAfterObfuscation":
|
|
Debug.Assert(prop.Type.ElementType == ElementType.Boolean);
|
|
strip = (bool)prop.Value;
|
|
break;
|
|
|
|
case "Feature":
|
|
Debug.Assert(prop.Type.ElementType == ElementType.String);
|
|
string feature = (UTF8String)prop.Value;
|
|
|
|
var match = OrderPattern.Match(feature);
|
|
if (match.Success) {
|
|
var orderStr = match.Groups[1].Value;
|
|
var f = match.Groups[2].Value;
|
|
int o;
|
|
if (!int.TryParse(orderStr, out o))
|
|
throw new NotSupportedException(string.Format("Failed to parse feature '{0}' in {1} ", feature, item));
|
|
order = o;
|
|
feature = f;
|
|
}
|
|
|
|
int sepIndex = feature.IndexOf(':');
|
|
if (sepIndex == -1) {
|
|
info.FeatureName = "";
|
|
info.FeatureValue = feature;
|
|
}
|
|
else {
|
|
info.FeatureName = feature.Substring(0, sepIndex);
|
|
info.FeatureValue = feature.Substring(sepIndex + 1);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new NotSupportedException("Unsupported property: " + prop.Name);
|
|
}
|
|
}
|
|
if (strip)
|
|
item.CustomAttributes.RemoveAt(i);
|
|
|
|
if (item is IMemberRef && !(item is ITypeDefOrRef))
|
|
info.ApplyToMembers = false;
|
|
|
|
ret.Add(Tuple.Create(order, info));
|
|
}
|
|
ret.Reverse();
|
|
return ret.OrderBy(pair => pair.Item1).Select(pair => pair.Item2);
|
|
}
|
|
|
|
bool ToInfo(ObfuscationAttributeInfo attr, out ProtectionSettingsInfo info) {
|
|
info = new ProtectionSettingsInfo();
|
|
|
|
info.Condition = null;
|
|
|
|
info.Exclude = (attr.Exclude ?? true);
|
|
info.ApplyToMember = (attr.ApplyToMembers ?? true);
|
|
info.Settings = attr.FeatureValue;
|
|
|
|
bool ok = true;
|
|
try {
|
|
new ObfAttrParser(protections).ParseProtectionString(null, info.Settings);
|
|
}
|
|
catch {
|
|
ok = false;
|
|
}
|
|
|
|
if (!ok) {
|
|
context.Logger.WarnFormat("Ignoring rule '{0}' in {1}.", info.Settings, attr.Owner);
|
|
return false;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(attr.FeatureName))
|
|
throw new ArgumentException("Feature name must not be set. Owner=" + attr.Owner);
|
|
if (info.Exclude && (!string.IsNullOrEmpty(attr.FeatureName) || !string.IsNullOrEmpty(attr.FeatureValue))) {
|
|
throw new ArgumentException("Feature property cannot be set when Exclude is true. Owner=" + attr.Owner);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ProtectionSettingsInfo ToInfo(Rule rule, PatternExpression expr) {
|
|
var info = new ProtectionSettingsInfo();
|
|
|
|
info.Condition = expr;
|
|
|
|
info.Exclude = false;
|
|
info.ApplyToMember = true;
|
|
|
|
var settings = new StringBuilder();
|
|
if (rule.Preset != ProtectionPreset.None)
|
|
settings.AppendFormat("preset({0});", rule.Preset.ToString().ToLowerInvariant());
|
|
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();
|
|
|
|
return info;
|
|
}
|
|
|
|
IEnumerable<ProtectionSettingsInfo> ReadInfos(IHasCustomAttribute item) {
|
|
foreach (var attr in ReadObfuscationAttributes(item)) {
|
|
ProtectionSettingsInfo info;
|
|
if (!string.IsNullOrEmpty(attr.FeatureName))
|
|
yield return AddRule(attr, null);
|
|
else if (ToInfo(attr, out info))
|
|
yield return info;
|
|
}
|
|
}
|
|
|
|
ConfuserContext context;
|
|
ConfuserProject project;
|
|
Packer packer;
|
|
Dictionary<string, string> packerParams;
|
|
List<byte[]> extModules;
|
|
|
|
static readonly object ModuleSettingsKey = new object();
|
|
|
|
/// <inheritdoc />
|
|
protected internal override void MarkMember(IDnlibDef member, ConfuserContext context) {
|
|
ModuleDef module = ((IMemberRef)member).Module;
|
|
var stack = context.Annotations.Get<ProtectionSettingsStack>(module, ModuleSettingsKey);
|
|
using (stack.Apply(member, Enumerable.Empty<ProtectionSettingsInfo>()))
|
|
return;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected internal override MarkerResult MarkProject(ConfuserProject proj, ConfuserContext context) {
|
|
this.context = context;
|
|
project = proj;
|
|
extModules = new List<byte[]>();
|
|
|
|
if (proj.Packer != null) {
|
|
if (!packers.ContainsKey(proj.Packer.Id)) {
|
|
context.Logger.ErrorFormat("Cannot find packer with ID '{0}'.", proj.Packer.Id);
|
|
throw new ConfuserException(null);
|
|
}
|
|
|
|
packer = packers[proj.Packer.Id];
|
|
packerParams = new Dictionary<string, string>(proj.Packer, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
var modules = new List<Tuple<ProjectModule, ModuleDefMD>>();
|
|
foreach (ProjectModule module in proj) {
|
|
if (module.IsExternal) {
|
|
extModules.Add(module.LoadRaw(proj.BaseDirectory));
|
|
continue;
|
|
}
|
|
|
|
ModuleDefMD modDef = module.Resolve(proj.BaseDirectory, context.Resolver.DefaultModuleContext);
|
|
context.CheckCancellation();
|
|
|
|
context.Resolver.AddToCache(modDef);
|
|
modules.Add(Tuple.Create(module, modDef));
|
|
}
|
|
foreach (var module in modules) {
|
|
context.Logger.InfoFormat("Loading '{0}'...", module.Item1.Path);
|
|
|
|
Rules rules = ParseRules(proj, module.Item1, context);
|
|
MarkModule(module.Item1, module.Item2, rules, module == modules[0]);
|
|
|
|
context.Annotations.Set(module.Item2, RulesKey, rules);
|
|
|
|
// Packer parameters are stored in modules
|
|
if (packer != null)
|
|
ProtectionParameters.GetParameters(context, module.Item2)[packer] = packerParams;
|
|
}
|
|
|
|
if (proj.Debug && proj.Packer != null)
|
|
context.Logger.Warn("Generated Debug symbols might not be usable with packers!");
|
|
|
|
return new MarkerResult(modules.Select(module => module.Item2).ToList(), packer, extModules);
|
|
}
|
|
|
|
ProtectionSettingsInfo AddRule(ObfuscationAttributeInfo attr, List<ProtectionSettingsInfo> infos) {
|
|
Debug.Assert(attr.FeatureName != null);
|
|
|
|
var pattern = attr.FeatureName;
|
|
PatternExpression expr;
|
|
try {
|
|
expr = new PatternParser().Parse(pattern);
|
|
}
|
|
catch (Exception ex) {
|
|
throw new Exception("Error when parsing pattern " + pattern + " in ObfuscationAttribute. Owner=" + attr.Owner, ex);
|
|
}
|
|
|
|
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(null, info.Settings);
|
|
}
|
|
catch {
|
|
ok = false;
|
|
}
|
|
|
|
if (!ok)
|
|
context.Logger.WarnFormat("Ignoring rule '{0}' in {1}.", info.Settings, attr.Owner);
|
|
else if (infos != null)
|
|
infos.Add(info);
|
|
return info;
|
|
}
|
|
|
|
void MarkModule(ProjectModule projModule, ModuleDefMD module, Rules rules, bool isMain) {
|
|
string snKeyPath = projModule.SNKeyPath;
|
|
string snKeyPass = projModule.SNKeyPassword;
|
|
string snPubKeyPath = projModule.SNPubKeyPath;
|
|
bool snDelaySig = projModule.SNDelaySig;
|
|
string snSigKeyPath = projModule.SNSigKeyPath;
|
|
string snSigKeyPass = projModule.SNSigKeyPassword;
|
|
string snPubSigKeyPath = projModule.SNPubSigKeyPath;
|
|
|
|
var stack = new ProtectionSettingsStack(context, protections);
|
|
|
|
var layer = new List<ProtectionSettingsInfo>();
|
|
// 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)) {
|
|
ProtectionSettingsInfo info;
|
|
if (ToInfo(attr, out info))
|
|
layer.Add(info);
|
|
}
|
|
else if (attr.FeatureName.Equals("generate debug symbol", StringComparison.OrdinalIgnoreCase)) {
|
|
if (!isMain)
|
|
throw new ArgumentException("Only main module can set 'generate debug symbol'.");
|
|
project.Debug = bool.Parse(attr.FeatureValue);
|
|
}
|
|
else if (attr.FeatureName.Equals("random seed", StringComparison.OrdinalIgnoreCase)) {
|
|
if (!isMain)
|
|
throw new ArgumentException("Only main module can set 'random seed'.");
|
|
project.Seed = attr.FeatureValue;
|
|
}
|
|
else if (attr.FeatureName.Equals("strong name key", StringComparison.OrdinalIgnoreCase)) {
|
|
snKeyPath = Path.Combine(project.BaseDirectory, attr.FeatureValue);
|
|
}
|
|
else if (attr.FeatureName.Equals("strong name key password", StringComparison.OrdinalIgnoreCase)) {
|
|
snKeyPass = attr.FeatureValue;
|
|
}
|
|
else if (attr.FeatureName.Equals("packer", StringComparison.OrdinalIgnoreCase)) {
|
|
if (!isMain)
|
|
throw new ArgumentException("Only main module can set 'packer'.");
|
|
new ObfAttrParser(packers).ParsePackerString(attr.FeatureValue, out packer, out packerParams);
|
|
}
|
|
else if (attr.FeatureName.Equals("external module", StringComparison.OrdinalIgnoreCase)) {
|
|
if (!isMain)
|
|
throw new ArgumentException("Only main module can add external modules.");
|
|
var rawModule = new ProjectModule { Path = attr.FeatureValue }.LoadRaw(project.BaseDirectory);
|
|
extModules.Add(rawModule);
|
|
}
|
|
else {
|
|
AddRule(attr, layer);
|
|
}
|
|
}
|
|
|
|
if (project.Debug && module.PdbState == null) {
|
|
module.LoadPdb();
|
|
}
|
|
|
|
snKeyPath = snKeyPath == null ? null : Path.Combine(project.BaseDirectory, snKeyPath);
|
|
snPubKeyPath = snPubKeyPath == null ? null : Path.Combine(project.BaseDirectory, snPubKeyPath);
|
|
snSigKeyPath = snSigKeyPath == null ? null : Path.Combine(project.BaseDirectory, snSigKeyPath);
|
|
snPubSigKeyPath = snPubSigKeyPath == null ? null : Path.Combine(project.BaseDirectory, snPubSigKeyPath);
|
|
|
|
var snKey = LoadSNKey(context, snKeyPath, snKeyPass);
|
|
context.Annotations.Set(module, SNKey, snKey);
|
|
|
|
var snPubKey = LoadSNPubKey(context, snPubKeyPath);
|
|
context.Annotations.Set(module, SNPubKey, snPubKey);
|
|
|
|
context.Annotations.Set(module, SNDelaySig, snDelaySig);
|
|
|
|
var snSigKey = LoadSNKey(context, snSigKeyPath, snSigKeyPass);
|
|
context.Annotations.Set(module, SNSigKey, snSigKey);
|
|
|
|
var snSigPubKey = LoadSNPubKey(context, snPubSigKeyPath);
|
|
context.Annotations.Set(module, SNSigPubKey, snSigPubKey);
|
|
|
|
using (stack.Apply(module, layer))
|
|
ProcessModule(module, stack);
|
|
}
|
|
|
|
void ProcessModule(ModuleDefMD module, ProtectionSettingsStack stack) {
|
|
context.Annotations.Set(module, ModuleSettingsKey, new ProtectionSettingsStack(stack));
|
|
foreach (var type in module.Types) {
|
|
using (stack.Apply(type, ReadInfos(type)))
|
|
ProcessTypeMembers(type, stack);
|
|
}
|
|
}
|
|
|
|
void ProcessTypeMembers(TypeDef type, ProtectionSettingsStack stack) {
|
|
foreach (var nestedType in type.NestedTypes) {
|
|
using (stack.Apply(nestedType, ReadInfos(nestedType)))
|
|
ProcessTypeMembers(nestedType, stack);
|
|
}
|
|
|
|
foreach (var property in type.Properties) {
|
|
using (stack.Apply(property, ReadInfos(property))) {
|
|
if (property.GetMethod != null)
|
|
ProcessMember(property.GetMethod, stack);
|
|
|
|
if (property.SetMethod != null)
|
|
ProcessMember(property.SetMethod, stack);
|
|
|
|
foreach (var m in property.OtherMethods)
|
|
ProcessMember(m, stack);
|
|
}
|
|
}
|
|
|
|
foreach (var evt in type.Events) {
|
|
using (stack.Apply(evt, ReadInfos(evt))) {
|
|
if (evt.AddMethod != null)
|
|
ProcessMember(evt.AddMethod, stack);
|
|
|
|
if (evt.RemoveMethod != null)
|
|
ProcessMember(evt.RemoveMethod, stack);
|
|
|
|
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, stack);
|
|
}
|
|
|
|
foreach (var field in type.Fields) {
|
|
ProcessMember(field, stack);
|
|
}
|
|
}
|
|
|
|
void ProcessMember(IDnlibDef member, ProtectionSettingsStack stack) {
|
|
using (stack.Apply(member, ReadInfos(member)))
|
|
ProcessBody(member as MethodDef, stack);
|
|
}
|
|
|
|
void ProcessBody(MethodDef method, ProtectionSettingsStack stack) {
|
|
if (method == null || method.Body == null)
|
|
return;
|
|
|
|
var declType = method.DeclaringType;
|
|
foreach (var instr in method.Body.Instructions)
|
|
if (instr.Operand is MethodDef) {
|
|
var cgType = ((MethodDef)instr.Operand).DeclaringType;
|
|
if (cgType.DeclaringType == declType && cgType.IsCompilerGenerated()) {
|
|
using (stack.Apply(cgType, ReadInfos(cgType)))
|
|
ProcessTypeMembers(cgType, stack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|