Skip to content
← Tous les articles
· Delta1 Labs Obscurcissement.NETAnalyse approfondie

Chiffrement des constantes en .NET : cacher les nombres que les décompilateurs lisent gratuitement

Le chiffrement des chaînes attire l'attention, mais les littéraux numériques de votre code — seuils, opcodes, nombres magiques, décalages — compilent en instructions ldc qu'un décompilateur lit instantanément. Voici comment les constantes numériques fuitent la logique, comment le chiffrement des constantes les remplace par des valeurs décodées à l'exécution, et où il s'insère aux côtés du chiffrement des chaînes et de la protection du flux de contrôle.

Le chiffrement des chaînes attire toute l”attention dans la protection .NET, et à juste titre — la liste des chaînes d”un décompilateur est une carte. Mais les chaînes ne sont que la moitié des littéraux de votre code. L”autre moitié sont des nombres, et les nombres précis sont très souvent la logique elle-même : le seuil contre lequel une vérification de licence compare, l”opcode dans un switch de protocole, le masque de bits qui sélectionne une fonctionnalité, la valeur magique qui valide un en-tête. Tous compilent en instructions de chargement nues et lisibles instantanément. Le chiffrement des constantes est l”équivalent numérique du chiffrement des chaînes, et sur du code sensible à la sécurité, il compte tout autant.

Comment un littéral numérique compile

Quand vous écrivez un littéral numérique, le compilateur C# émet l”une des instructions ldc (« load constant »), avec la valeur juste là dans l”IL. Considérez une vérification triviale :

if (attempts > 3 && headerMagic == 0x5A4D)
    Reject();

L”IL porte les deux nombres en clair :

ldloc.0            // attempts
ldc.i4.3           // the literal 3
ble.s   IL_0014
ldloc.1            // headerMagic
ldc.i4   0x5A4D    // the literal "MZ" PE signature
bne.un.s IL_0014
call    Reject

Un décompilateur reconstruit cela exactement comme le source que vous avez écrit — 3 et 0x5A4D et tout. La seconde constante est particulièrement parlante : 0x5A4D est la signature MZ de l”en-tête DOS, donc un lecteur sait instantanément que ce code renifle un fichier PE, sans un seul commentaire ni chaîne pour l”aider. Les petits entiers ont leurs propres opcodes courts (ldc.i4.0 à ldc.i4.8, ldc.i4.s pour un octet), les plus grands utilisent le ldc.i4/ldc.i8/ldc.r8 complet, mais dans tous les cas la valeur est un opérande littéral que n”importe qui peut lire.

Ce que fait le chiffrement des constantes

Le chiffrement des constantes réécrit ces chargements littéraux pour que la valeur réelle n”apparaisse jamais dans l”IL. Chaque constante protégée est stockée sous une forme encodée et remplacée au site d”appel par une petite routine de décodage qui la reconstruit à l”exécution. Conceptuellement, le ldc devient un appel :

// before:
ldc.i4   0x5A4D

// after (conceptually):
ldc.i4   0x9C31          // an encoded token, not the real value
call     int32 <Decode>::N(int32)   // returns 0x5A4D at runtime

La routine de décodage est générée par l”obscurcisseur et est elle-même obscurcie — ce n”est pas un unique xor évident avec une clé visible posée à côté des données. L”effet est qu”un décompilateur statique voit Decode.N(0x9C31) là où le source avait 0x5A4D. Le nombre a disparu du listing ; le récupérer signifie exécuter réellement (ou émuler fidèlement) la logique de décodage, ce qui est exactement le travail que l”analyse statique essaie d”éviter.

source / IL nuldc.i4 0x5A4Dencoderdans l'IL distribuéldc.i4 0x9C31exécutiondécodé à l'usageDecode.N(..)=0x5A4D

Les nombres qui valent la peine d’être cachés

Tout entier n”est pas intéressant — une boucle qui compte jusqu”à arr.Length ne révèle rien. Ce que le chiffrement des constantes protège, c”est la classe de littéraux qui sont le comportement :

  • Seuils et limites dans les vérifications — le plafond de tentatives > 3, un compte de jours d”essai, une limite de sièges. Voir le nombre dit à l”attaquant précisément quelle frontière pousser.
  • Nombres magiques et signatures — octets d”en-tête comme 0x5A4D, marqueurs de format, graines de checksum. Ceux-ci étiquettent exactement ce que le code analyse ou valide.
  • Opcodes de protocole et d’état — les cas entiers d”un switch qui pilote une machine à états ou un protocole réseau. En clair, tout le protocole est énumérable depuis la table de sauts.
  • Masques de bits de drapeaux de fonctionnalités — une valeur comme 0x04 combinée par AND contre un champ de droits nomme le bit exact qui débloque une fonctionnalité, ce qui est une invitation à le forcer.
  • Codes de statut et de résultat — la valeur précise qu”une routine de sécurité retourne en cas de succès, qu”un attaquant adorerait faire produire inconditionnellement par le code.

Dans chaque cas, le nombre porte le sens, et le retirer du listing statique élève le coût de la compréhension ou de la manipulation de la vérification.

Où il s’insère avec les autres transformations

Le chiffrement des constantes est une couche, et il est le plus fort en combinaison. Seul, il cache des valeurs mais laisse intacte la forme environnante ; associé au chiffrement des chaînes, il ferme l”autre moitié de la fuite de littéraux, et associé à l”obscurcissement du flux de contrôle, il cache non seulement ce que sont les constantes mais où les comparaisons ont lieu. Une vérification de licence protégée par les trois ne présente plus au lecteur un net if (days > 30) contre un littéral visible — la valeur est décodée à l”exécution, les messages de chaîne sont chiffrés, et la branche elle-même est enfouie dans un graphe de contrôle aplati.

Il se compose aussi avec la protection au niveau méthode : une constante qui n”est jamais décodée qu”à l”intérieur d”une méthode chiffrée ou virtualisée n”existe jamais sous forme nue dans l”assemblage distribué, parce que le corps de méthode portant l”appel de décodage est lui-même protégé jusqu”à l”exécution.

Une note sur le coût et le cadrage

Il n”y a pas de protection gratuite. Un ldc.i4 nu est une seule instruction ; une constante décodée est un appel de méthode, donc chaque littéral protégé échange un chargement direct contre une petite quantité de travail à l”exécution. Pour le code ordinaire, c”est invisible. Le seul endroit où y penser est une boucle numérique vraiment chaude qui lit la même constante des millions de fois — là, le chiffrement des constantes en masse peut apparaître dans un profilage. La réponse est le cadrage, pas l”évitement : appliquez le chiffrement des constantes au code où les nombres sont des secrets à garder — licensing, gestion de protocole, validation, vérifications de droits — et laissez tranquilles les noyaux arithmétiques qui ne contiennent aucun littéral sensible. Nebula vous permet de cibler la protection sur les types et méthodes qui le méritent, de sorte que les constantes pertinentes pour la sécurité sont cachées tandis que les maths critiques pour la performance restent un chargement nu.

La conclusion est simple : un décompilateur lit vos nombres aussi aisément que vos chaînes, et sur du code sensible à la sécurité, les nombres sont fréquemment toute l”histoire. Chiffrez les constantes qui encodent votre logique, combinez la transformation avec la protection des chaînes et du flux de contrôle, et cadrez-la là où elle gagne son coût — et les littéraux nets et auto-explicatifs qui livraient autrefois vos vérifications au lecteur disparaissent du listing.

Essayez Nebula.NET

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