Skip to Content
New release 12 available 🎉

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.git

The 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.

Last updated on