Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 246 additions & 13 deletions src/BitMono.Core/Analyzing/ReflectionCriticalAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,49 @@ public class ReflectionCriticalAnalyzer : ICriticalAnalyzer<MethodDefinition>
{
private readonly ObfuscationSettings _obfuscationSettings;
private readonly List<MethodDefinition> _cachedMethods;
private readonly List<FieldDefinition> _cachedFields;
private readonly List<PropertyDefinition> _cachedProperties;
private readonly List<EventDefinition> _cachedEvents;
private readonly List<TypeDefinition> _cachedTypes;
private static readonly string[] ReflectionMethods =
[
nameof(Type.GetMethod),
nameof(Type.GetField),
nameof(Type.GetProperty),
nameof(Type.GetEvent),
nameof(Type.GetMember)
nameof(Type.GetMember),
nameof(Type.GetTypeFromHandle)
];

public ReflectionCriticalAnalyzer(IOptions<ObfuscationSettings> obfuscation)
{
_obfuscationSettings = obfuscation.Value;
_cachedMethods = [];
_cachedFields = [];
_cachedProperties = [];
_cachedEvents = [];
_cachedTypes = [];
}

public IReadOnlyList<MethodDefinition> CachedMethods => _cachedMethods.AsReadOnly();
public IReadOnlyList<FieldDefinition> CachedFields => _cachedFields.AsReadOnly();
public IReadOnlyList<PropertyDefinition> CachedProperties => _cachedProperties.AsReadOnly();
public IReadOnlyList<EventDefinition> CachedEvents => _cachedEvents.AsReadOnly();
public IReadOnlyList<TypeDefinition> CachedTypes => _cachedTypes.AsReadOnly();

public bool NotCriticalToMakeChanges(MethodDefinition method)
{
if (_obfuscationSettings.ReflectionMembersObfuscationExclude == false)
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
{
return true;
}
if (_cachedMethods.FirstOrDefault(x => x.Name.Equals(method.Name)) != null)
{
return false;
}

bool foundReflection = false;

if (method.CilMethodBody is { } body)
{
body.ConstructSymbolicFlowGraph(out var dataFlowGraph);
Expand All @@ -46,40 +62,257 @@ public bool NotCriticalToMakeChanges(MethodDefinition method)
{
if (IsReflection(calledMethod))
{
var traceArgument = TraceLdstrArgument(body, instruction);
if (traceArgument?.Operand is string traceMethodName)
var traceArgument = TraceStringArgument(body, instruction);
if (traceArgument?.Operand is string memberName)
{
foreach (var possibleMethod in method.DeclaringModule
.FindMembers()
.OfType<MethodDefinition>()
.Where(x => x.Name.Equals(traceMethodName)))
var module = method.DeclaringModule;
var allMembers = module.FindMembers();

switch (calledMethod.Name.Value)
{
_cachedMethods.Add(possibleMethod);
return false;
case nameof(Type.GetMethod):
foreach (var possibleMethod in allMembers.OfType<MethodDefinition>()
.Where(x => x.Name.Equals(memberName)))
{
if (possibleMethod == method && !_cachedMethods.Contains(possibleMethod))
{
_cachedMethods.Add(possibleMethod);
foundReflection = true;
}
}
break;

case nameof(Type.GetField):
foreach (var possibleField in allMembers.OfType<FieldDefinition>()
.Where(x => x.Name.Equals(memberName)))
{
_cachedFields.Add(possibleField);
foundReflection = true;
}
break;

case nameof(Type.GetProperty):
foreach (var possibleProperty in allMembers.OfType<PropertyDefinition>()
.Where(x => x.Name.Equals(memberName)))
{
_cachedProperties.Add(possibleProperty);
foundReflection = true;
}
break;

case nameof(Type.GetEvent):
foreach (var possibleEvent in allMembers.OfType<EventDefinition>()
.Where(x => x.Name.Equals(memberName)))
{
_cachedEvents.Add(possibleEvent);
foundReflection = true;
}
break;

case nameof(Type.GetMember):
foreach (var possibleMember in allMembers)
{
string? memberNameToCheck = possibleMember switch
{
MethodDefinition m => m.Name,
FieldDefinition f => f.Name,
PropertyDefinition p => p.Name,
EventDefinition e => e.Name,
TypeDefinition t => t.Name,
_ => null
};

if (memberNameToCheck != null && memberNameToCheck.Equals(memberName))
{
switch (possibleMember)
{
case MethodDefinition m:
_cachedMethods.Add(m);
break;
case FieldDefinition f:
_cachedFields.Add(f);
break;
case PropertyDefinition p:
_cachedProperties.Add(p);
break;
case EventDefinition e:
_cachedEvents.Add(e);
break;
case TypeDefinition t:
_cachedTypes.Add(t);
break;
}
foundReflection = true;
}
}
break;
}
}
}
else if (IsTypeGetTypeFromHandle(calledMethod))
{
var typeFromHandle = TraceTypeFromHandle(body, instruction);
if (typeFromHandle != null)
{
_cachedTypes.Add(typeFromHandle);
foundReflection = true;
}
}
}
else if (instruction?.OpCode == CilOpCodes.Ldtoken && instruction.Operand is ITypeDefOrRef typeRef)
{
if (typeRef.Resolve() is TypeDefinition typeDef)
{
_cachedTypes.Add(typeDef);
foundReflection = true;
}
}
}
}
}
return true;

return !foundReflection;
}

public bool NotCriticalToMakeChanges(FieldDefinition field)
{
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
{
return true;
}
return _cachedFields.FirstOrDefault(x => x.Name.Equals(field.Name)) != null;
}

public bool NotCriticalToMakeChanges(PropertyDefinition property)
{
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
{
return true;
}
return _cachedProperties.FirstOrDefault(x => x.Name.Equals(property.Name)) != null;
}

public bool NotCriticalToMakeChanges(EventDefinition eventDef)
{
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
{
return true;
}
return _cachedEvents.FirstOrDefault(x => x.Name.Equals(eventDef.Name)) != null;
}

public bool NotCriticalToMakeChanges(TypeDefinition type)
{
if (!_obfuscationSettings.ReflectionMembersObfuscationExclude)
{
return true;
}
return _cachedTypes.FirstOrDefault(x => x.Name.Equals(type.Name)) != null;
}

private static bool IsReflection(IMethodDefOrRef calledMethod)
{
return calledMethod.DeclaringType.IsSystemType() &&
ReflectionMethods.Contains(calledMethod.Name.Value);
}
private static CilInstruction? TraceLdstrArgument(CilMethodBody body, CilInstruction instruction)

private static bool IsTypeGetTypeFromHandle(IMethodDefOrRef calledMethod)
{
for (var i = body.Instructions.IndexOf(instruction); i > 0 && body.Instructions.Count.IsLess(i) == false; i--)
return calledMethod.DeclaringType.IsSystemType() &&
calledMethod.Name.Value == nameof(Type.GetTypeFromHandle);
}

private static TypeDefinition? TraceTypeFromHandle(CilMethodBody body, CilInstruction instruction)
{
var callIndex = body.Instructions.IndexOf(instruction);
if (callIndex <= 0) return null;

for (var i = callIndex - 1; i >= 0; i--)
{
var prevInstruction = body.Instructions[i];
if (prevInstruction.OpCode == CilOpCodes.Ldtoken && prevInstruction.Operand is ITypeDefOrRef typeRef)
{
return typeRef.Resolve();
}
}
return null;
}
private static CilInstruction? TraceStringArgument(CilMethodBody body, CilInstruction instruction)
{
return TraceStringArgumentSimple(body, instruction);
}

private static CilInstruction? TraceStringArgumentSimple(CilMethodBody body, CilInstruction instruction)
{
var callIndex = body.Instructions.IndexOf(instruction);
if (callIndex <= 0) return null;

for (var i = callIndex - 1; i >= 0; i--)
{
var previousInstruction = body.Instructions[i];

if (previousInstruction.OpCode == CilOpCodes.Ldstr)
{
return previousInstruction;
}

if (previousInstruction.OpCode == CilOpCodes.Ldloc_0 ||
previousInstruction.OpCode == CilOpCodes.Ldloc_1 ||
previousInstruction.OpCode == CilOpCodes.Ldloc_2 ||
previousInstruction.OpCode == CilOpCodes.Ldloc_3 ||
previousInstruction.OpCode == CilOpCodes.Ldloc_S ||
previousInstruction.OpCode == CilOpCodes.Ldloc)
{
var variableIndex = GetVariableIndex(previousInstruction);
if (variableIndex >= 0)
{
var stringInstruction = FindStringAssignmentToVariable(body, variableIndex, i);
if (stringInstruction != null)
{
return stringInstruction;
}
}
}
}
return null;
}

private static int GetVariableIndex(CilInstruction instruction)
{
return instruction.OpCode.Code switch
{
CilCode.Ldloc_0 => 0,
CilCode.Ldloc_1 => 1,
CilCode.Ldloc_2 => 2,
CilCode.Ldloc_3 => 3,
CilCode.Ldloc_S => ((CilLocalVariable)instruction.Operand!).Index,
CilCode.Ldloc => ((CilLocalVariable)instruction.Operand!).Index,
_ => -1
};
}

private static CilInstruction? FindStringAssignmentToVariable(CilMethodBody body, int variableIndex, int maxIndex)
{
for (var i = 0; i < maxIndex; i++)
{
var instruction = body.Instructions[i];

if ((instruction.OpCode == CilOpCodes.Stloc_0 && variableIndex == 0) ||
(instruction.OpCode == CilOpCodes.Stloc_1 && variableIndex == 1) ||
(instruction.OpCode == CilOpCodes.Stloc_2 && variableIndex == 2) ||
(instruction.OpCode == CilOpCodes.Stloc_3 && variableIndex == 3) ||
(instruction.OpCode == CilOpCodes.Stloc_S && ((CilLocalVariable)instruction.Operand!).Index == variableIndex) ||
(instruction.OpCode == CilOpCodes.Stloc && ((CilLocalVariable)instruction.Operand!).Index == variableIndex))
{
for (var j = i - 1; j >= 0; j--)
{
var prevInstruction = body.Instructions[j];
if (prevInstruction.OpCode == CilOpCodes.Ldstr)
{
return prevInstruction;
}
}
}
}
return null;
}
Expand Down
15 changes: 15 additions & 0 deletions src/BitMono.Core/Attributes/MemberInclusionFlags.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
namespace BitMono.Core.Attributes;

/// <summary>
/// Flags that specify which types of members should be excluded from resolution (obfuscation).
/// Used with the <see cref="DoNotResolveAttribute"/> to control which members are protected from obfuscation.
/// </summary>
[Flags]
public enum MemberInclusionFlags
{
/// <summary>
/// Exclude special runtime members from obfuscation (e.g., special methods, properties, or fields used by the runtime).
/// </summary>
SpecialRuntime = 0x1,

/// <summary>
/// Exclude model members from obfuscation (e.g., data models, DTOs, or entities that should preserve their structure).
/// </summary>
Model = 0x2,

/// <summary>
/// Exclude members that are used in reflection from obfuscation (e.g., methods, fields, properties accessed via reflection).
/// </summary>
Reflection = 0x4,
}
1 change: 1 addition & 0 deletions src/BitMono.Core/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
global using BitMono.Core.Resolvers;
global using BitMono.Core.Services;
global using BitMono.Utilities.AsmResolver;
global using Echo.DataFlow;
global using Echo.DataFlow.Analysis;
global using Echo.Platforms.AsmResolver;
global using JetBrains.Annotations;
Expand Down
Loading
Loading