Hiding the Call Graph with Dynamic Proxy
A decompiler reconstructs a lot from method calls: which framework APIs you use, how your own methods chain together, and even the original names of local variables (inferred from the types you call). This example uses the Babel Obfuscator NuGet package to enable Dynamic Proxy, which replaces direct calls with calls routed through generated proxy delegates so the call graph is no longer readable from the metadata.
To use this example you need a site license for Babel Obfuscator (Ultimate, Server or Data Center editions). The source code is available on GitHub:
git clone https://github.com/babelfornet/dynamic-proxy-nuget-example.gitThe business logic to hide is an ordinary set of methods that call each other and the .NET library
(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 …
}After obfuscation none of these calls is direct any more — each jumps through a generated proxy.
The PricingEngine console project enables Dynamic Proxy in Release with a single property:
<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>With external=true, calls into the .NET base class library and referenced assemblies are hidden —
this alone defeats the trick where a decompiler recovers a variable’s original name from the type it
calls. With internal=true, the calls between your own types are hidden as well, obscuring the
application’s logic. Dynamic Proxy covers static, instance, virtual,
interface and generic (including LINQ) calls, and leaves a small set of caller-sensitive calls direct
so behavior never changes.
Building the sample in Release produces identical output while replacing the original direct calls of the pricing engine with generated proxy delegate types — the call flow a decompiler reconstructs is no longer the one you wrote. Dynamic Proxy also resists automated deobfuscation.