Décompiler des assemblies ReadyToRun (R2R) : pourquoi le code natif précompilé se décompile sans peine
Les images ReadyToRun embarquent du code natif précompilé — et pourtant elles se décompilent en C# aussi facilement que n'importe quelle assembly, parce que l'IL est toujours là. Voici ce que R2R stocke réellement, comment le reconnaître et pourquoi un décompilateur ignore la partie native.
Si on vous a déjà tendu une assembly .NET « précompilée » en supposant qu’un décompilateur s’y casserait les dents, ReadyToRun est la bonne surprise. R2R est la façon dont .NET livre du code à démarrage rapide — les bibliothèques du runtime elles-mêmes sont en R2R, et PublishReadyToRun=true l’active pour vos applications et images de conteneur — et cela semble devoir faire échouer la décompilation, puisqu’il contient du code natif compilé en AOT. Il n’en est rien. Une assembly R2R se décompile en C# propre parce que la seule chose que R2R ne jette jamais, c’est l’IL. Comprendre pourquoi rend toute une catégorie de questions « puis-je lire ceci ? » faciles à trancher.
Ce que contient réellement une image R2R
Une assembly .NET normale, c’est métadonnées + IL, compilée en code natif par le JIT au premier lancement. Une image R2R est produite par crossgen2, qui exécute ce JIT à l’avance et stocke le code natif obtenu dans l’assembly, afin que le runtime puisse sauter le JIT au démarrage. Et l’essentiel : il stocke le code natif en plus de, et non à la place des, métadonnées et IL d’origine.
Le fichier PE contient donc trois choses qui comptent ici : les tables de métadonnées ECMA-335 (types, méthodes, signatures — intactes), les corps de méthode en IL (également présents), et une section ReadyToRun avec le code natif précompilé plus les tables dont le runtime a besoin pour l’utiliser. Un ReadyToRunHeader, atteignable via le répertoire d’en-tête managé-natif de l’en-tête CLI, relie le tout et marque l’image comme R2R.
Pourquoi le décompilateur ignore le côté natif
Un décompilateur comme Glass est un reconstructeur IL-vers-C# : il parcourt les tables de métadonnées pour récupérer les types et signatures, puis lit le corps IL de chaque méthode et le relève en C#. Le code natif de R2R vit dans une section distincte avec ses propres tables de recherche, et le décompilateur ne la consulte tout simplement pas : l’IL est la définition de référence et portable de chaque méthode, et il est toujours là. C’est pourquoi ouvrir une image R2R est, du point de vue du décompilateur, indiscernable de l’ouverture d’une assembly uniquement JIT : mêmes métadonnées, même IL, même C# en sortie.
C’est aussi pourquoi vous pouvez décompiler les bibliothèques du runtime .NET (livrées en R2R), une application PublishReadyToRun ou une image de conteneur crossgen sans traitement particulier. L’inquiétude du « c’est précompilé » porte sur les octets natifs, et ce n’est pas ce que lit un décompilateur.
Reconnaître R2R, et la seule vraie exception
Pour confirmer qu’une assembly est R2R, cherchez l’en-tête ReadyToRun (les outils l’étiquettent « ReadyToRun » et affichent souvent un numéro de version R2R), ou connaissez simplement son origine : crossgen2 / PublishReadyToRun. La présence de l’IL est la garantie que vous pouvez la décompiler.
L’exception qui égare est NativeAOT, qui ressemble mais est le cas opposé. NativeAOT compile toute l’application en un exécutable natif autonome sans IL ni métadonnées managées à récupérer : il n’y a rien qu’un décompilateur d’IL puisse lire. Le test rapide : si un outil peut charger le fichier en tant qu’assembly .NET, il est managé (IL normal ou R2R) et se décompile ; si ce n’est qu’un binaire natif, c’est du NativeAOT et vous avez affaire à un travail de désassembleur natif, pas de décompilation. (Pour l’effet sur votre modèle de menace en tant qu’éditeur, voyez protéger le code .NET de la décompilation.)
Le faire avec Glass
Glass ouvre les assemblies ReadyToRun de façon transparente — il lit l’IL intact et reconstruit le C#, en ignorant la section native R2R, si bien que les bibliothèques du runtime, un service crossgen ou une application PublishReadyToRun se lisent comme n’importe quelle autre assembly. C’est le même flux que décompiler une DLL en C# ; R2R ne se met tout simplement pas en travers.
Glass est gratuit : pointez-le sur une image R2R et voyez l’IL revenir en C#.
Essayez Nebula.NET
Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.