Skip to content
← Tous les articles
· Delta1 Labs Obfuscation.NETUnity

Obfusquer les assemblages d'un jeu Unity : Mono, IL2CPP et les exigences de keep-rules de la sérialisation

Comment obfusquer le C# d'un jeu Unity sans casser les scènes, les prefabs ni l'inspecteur — ce que protègent respectivement Mono et IL2CPP, et les keep-rules exactes qu'exigent la sérialisation et la réflexion de Unity.

Livrez un jeu Unity construit sur le backend de scripting Mono et vous avez, en pratique, publié votre code source. Le Assembly-CSharp.dll posé dans le dossier Managed de votre player est de l’IL .NET ordinaire, et un décompilateur le retransforme en C# lisible en quelques secondes — vos formules de combat, votre anti-triche, votre validation d’achats intégrés, vos URLs secrètes, tout y est. L’obfuscation est la réponse habituelle, mais Unity est le seul environnement .NET où tout renommer naïvement va casser votre jeu. Unity s’appuie sur la sérialisation et la réflexion par nom plus fortement que presque tout autre framework, et il faut dire à l’obfuscateur ce qu’il ne doit pas toucher.

Cet article couvre ce que chaque backend Unity expose réellement, les éléments précis que Unity résout par nom, et les keep-rules qui vous laissent obfusquer agressivement sans un seul prefab cassé.

Deux backends, deux modèles de menace

Unity a deux backends de scripting, et ils changent le sens même de l’obfuscation.

Mono compile vos scripts en assemblages managés livrés sous forme de vrais fichiers .dll (Assembly-CSharp.dll, Assembly-CSharp-firstpass.dll et tout assemblage précompilé/asmdef). Ce sont des images CLI standard : décompilables, donc entièrement obfusquables. Le renommage, l’aplatissement du flux de contrôle et le chiffrement de chaînes s’appliquent au binaire livré exactement comme pour n’importe quelle app .NET.

IL2CPP prend ce même IL managé et le transpile en C++, qui est ensuite compilé en une bibliothèque native pour la plateforme cible. Il n’y a pas d’assemblage managé à renommer à l’exécution — mais IL2CPP émet un fichier global-metadata.dat à côté du binaire qui préserve en clair les noms de types, les noms de méthodes, les noms de champs et (c’est crucial) chaque littéral de chaîne. Des outils comme Il2CppDumper lisent ce fichier pour reconstituer une image quasi complète de votre code. IL2CPP n’est donc pas de l’obfuscation ; c’est de la transpilation avec un sidecar de métadonnées. Protéger là-dessus veut dire transformer les assemblages managés avant l’exécution d’IL2CPP, pour que les noms et les chaînes qui finissent intégrés dans global-metadata.dat soient déjà chiffrés ou renommés.

Where obfuscation fits in each Unity backend C# scripts → Roslyn → Assembly-CSharp.dll (managed IL) Nebula obfuscate (IL) Mono backend Ship obfuscated .dll in player IL2CPP backend il2cpp.exe → C++ → native native lib + global-metadata.dat (names/strings) Obfuscating the IL first means the metadata IL2CPP bakes in is already renamed & string-encrypted.

Ce que Unity résout par nom — et qui cassera si vous le renommez

L’éditeur comme le runtime font une quantité surprenante de liaison par chaîne et par nom. Chacun de ces points est un endroit où le renommage aveugle corrompt le jeu.

  • Champs sérialisés. Chaque champ public et chaque champ [SerializeField] private d’un MonoBehaviour ou d’un ScriptableObject est enregistré dans les scènes, les prefabs et les fichiers d’assets, indexé par son nom exact. Renommez le champ et la valeur enregistrée ne se charge plus.
  • Références de type de script. Une scène stocke « ce GameObject a un composant de type PlayerController » par identité de type (et d’assemblage). Renommer la classe détache chaque instance.
  • UnityEvents. Les listeners UnityEvent câblés dans l’inspecteur (votre OnClick de bouton, etc.) persistent le nom de la méthode cible sous forme de chaîne. Renommez la méthode et le bouton ne fait rien.
  • Appels dispatchés par chaîne. SendMessage, BroadcastMessage, Invoke, InvokeRepeating et AnimationEvent appellent tous les méthodes par nom en chaîne.
  • Hooks de cycle de vie et d’attributs. Unity trouve Awake, Start, Update, OnEnable, etc. par nom via la réflexion ; il scanne aussi [RuntimeInitializeOnLoadMethod], [MenuItem] et consorts. Les méthodes magiques figurent généralement sur une keep-list intégrée, mais pas la réflexion personnalisée.
  • Addressables / asset bundles. Tout ce que vous chargez par nom de type ou par adresse en chaîne à l’exécution.

Un exemple concret

Voici un composant parfaitement normal que le renommage détruirait de trois manières différentes :

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
}

Si Nebula renomme TurretController → a, fireRate → b et SpawnProjectile → c, alors chaque prefab de tourelle perd sa valeur de cadence de tir, chaque scène perd ses composants de tourelle, et l’animation de tir cesse de générer des projectiles — sans aucune erreur de compilation, car aucune de ces liaisons n’est vérifiée à la compilation.

Les keep-rules qui corrigent cela

L’objectif n’est pas « exclure les MonoBehaviours de l’obfuscation ». Cela laisserait la majeure partie de votre code en clair. L’objectif est de conserver seulement les noms auxquels Unity se lie, et d’obfusquer tout ce qui se trouve derrière eux — corps de méthodes, auxiliaires privés, algorithmes et littéraux de chaîne.

Nebula exprime cela à travers ses règles de keep-list / exclusion de renommage (le même mécanisme couvert dans keep-lists et exclusions de renommage). Un jeu de règles Unity pratique conserve :

  • les noms de tous les types dérivant de UnityEngine.MonoBehaviour et UnityEngine.ScriptableObject ;
  • tous les membres sérialisés de ces types (champs publics et champs [SerializeField], et auto-propriétés [field: SerializeField]) ;
  • toute méthode ciblée par un UnityEvent ou appelée via SendMessage/Invoke/AnimationEvent.

Exprimé au niveau des membres, cela donne « conserver le nom de tout champ marqué [SerializeField] ou public sur une sous-classe de UnityEngine.Object, et conserver le nom de type de toute sous-classe de MonoBehaviour/ScriptableObject ». Les corps de méthodes ne sont jamais affectés par les règles de conservation de noms, de sorte que l’aplatissement du flux de contrôle et le chiffrement de chaînes s’appliquent toujours à SpawnProjectile même si son nom est préservé — la logique est protégée, seul l’identifiant survit.

Tout le reste — votre logique métier en C# pur, le réseau, le format de sauvegarde, l’anti-triche et tous les littéraux de chaîne qu’ils contiennent — reçoit le traitement complet.

L’intégrer à la build

Tenez l’obfuscation à l’écart de l’éditeur et des machines de développement ; exécutez-la sur l’artefact. Pour le backend Mono, ajoutez une étape post-build (un post-traitement BuildPipeline ou un stage de CI) qui obfusque Assembly-CSharp.dll et vos autres assemblages de jeu dans le répertoire Managed du player, puis réempaquette. Pour IL2CPP, obfusquez les assemblages managés avant la conversion IL2CPP afin que le C++ généré et global-metadata.dat héritent des symboles renommés et des chaînes chiffrées. Exécuter la protection en CI (voir obfusquer uniquement sur le serveur de build) signifie que votre équipe continue de déboguer un code propre et lisible et que seules les builds livrées sont durcies.

Faire cela avec Nebula

Nebula obfusque les assemblages managés à partir desquels un jeu Unity est construit — renommage, aplatissement du flux de contrôle et chiffrement de chaînes — avec un modèle de keep-list assez précis pour préserver exactement les champs sérialisés, les noms de types de composants et les méthodes cibles d’événements que Unity lie par nom, tout en durcissant quand même chaque corps de méthode derrière eux. Elle s’exécute comme une étape post-build ou de CI, si bien que votre éditeur reste rapide et que seul le player livré est protégé. Nebula Pro ajoute la virtualisation de code pour la poignée de routines qui comptent le plus — vérifications de licence, anti-triche, validation d’achats.

Commencez avec l’édition gratuite pour durcir votre première build, ou consultez les offres quand vous serez prêt pour la virtualisation.

Essayez Nebula.NET

Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.