Die Assemblys eines Unity-Spiels obfuskieren: Mono, IL2CPP und die Keep-Rule-Anforderungen der Serialisierung
Wie Sie das C# eines Unity-Spiels obfuskieren, ohne Szenen, Prefabs oder den Inspector zu zerstören — was Mono und IL2CPP jeweils schützen und welche exakten Keep-Rules die Serialisierung und Reflection von Unity verlangen.
Liefern Sie ein auf dem Mono-Scripting-Backend gebautes Unity-Spiel aus, haben Sie faktisch Ihren Quellcode veröffentlicht. Die Assembly-CSharp.dll im Managed-Ordner Ihres Players ist gewöhnliches .NET-IL, und ein Decompiler verwandelt sie in Sekunden in lesbares C# zurück — Ihre Kampfformeln, Ihr Anti-Cheat, Ihre In-App-Kauf-Validierung, Ihre geheimen URLs, alles direkt sichtbar. Obfuskierung ist die übliche Antwort, aber Unity ist die eine .NET-Umgebung, in der naives Umbenennen von allem Ihr Spiel tatsächlich zerstört. Unity stützt sich stärker als fast jedes andere Framework auf namensbasierte Serialisierung und Reflection, und man muss dem Obfuskator sagen, was er nicht anfassen darf.
Dieser Beitrag behandelt, was jedes Unity-Backend tatsächlich offenlegt, die konkreten Dinge, die Unity über ihren Namen auflöst, und die Keep-Rules, mit denen Sie aggressiv obfuskieren können, ohne ein einziges zerstörtes Prefab.
Zwei Backends, zwei Bedrohungsmodelle
Unity hat zwei Scripting-Backends, und sie verändern, was Obfuskierung überhaupt bedeutet.
Mono kompiliert Ihre Skripte zu verwalteten Assemblys, die als echte .dll-Dateien ausgeliefert werden (Assembly-CSharp.dll, Assembly-CSharp-firstpass.dll und alle vorkompilierten Assemblys/asmdefs). Das sind Standard-CLI-Images: dekompilierbar und daher vollständig obfuskierbar. Umbenennen, Kontrollfluss-Flattening und String-Verschlüsselung gelten für das ausgelieferte Binary genau wie bei jeder anderen .NET-App.
IL2CPP nimmt dasselbe verwaltete IL und transpiliert es zu C++, das anschließend zu einer nativen Bibliothek für die Zielplattform kompiliert wird. Es gibt keine verwaltete Assembly, die zur Laufzeit umbenannt werden müsste — aber IL2CPP gibt neben dem Binary eine Datei global-metadata.dat aus, die Typnamen, Methodennamen, Feldnamen und (entscheidend) jedes String-Literal im Klartext bewahrt. Werkzeuge wie Il2CppDumper lesen diese Datei, um ein nahezu vollständiges Bild Ihres Codes zu rekonstruieren. IL2CPP ist also keine Obfuskierung; es ist Transpilation mit einem Metadaten-Sidecar. Schutz bedeutet dort, die verwalteten Assemblys zu transformieren, bevor IL2CPP läuft, damit die Namen und Strings, die in global-metadata.dat einbrennen, bereits verschlüsselt oder umbenannt sind.
Was Unity über den Namen auflöst — und beim Umbenennen zerbricht
Sowohl der Editor als auch die Laufzeit betreiben überraschend viel string- und namensbasierte Bindung. Jeder dieser Punkte ist eine Stelle, an der blindes Umbenennen das Spiel beschädigt.
- Serialisierte Felder. Jedes
public-Feld und jedes[SerializeField] private-Feld einesMonoBehaviouroderScriptableObjectwird in Szenen, Prefabs und Asset-Dateien gespeichert, indexiert über seinen exakten Namen. Benennen Sie das Feld um, lässt sich der gespeicherte Wert nicht mehr laden. - Skript-Typreferenzen. Eine Szene speichert „dieses GameObject hat eine Komponente vom Typ
PlayerController” über die Typ- (und Assembly-)Identität. Das Umbenennen der Klasse löst jede Instanz ab. - UnityEvents. Im Inspector verdrahtete
UnityEvent-Listener (Ihr Button-OnClickusw.) persistieren den Zielmethodennamen als String. Benennen Sie die Methode um, tut der Button nichts. - String-dispatchte Aufrufe.
SendMessage,BroadcastMessage,Invoke,InvokeRepeatingundAnimationEventrufen Methoden sämtlich über ihren String-Namen auf. - Lebenszyklus- & Attribut-Hooks. Unity findet
Awake,Start,Update,OnEnableusw. per Reflection über ihren Namen; außerdem scannt es nach[RuntimeInitializeOnLoadMethod],[MenuItem]und Ähnlichem. Magische Methoden stehen üblicherweise auf einer eingebauten Keep-List, benutzerdefinierte Reflection jedoch nicht. - Addressables / Asset-Bundles. Alles, was Sie zur Laufzeit über den Typnamen oder über eine String-Adresse laden.
Ein konkretes Beispiel
Hier eine vollkommen normale Komponente, die das Umbenennen auf drei verschiedene Arten ruinieren würde:
using UnityEngine;
using UnityEngine.Events;
public class TurretController : MonoBehaviour // (1) type name is in every scene/prefab using it
{
[SerializeField] private float fireRate = 0.5f; // (2) field name is saved in the asset
[SerializeField] private UnityEvent onFire; // (3) persistent listeners store a method name
// Called by name from an AnimationEvent on the fire clip:
public void SpawnProjectile() { /* ... */ } // (4) resolved by string, not a direct call
}
Benennt Nebula TurretController → a, fireRate → b und SpawnProjectile → c um, dann verliert jedes Geschütz-Prefab seinen Feuerraten-Wert, jede Szene verliert ihre Geschütz-Komponenten, und die Feuer-Animation erzeugt keine Projektile mehr — ohne jeden Compilerfehler, weil keine dieser Bindungen zur Kompilierzeit geprüft wird.
Die Keep-Rules, die das beheben
Das Ziel ist nicht „MonoBehaviours von der Obfuskierung ausschließen”. Das würde den Großteil Ihres Codes im Klartext belassen. Das Ziel ist, nur die Namen zu bewahren, an die Unity bindet, und alles dahinter zu obfuskieren — Methodenrümpfe, private Helfer, Algorithmen und String-Literale.
Nebula drückt das über seine Keep-List- / Umbenennungs-Ausschlussregeln aus (derselbe Mechanismus, der in Keep-Lists und Umbenennungs-Ausschlüsse behandelt wird). Ein praxistauglicher Unity-Regelsatz bewahrt:
- die Namen aller Typen, die von
UnityEngine.MonoBehaviourundUnityEngine.ScriptableObjectabgeleitet sind; - alle serialisierten Member dieser Typen (öffentliche Felder und
[SerializeField]-Felder sowie[field: SerializeField]-Auto-Properties); - jede Methode, auf die ein UnityEvent zielt oder die über
SendMessage/Invoke/AnimationEventaufgerufen wird.
Gegen die Member formuliert lautet das: „bewahre den Namen jedes Felds, das [SerializeField] oder öffentlich ist, auf einer Unterklasse von UnityEngine.Object, und bewahre den Typnamen jeder Unterklasse von MonoBehaviour/ScriptableObject”. Methodenrümpfe sind von Namens-Keep-Rules nie betroffen, sodass Kontrollfluss-Flattening und String-Verschlüsselung weiterhin für SpawnProjectile gelten, obwohl dessen Name bewahrt bleibt — die Logik ist geschützt, nur der Bezeichner überlebt.
Alles andere — Ihre reine C#-Domänenlogik, das Networking, das Speicherstand-Format, der Anti-Cheat und sämtliche darin enthaltenen String-Literale — erhält die volle Behandlung.
Die Einbettung in den Build
Halten Sie die Obfuskierung aus dem Editor und von Entwicklermaschinen heraus; führen Sie sie auf dem Artefakt aus. Für das Mono-Backend fügen Sie einen Post-Build-Schritt hinzu (eine BuildPipeline-Nachbearbeitung oder eine CI-Stufe), der Assembly-CSharp.dll und Ihre übrigen Spiel-Assemblys im Managed-Verzeichnis des Players obfuskiert und dann neu verpackt. Für IL2CPP obfuskieren Sie die verwalteten Assemblys vor der IL2CPP-Konvertierung, damit das erzeugte C++ und global-metadata.dat die umbenannten Symbole und verschlüsselten Strings erben. Den Schutz in der CI auszuführen (siehe Obfuskieren nur auf dem Build-Server) bedeutet, dass Ihr Team weiterhin sauberen, lesbaren Code debuggt und nur ausgelieferte Builds gehärtet werden.
Das mit Nebula umsetzen
Nebula obfuskiert die verwalteten Assemblys, aus denen ein Unity-Spiel gebaut ist — Umbenennen, Kontrollfluss-Flattening und String-Verschlüsselung — mit einem Keep-List-Modell, das präzise genug ist, um genau die serialisierten Felder, Komponenten-Typnamen und Event-Zielmethoden zu bewahren, die Unity über den Namen bindet, und härtet dennoch jeden Methodenrumpf dahinter. Es läuft als Post-Build- oder CI-Schritt, sodass Ihr Editor schnell bleibt und nur der ausgelieferte Player geschützt wird. Nebula Pro ergänzt Code-Virtualisierung für die Handvoll Routinen, die am meisten zählen — Lizenzprüfungen, Anti-Cheat, Kauf-Validierung.
Beginnen Sie mit der kostenlosen Edition, um Ihren ersten Build zu härten, oder sehen Sie sich die Tarife an, wenn Sie für die Virtualisierung bereit sind.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.