Masquer le graphe d’appels avec le proxy dynamique
Un décompilateur reconstitue beaucoup de choses à partir des appels de méthode : les API du framework que vous utilisez, la façon dont vos propres méthodes s’enchaînent, et même les noms d’origine des variables locales (déduits des types que vous appelez). Cet exemple utilise le package NuGet Babel Obfuscator pour activer le proxy dynamique, qui remplace les appels directs par des appels acheminés par des délégués proxy générés, de sorte que le graphe d’appels n’est plus lisible dans les métadonnées.
Pour utiliser cet exemple, vous avez besoin d’une licence de site pour Babel Obfuscator (édition Ultimate, Server ou Data Center). Le code source est disponible sur GitHub :
git clone https://github.com/babelfornet/dynamic-proxy-nuget-example.gitLa logique métier à masquer est un ensemble ordinaire de méthodes qui s’appellent entre elles et
appellent la bibliothèque .NET (src/PricingEngine/Pricing.cs) :
internal static class Pricing
{
public static decimal Quote(string sku, int quantity, string tier)
{
decimal unit = BasePrice(sku); // internal call
decimal discounted = ApplyTierDiscount(unit, tier);
decimal volume = ApplyVolumeBreak(discounted, quantity);
return Math.Round(volume * quantity, 2); // external (BCL) call
}
// BasePrice / ApplyTierDiscount / ApplyVolumeBreak …
}Après l’obfuscation, aucun de ces appels n’est plus direct : chacun passe par un proxy généré.
Le projet console PricingEngine active le proxy dynamique en Release avec une seule propriété :
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="Babel.Obfuscator" Version="12.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DynamicProxy>external=true;internal=true;true</DynamicProxy>
<FlattenNamespaces>true</FlattenNamespaces>
<OverloadedRenaming>true</OverloadedRenaming>
<BabelWarningsAsErrors>W00000</BabelWarningsAsErrors>
</PropertyGroup>Avec external=true, les appels vers la bibliothèque de classes de base .NET et vers les assemblies
référencés sont masqués, ce qui suffit à déjouer l’astuce par laquelle un décompilateur retrouve le
nom d’origine d’une variable à partir du type appelé. Avec internal=true, les appels entre
vos propres types sont masqués eux aussi, ce qui dissimule la logique de l’application. Le
proxy dynamique couvre les appels statiques, d’instance, virtuels,
d’interface et gĂ©nĂ©riques (LINQ compris), et laisse directs un petit nombre d’appels sensibles Ă
l’appelant, pour que le comportement ne change jamais.
Générer l’exemple en Release produit une sortie identique tout en remplaçant les appels directs d’origine du moteur de tarification par des types de délégués proxy générés : le flux d’appels que reconstitue un décompilateur n’est plus celui que vous avez écrit. Le proxy dynamique résiste aussi à la désobfuscation automatisée.