Performances et réglages
L’obfuscation du flux de contrôle échange un peu de temps d’exécution contre de la protection. Les algorithmes légers sont pratiquement gratuits ; les algorithmes d’aplatissement ajoutent un répartiteur qui s’exécute à chaque appel et coûtent donc davantage. Cette page compare les algorithmes, explique le paramètre itérations et montre comment préserver la rapidité des méthodes critiques pour les performances.
Comparer les algorithmes
| Algorithme | Famille | Coût relatif à l’exécution | IL vérifiable | Édition |
|---|---|---|---|---|
if | Légère | Négligeable | Oui | Toutes |
goto | Légère | Négligeable | Oui | Toutes |
switch | Aplatissement | Élevé | Oui | Toutes |
case | Aplatissement (avec switch) | Élevé | Oui | Toutes |
call | Aplatissement (avec switch) | Élevé | Oui | Toutes |
value | Aplatissement (avec switch) | Élevé | Oui | Toutes |
chain (état chaîné) | Aplatissement | Élevé (≈ switch) | Oui | Ultimate |
token | — | Faible | Non | Toutes |
underflow | — | Faible | Non | Toutes |
Lecture du tableau.
ifetgotoajoutent des branchements et des conditions, mais pas de répartiteur : leur coût à l’exécution est négligeable et vous pouvez les appliquer largement.- La famille d’aplatissement (
switch,case,call,value) etchainreconstruisent une méthode autour d’un répartiteur. Sur une petite méthode appelée des dizaines de millions de fois dans une boucle serrée, une seule passe d’aplatissement ajoute environ un ordre de grandeur au temps par appel de cette méthode. L’état chaîné reste à quelques pour cent deswitchalors qu’il est beaucoup plus difficile à désobfusquer : la protection supplémentaire est pratiquement gratuite par rapport à l’aplatissement lui-même. tokenetunderflowsont peu coûteux à l’exécution mais produisent de l’IL non vérifiable ; Babel les désactive donc sur .NET et .NET Core.
Ces chiffres correspondent au pire cas : ils proviennent d’un micro-benchmark qui ne fait rien d’autre qu’appeler en boucle une minuscule méthode aplatie. La plupart des méthodes ne sont pas appelées dans des boucles aussi sollicitées, et l’effet réel de l’aplatissement est donc généralement faible. Il reste néanmoins préférable de réserver l’aplatissement aux méthodes qui comptent et de l’éviter sur les chemins critiques.
Itérations du flux de contrôle
L’effet d’obfuscation de tout algorithme de flux de contrôle dépend avant tout du nombre d’itérations appliquées à une méthode : plus ce nombre est élevé, plus le résultat est complexe et, pour les algorithmes d’aplatissement, plus le coût à l’exécution augmente, car chaque passe ajoute une couche de répartition.
Babel vous permet de définir le nombre d’itérations avec --iterations <n> (ou ILIterations depuis MSBuild). La valeur 0 désactive l’obfuscation du flux de contrôle. Il existe une limite supérieure, qui dépend de la structure de la méthode, au-delà de laquelle le flux d’une méthode donnée ne peut plus être modifié ; augmenter ce nombre n’ajoute donc pas toujours des instructions.
Limiter l’obfuscation du flux de contrôle
Comme l’aplatissement ajoute des branchements à chaque méthode qu’il touche, l’appliquer sans discernement peut ralentir le code critique pour les performances. Utilisez des règles XML pour limiter l’obfuscation du flux de contrôle aux méthodes qui en ont besoin et exclure les autres, par exemple les méthodes générées par le compilateur, qui contiennent rarement une propriété intellectuelle à protéger :
<Rules>
<Rule name="rule" feature="control flow" exclude="true" applyToMembers="true">
<Target>Classes</Target>
<Pattern>*</Pattern>
<HasAttribute onEnclosingType="false">System.Runtime.CompilerServices.CompilerGeneratedAttribute</HasAttribute>
</Rule>
</Rules>Cette règle vise la fonctionnalité control flow avec exclude="true", et applyToMembers="true" l’étend à tous les membres des classes correspondantes. L’élément <Target> sélectionne le genre d’élément (ici, Classes), <Pattern> correspond à leurs noms (* = tous) et <HasAttribute> restreint la correspondance aux classes qui portent CompilerGeneratedAttribute.
La même approche permet d’exclure toute méthode que vous savez critique pour les performances, afin d’équilibrer protection et vitesse : aplatissez le code sensible et laissez intacts les chemins critiques.