Add a self-check for the member reordering exclusions
parent
a1237f87d5
commit
f574f73f92
|
|
@ -0,0 +1,4 @@
|
|||
bin/
|
||||
obj/
|
||||
reordered.dll
|
||||
UnityEngine.CoreModule.dll
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
using dnlib.DotNet;
|
||||
using Obfuz.ObfusPasses.SymbolObfus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
class AllowAll : IObfuscationPolicy
|
||||
{
|
||||
public bool NeedRename(TypeDef t) => t.Name != "Plain08";
|
||||
public bool NeedRename(MethodDef m) => m.Name != "C";
|
||||
public bool NeedRename(FieldDef f) => f.Name != "f3";
|
||||
public bool NeedRename(PropertyDef p) => p.Name != "Prop2";
|
||||
public bool NeedRename(EventDef e) => e.Name != "E2";
|
||||
}
|
||||
|
||||
static class Program
|
||||
{
|
||||
static int failures;
|
||||
static void Check(bool ok, string what)
|
||||
{
|
||||
Console.WriteLine((ok ? "PASS " : "FAIL ") + what);
|
||||
if (!ok) failures++;
|
||||
}
|
||||
|
||||
static ModuleDefMD Load(string path)
|
||||
{
|
||||
var ctx = ModuleDef.CreateModuleContext();
|
||||
((AssemblyResolver)ctx.AssemblyResolver).EnableTypeDefCache = true;
|
||||
((AssemblyResolver)ctx.AssemblyResolver).DefaultModuleContext = ctx;
|
||||
var res = (AssemblyResolver)ctx.AssemblyResolver;
|
||||
res.PreSearchPaths.Add(Path.GetDirectoryName(Path.GetFullPath(path)));
|
||||
res.PreSearchPaths.Add(Path.GetDirectoryName(typeof(object).Assembly.Location));
|
||||
res.PostSearchPaths.Add(Path.GetDirectoryName(typeof(object).Assembly.Location));
|
||||
return ModuleDefMD.Load(path, ctx);
|
||||
}
|
||||
|
||||
static Dictionary<string, List<string>> Snapshot(ModuleDefMD mod)
|
||||
{
|
||||
var d = new Dictionary<string, List<string>>();
|
||||
d["#types"] = mod.Types.Select(t => t.FullName).ToList();
|
||||
foreach (var t in mod.GetTypes())
|
||||
{
|
||||
d[t.FullName + "#f"] = t.Fields.Select(f => f.Name.String).ToList();
|
||||
d[t.FullName + "#m"] = t.Methods.Select(m => m.Name.String).ToList();
|
||||
d[t.FullName + "#p"] = t.Properties.Select(p => p.Name.String).ToList();
|
||||
d[t.FullName + "#e"] = t.Events.Select(e => e.Name.String).ToList();
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static bool Same(List<string> a, List<string> b) => a.SequenceEqual(b);
|
||||
|
||||
static readonly HashSet<string> Pinned = new HashSet<string> { "Plain08", "C", "f3", "Prop2", "E2", "V1", "V2", "V3", "<Module>", "Boot" };
|
||||
|
||||
static bool IsRenamable(string key, string name)
|
||||
{
|
||||
if (Pinned.Contains(name) || name.StartsWith(".")) return false;
|
||||
if (key.StartsWith("Fx.Colour") || key.StartsWith("Fx.SeqLayout#f") || key.StartsWith("Fx.ExpLayout#f")
|
||||
|| key.StartsWith("Fx.PlainStruct#f") || key.StartsWith("Fx.SerialisableData#f")
|
||||
|| key.StartsWith("Fx.Script#f") || key.StartsWith("Fx.ScriptChild#f") || key.StartsWith("Fx.SoData#f")) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int Main()
|
||||
{
|
||||
string dll = "fixture/bin/Debug/netstandard2.0/fixture.dll";
|
||||
var before = Snapshot(Load(dll));
|
||||
|
||||
var mod1 = Load(dll);
|
||||
new MemberReorder(1234, new AllowAll()).Process(new List<ModuleDef> { mod1 });
|
||||
var after1 = Snapshot(mod1);
|
||||
|
||||
var mod2 = Load(dll);
|
||||
new MemberReorder(9999, new AllowAll()).Process(new List<ModuleDef> { mod2 });
|
||||
var after2 = Snapshot(mod2);
|
||||
|
||||
// exclusions: order must be byte-identical to the original
|
||||
foreach (var key in new[] {
|
||||
"Fx.Colour#f", // enum members
|
||||
"Fx.SeqLayout#f", // [StructLayout(Sequential)]
|
||||
"Fx.ExpLayout#f", // [StructLayout(Explicit)] + [FieldOffset]
|
||||
"Fx.PlainStruct#f", // implicitly sequential struct
|
||||
"Fx.SerialisableData#f", // [Serializable]
|
||||
"Fx.Script#f", // MonoBehaviour serialised fields
|
||||
"Fx.ScriptChild#f", // MonoBehaviour subclass
|
||||
"Fx.SoData#f", // ScriptableObject
|
||||
})
|
||||
{
|
||||
Check(Same(before[key], after1[key]), "field order pinned: " + key);
|
||||
}
|
||||
|
||||
// virtual methods keep their slot index
|
||||
foreach (var t in new[] { "Fx.BaseVirt", "Fx.DerivedVirt" })
|
||||
{
|
||||
var b = before[t + "#m"];
|
||||
var a = after1[t + "#m"];
|
||||
bool ok = true;
|
||||
for (int i = 0; i < b.Count; i++)
|
||||
{
|
||||
if (b[i].StartsWith("V") && a[i] != b[i]) ok = false;
|
||||
}
|
||||
Check(ok, "virtual slots pinned: " + t);
|
||||
}
|
||||
|
||||
// pinned-by-policy members keep their index
|
||||
Check(before["Fx.Plain01#f"].IndexOf("f3") == after1["Fx.Plain01#f"].IndexOf("f3"), "policy-pinned field index held");
|
||||
Check(before["Fx.Plain01#m"].IndexOf("C") == after1["Fx.Plain01#m"].IndexOf("C"), "policy-pinned method index held");
|
||||
Check(before["Fx.Plain09#p"].IndexOf("Prop2") == after1["Fx.Plain09#p"].IndexOf("Prop2"), "policy-pinned property index held");
|
||||
Check(before["Fx.Plain10#e"].IndexOf("E2") == after1["Fx.Plain10#e"].IndexOf("E2"), "policy-pinned event index held");
|
||||
Check(before["#types"].IndexOf("Fx.Plain08") == after1["#types"].IndexOf("Fx.Plain08"), "policy-pinned type index held");
|
||||
Check(before["#types"].IndexOf("Fx.BootA") == after1["#types"].IndexOf("Fx.BootA"), "RuntimeInitializeOnLoadMethod type index held");
|
||||
Check(after1["#types"][0] == "<Module>", "<Module> stays at index 0");
|
||||
|
||||
// membership is preserved everywhere
|
||||
bool memberOk = before.Keys.All(k => after1.ContainsKey(k)
|
||||
&& before[k].OrderBy(x => x, StringComparer.Ordinal).SequenceEqual(after1[k].OrderBy(x => x, StringComparer.Ordinal)));
|
||||
Check(memberOk, "no member lost or duplicated");
|
||||
|
||||
// the point of the plan: positional alignment must break, and rotate per seed
|
||||
Check(!Same(before["#types"], after1["#types"]), "type order changed vs original");
|
||||
Check(!Same(after1["#types"], after2["#types"]), "type order differs between seeds");
|
||||
int movedFields = before["Fx.Plain01#f"].Where((n, i) => after1["Fx.Plain01#f"][i] != n).Count();
|
||||
Check(movedFields > 0, "plain class fields reordered");
|
||||
int movedMethods = before["Fx.Plain02#m"].Where((n, i) => after1["Fx.Plain02#m"][i] != n).Count();
|
||||
Check(movedMethods > 0, "plain class methods reordered");
|
||||
|
||||
// the reordered module still writes and reloads
|
||||
var outPath = "reordered.dll";
|
||||
mod1.Write(outPath);
|
||||
var reloaded = Load(outPath);
|
||||
Check(Same(after1["#types"], reloaded.Types.Select(t => t.FullName).ToList()), "written module preserves new type order");
|
||||
Check(reloaded.GetTypes().Count() == mod1.GetTypes().Count(), "written module keeps every type");
|
||||
var rd = Snapshot(reloaded);
|
||||
Check(rd.Keys.All(k => Same(after1[k], rd[k])), "written module preserves every member order");
|
||||
var owned = reloaded.GetTypes().All(t => t.Module != null
|
||||
&& t.Fields.All(f => f.DeclaringType == t) && t.Methods.All(m => m.DeclaringType == t));
|
||||
Check(owned, "declaring-type back-pointers intact after reorder");
|
||||
|
||||
var asm = System.Reflection.Assembly.LoadFrom(Path.GetFullPath(outPath));
|
||||
var derived = asm.GetType("Fx.DerivedVirt");
|
||||
var instance = Activator.CreateInstance(derived);
|
||||
var baseType = asm.GetType("Fx.BaseVirt");
|
||||
Check((int)baseType.GetMethod("V1").Invoke(instance, null) == 11
|
||||
&& (int)baseType.GetMethod("V2").Invoke(instance, null) == 22
|
||||
&& (int)baseType.GetMethod("V3").Invoke(instance, null) == 33, "virtual dispatch still resolves to the override");
|
||||
var p09 = asm.GetType("Fx.Plain09");
|
||||
var p09i = Activator.CreateInstance(p09);
|
||||
p09.GetProperty("Prop3").SetValue(p09i, 7);
|
||||
Check((int)p09.GetProperty("Prop3").GetValue(p09i) == 7, "property accessors still bound after reorder");
|
||||
var exp = asm.GetType("Fx.ExpLayout");
|
||||
var expi = Activator.CreateInstance(exp);
|
||||
exp.GetField("b").SetValue(expi, 5);
|
||||
Check((int)exp.GetField("b").GetValue(expi) == 5 && (int)exp.GetField("a").GetValue(expi) == 0, "explicit-layout offsets still honoured");
|
||||
var seqFields = asm.GetType("Fx.SeqLayout").GetFields().Select(f => f.Name).ToList();
|
||||
Check(seqFields.SequenceEqual(new[] { "a", "b", "c", "d" }), "sequential-layout field order unchanged at runtime");
|
||||
var scriptFields = asm.GetType("Fx.Script").GetFields().Select(f => f.Name).ToList();
|
||||
Check(scriptFields.SequenceEqual(new[] { "hp", "title", "speed", "armed" }), "MonoBehaviour serialised field order unchanged at runtime");
|
||||
var enumNames = Enum.GetNames(asm.GetType("Fx.Colour")).ToList();
|
||||
Check(enumNames.SequenceEqual(new[] { "Red", "Green", "Blue", "Alpha", "Cyan", "Magenta" }), "enum member order unchanged at runtime");
|
||||
|
||||
var policy = new AllowAll();
|
||||
int renamed = 0, aligned = 0;
|
||||
foreach (var key in before.Keys)
|
||||
{
|
||||
var a = after1[key];
|
||||
var b = after2[key];
|
||||
for (int i = 0; i < a.Count; i++)
|
||||
{
|
||||
if (!IsRenamable(key, a[i])) continue;
|
||||
renamed++;
|
||||
if (a[i] == b[i]) aligned++;
|
||||
}
|
||||
}
|
||||
double rate = renamed == 0 ? 0 : (double)aligned / renamed;
|
||||
Console.WriteLine($"positional recovery of renamed members across two seeds: {aligned}/{renamed} = {rate:P1}");
|
||||
Check(rate < 0.35, "positional differ cannot align renamed members between two builds");
|
||||
|
||||
Console.WriteLine(failures == 0 ? "ALL PASS" : failures + " FAILURE(S)");
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Fx
|
||||
{
|
||||
public enum Colour { Red, Green, Blue, Alpha, Cyan, Magenta }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class SeqLayout { public int a; public int b; public int c; public int d; }
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public class ExpLayout
|
||||
{
|
||||
[FieldOffset(0)] public int a;
|
||||
[FieldOffset(4)] public int b;
|
||||
[FieldOffset(8)] public int c;
|
||||
}
|
||||
|
||||
public struct PlainStruct { public int a; public int b; public int c; public int d; }
|
||||
|
||||
[Serializable]
|
||||
public class SerialisableData { public int a; public string b; public float c; public bool d; }
|
||||
|
||||
public class Script : MonoBehaviour
|
||||
{
|
||||
public int hp; public string title; public float speed; public bool armed;
|
||||
public void M1() { } public void M2() { } public void M3() { } public void M4() { }
|
||||
}
|
||||
|
||||
public class ScriptChild : Script { public int extra; public string more; public double yet; }
|
||||
|
||||
public class SoData : ScriptableObject { public int x; public string y; public float z; }
|
||||
|
||||
public class BaseVirt
|
||||
{
|
||||
public virtual int V1() => 1;
|
||||
public virtual int V2() => 2;
|
||||
public virtual int V3() => 3;
|
||||
public int P1() => 1;
|
||||
public int P2() => 2;
|
||||
public int P3() => 3;
|
||||
public int P4() => 4;
|
||||
}
|
||||
|
||||
public class DerivedVirt : BaseVirt
|
||||
{
|
||||
public override int V1() => 11;
|
||||
public override int V2() => 22;
|
||||
public override int V3() => 33;
|
||||
public int Q1() => 1;
|
||||
public int Q2() => 2;
|
||||
public int Q3() => 3;
|
||||
public int Q4() => 4;
|
||||
}
|
||||
|
||||
public class BootA { [RuntimeInitializeOnLoadMethod] public static void Boot() { } }
|
||||
|
||||
public class Plain01 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain02 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain03 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain04 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain05 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain06 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain07 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain08 { public int f1, f2, f3, f4, f5, f6; public void A(){} public void B(){} public void C(){} public void D(){} public void E(){} public void F(){} }
|
||||
public class Plain09 { public int Prop1 {get;set;} public int Prop2 {get;set;} public int Prop3 {get;set;} public int Prop4 {get;set;} }
|
||||
public class Plain10 { public event Action E1; public event Action E2; public event Action E3; public event Action E4; public void Fire(){E1();E2();E3();E4();} }
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>fixture</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup><ProjectReference Include="../unitystub/unitystub.csproj" /></ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<AssemblyName>probe</AssemblyName>
|
||||
<RootNamespace>probe</RootNamespace>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<NoWarn>CS0168;CS0219;CS0414;CS1998;CS0162</NoWarn>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="dnlib"><HintPath>../../Plugins/dnlib.dll</HintPath></Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="stubs.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="../../Editor/ConstValues.cs" />
|
||||
<Compile Include="../../Editor/Utils/MetaUtil.cs" />
|
||||
<Compile Include="../../Editor/Utils/TypeSigUtil.cs" />
|
||||
<Compile Include="../../Editor/Utils/CachedDictionary.cs" />
|
||||
<Compile Include="../../Editor/Utils/GenericArgumentContext.cs" />
|
||||
<Compile Include="../../Editor/Utils/ThisArgType.cs" />
|
||||
<Compile Include="../../Editor/ObfusPasses/SymbolObfus/IObfuscationPolicy.cs" />
|
||||
<Compile Include="../../Editor/ObfusPasses/SymbolObfus/MemberReorder.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
dotnet build fixture/fixture.csproj -v q --nologo
|
||||
dotnet build probe.csproj -v q --nologo
|
||||
cp unitystub/bin/Debug/netstandard2.0/UnityEngine.CoreModule.dll bin/Debug/net7.0/
|
||||
cp unitystub/bin/Debug/netstandard2.0/UnityEngine.CoreModule.dll .
|
||||
dotnet bin/Debug/net7.0/probe.dll
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
namespace UnityEngine.Assertions {
|
||||
public static class Assert {
|
||||
public static void IsTrue(bool c) { if(!c) throw new System.Exception("assert"); }
|
||||
public static void IsTrue(bool c, string m) { if(!c) throw new System.Exception(m); }
|
||||
public static void IsNotNull(object o) { if(o==null) throw new System.Exception("null"); }
|
||||
}
|
||||
}
|
||||
namespace UnityEngine {
|
||||
public static class Debug {
|
||||
public static void Log(object o) { System.Console.WriteLine(o); }
|
||||
public static void LogWarning(object o) { System.Console.WriteLine(o); }
|
||||
public static void LogError(object o) { System.Console.WriteLine(o); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace UnityEngine
|
||||
{
|
||||
public class Object { }
|
||||
public class Component : Object { }
|
||||
public class Behaviour : Component { }
|
||||
public class MonoBehaviour : Behaviour { }
|
||||
public class ScriptableObject : Object { }
|
||||
[System.AttributeUsage(System.AttributeTargets.Method)]
|
||||
public class RuntimeInitializeOnLoadMethodAttribute : System.Attribute { }
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>UnityEngine.CoreModule</AssemblyName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue