Add symbolObfusSettings.enableMemberReordering to shuffle type and member declaration order

pull/36/head
olivato 2026-09-01 16:13:56 +01:00
parent e6fd9d404c
commit a1237f87d5
4 changed files with 170 additions and 0 deletions

View File

@ -0,0 +1,146 @@
// Copyright 2025 Code Philosophy
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using dnlib.DotNet;
using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Obfuz.ObfusPasses.SymbolObfus
{
public class MemberReorder
{
private readonly Random _random;
private readonly IObfuscationPolicy _renamePolicy;
public MemberReorder(int seed, IObfuscationPolicy renamePolicy)
{
if (seed == 0)
{
throw new Exception("SymbolObfuscationSettings.enableMemberReordering requires a non-zero nameRandomSeed, otherwise the member layout is identical in every build.");
}
_random = new Random(seed);
_renamePolicy = renamePolicy;
}
public void Process(List<ModuleDef> modules)
{
foreach (ModuleDef mod in modules)
{
Reorder(mod.Types, t => t.IsGlobalModuleType || IsPositionPinnedType(t));
foreach (TypeDef type in mod.GetTypes().ToList())
{
Reorder(type.NestedTypes, IsPositionPinnedType);
if (MayReorderFields(type))
{
Reorder(type.Fields, f => !_renamePolicy.NeedRename(f));
}
Reorder(type.Methods, IsPositionPinnedMethod);
Reorder(type.Properties, p => !_renamePolicy.NeedRename(p));
Reorder(type.Events, e => !_renamePolicy.NeedRename(e));
}
}
}
private bool IsPositionPinnedType(TypeDef type)
{
if (!_renamePolicy.NeedRename(type))
{
return true;
}
return type.Methods.Any(MetaUtil.HasRuntimeInitializeOnLoadMethodAttribute);
}
private bool IsPositionPinnedMethod(MethodDef method)
{
if (method.IsVirtual || method.IsAbstract || method.HasOverrides)
{
return true;
}
if (MetaUtil.HasRuntimeInitializeOnLoadMethodAttribute(method))
{
return true;
}
return !_renamePolicy.NeedRename(method);
}
private static bool MayReorderFields(TypeDef type)
{
if (type.IsEnum || type.IsValueType)
{
return false;
}
if (type.IsSequentialLayout || type.IsExplicitLayout)
{
return false;
}
if (MetaUtil.IsScriptOrSerializableType(type))
{
return false;
}
return !type.Fields.Any(f => f.FieldOffset != null);
}
private void Reorder<T>(IList<T> members, Func<T, bool> isPinned)
{
int count = members.Count;
if (count < 2)
{
return;
}
var order = new List<T>(count);
var movableSlots = new List<int>();
for (int i = 0; i < count; i++)
{
T member = members[i];
order.Add(member);
if (!isPinned(member))
{
movableSlots.Add(i);
}
}
if (movableSlots.Count < 2)
{
return;
}
var movable = movableSlots.Select(i => order[i]).ToList();
for (int i = movable.Count - 1; i > 0; i--)
{
int j = _random.Next(i + 1);
T tmp = movable[i];
movable[i] = movable[j];
movable[j] = tmp;
}
for (int i = 0; i < movableSlots.Count; i++)
{
order[movableSlots[i]] = movable[i];
}
members.Clear();
foreach (T member in order)
{
members.Add(member);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a8d01b1f9a245ad830455326004cf10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -51,6 +51,8 @@ namespace Obfuz.ObfusPasses.SymbolObfus
private readonly VirtualMethodGroupCalculator _virtualMethodGroupCalculator;
private readonly List<MethodDef> _virtualMethods = new List<MethodDef>();
private readonly List<Type> _customPolicyTypes;
private readonly bool _enableMemberReordering;
private readonly int _nameRandomSeed;
class CustomAttributeInfo
{
@ -69,6 +71,8 @@ namespace Obfuz.ObfusPasses.SymbolObfus
_virtualMethodGroupCalculator = new VirtualMethodGroupCalculator();
_nameMaker = settings.debug ? NameMakerFactory.CreateDebugNameMaker() : NameMakerFactory.CreateNameMakerBaseASCIICharSet(settings.obfuscatedNamePrefix, settings.nameRandomSeed);
_customPolicyTypes = settings.customRenamePolicyTypes;
_enableMemberReordering = settings.enableMemberReordering;
_nameRandomSeed = settings.nameRandomSeed;
}
public void Init()
@ -205,6 +209,10 @@ namespace Obfuz.ObfusPasses.SymbolObfus
public void Process()
{
if (_enableMemberReordering)
{
new MemberReorder(_nameRandomSeed, _renamePolicy).Process(_toObfuscatedModules);
}
_renameRecordMap.Init(_toObfuscatedModules, _nameMaker);
BuildVirtualMethodGroup();
RenameTypes();

View File

@ -35,6 +35,7 @@ namespace Obfuz.Settings
public bool keepUnknownSymbolInSymbolMappingFile;
public string symbolMappingFile;
public int nameRandomSeed;
public bool enableMemberReordering;
public List<string> ruleFiles;
public List<Type> customRenamePolicyTypes;
}
@ -62,6 +63,9 @@ namespace Obfuz.Settings
[Tooltip("random seed for generated names. 0 keeps the original deterministic sequence; any other value randomises which name each symbol gets, so the mapping differs between builds")]
public int nameRandomSeed = 0;
[Tooltip("shuffle the declaration order of types and members, seeded from nameRandomSeed, so a positional diff of two builds cannot align them. Requires a non-zero nameRandomSeed")]
public bool enableMemberReordering = false;
[Tooltip("debug symbol mapping file path, used for debugging purposes")]
public string debugSymbolMappingFile = "Assets/Obfuz/SymbolObfus/symbol-mapping-debug.xml";
@ -87,6 +91,7 @@ namespace Obfuz.Settings
keepUnknownSymbolInSymbolMappingFile = keepUnknownSymbolInSymbolMappingFile,
symbolMappingFile = GetSymbolMappingFile(),
nameRandomSeed = nameRandomSeed,
enableMemberReordering = enableMemberReordering,
ruleFiles = ruleFiles?.ToList() ?? new List<string>(),
customRenamePolicyTypes = customRenamePolicyTypes?.Select(typeName => ReflectionUtil.FindUniqueTypeInCurrentAppDomain(typeName)).ToList() ?? new List<Type>(),
};