DEV Community

Skater
Skater

Posted on

Stop Letting Obfuscation Break Your Reflection Code: A Practical .NET Survival Guide

You've finished your .NET application, run it through an obfuscator, and everything looks great.
Then production starts throwing exceptions:
TypeLoadException
MissingMethodException
JsonSerializationException
Nothing changed in your business logic. Unit tests passed before protection. Yet the obfuscated build suddenly behaves differently.
If this sounds familiar, you're probably dealing with one of the most common .NET protection pitfalls: reflection-aware code colliding with aggressive symbol renaming. The source article discusses this exact challenge and explains that reflection-dependent applications require special handling during obfuscation.


The Day Reflection Meets Obfuscation
Most obfuscators protect applications by renaming classes, methods, properties, and fields.
For example:
MyApp.DataProcessor
might become:
a.a
and
ProcessData()
might become:
b()
Direct method calls usually continue working because the compiler and obfuscator maintain the internal references. The source article explains that standard .NET calls are linked through metadata and can be updated consistently during obfuscation.
Reflection doesn't have that luxury.
Reflection often depends on textual names at runtime.


A Real-World Example
Imagine a plugin system:
Type pluginType =
Type.GetType("MyApp.DataProcessor");
object plugin =
Activator.CreateInstance(pluginType);
Before obfuscation:
Lookup Target:
"MyApp.DataProcessor"
Metadata:
"MyApp.DataProcessor"
Result:
Success
After aggressive renaming:
Lookup Target:
"MyApp.DataProcessor"
Metadata:
"a.a"
Result:
Failure
The runtime still searches for the original name, but that name no longer exists in assembly metadata. The analyzed document specifically identifies Type.GetType() and related reflection APIs as common failure points after renaming.


Reflection Is Everywhere, Even When You Don't Think It Is
Many developers assume reflection is limited to specialized frameworks.
In reality, it powers a surprising amount of modern .NET infrastructure.
Applications frequently rely on:
• Dynamic plugin loading
• Dependency injection frameworks
• JSON serialization
• Runtime factory patterns
• WPF bindings and XAML
• Assembly scanning
• Custom attribute discovery
The source article highlights reflection APIs such as Type.GetType(), Activator.CreateInstance(), GetMethod(), and dynamic assembly loading as areas that should be reviewed before obfuscation.


The Wrong Solution: Disable Obfuscation Everywhere
A common reaction is:
"Reflection is breaking. Let's turn off obfuscation."
That fixes the runtime issue but removes much of the protection you were trying to achieve.
A better approach is understanding which identifiers form part of your runtime contract.
Only those identifiers need to remain stable.
Everything else can still be heavily protected.


Think in Terms of Contracts
Here's a useful way to evaluate your codebase:
Public Runtime Contracts
These are names your application actively searches for:
GetMethod("ProcessData")
Type.GetType("MyApp.DataProcessor")
These identifiers may need to stay unchanged.
Internal Implementation
This is the actual business logic:
public void ProcessData(string payload)
{
// processing logic
}
The name may remain visible, but the implementation can still be protected using control-flow transformations, string encryption, anti-tamper features, and other techniques. The source article notes that excluding names from renaming does not prevent additional protection layers from being applied.


How to Find Reflection Risks Before They Become Bugs
Before obfuscating a project, audit the codebase for patterns like:
Type.GetType(
GetMethod(
GetProperty(
Activator.CreateInstance(
Assembly.GetType(
Assembly.LoadFrom(
Each occurrence should raise a question:
Will this lookup still succeed if the target name changes?
If the answer is no, that member should become part of your protection strategy.
The source article recommends identifying reflection-dependent members before configuring obfuscation rules.


Serialization Can Fail Too
Many teams first encounter this issue through JSON processing rather than explicit reflection code.
For example:
public class Customer
{
public string FirstName { get; set; }
}
If a serializer expects:
{
"firstName": "Alice"
}
but property names have been aggressively renamed, deserialization can fail.
The analyzed article identifies JSON serialization as another common metadata-related problem after obfuscation.


WPF Developers Face an Additional Challenge
WPF introduces another metadata dependency layer.
XAML references classes, controls, resources, and bindings using names that must remain synchronized with compiled metadata.
The source article explains that WPF applications often require dedicated XAML/BAML-aware processing to prevent runtime parsing failures after obfuscation.
If you're protecting a desktop application, always include XAML validation in your testing process.


The Production Checklist I Recommend
Before shipping an obfuscated build:
✅ Scan for Reflection
Review every runtime type and method lookup.
✅ Define Exclusions Carefully
Preserve only the identifiers required by runtime contracts.
✅ Keep Strong Protection Enabled
Continue using:
• String encryption
• Control-flow protection
• Anti-tamper mechanisms
• Anti-decompilation features
The source article specifically recommends keeping string encryption and control-flow protection active even when selected names are excluded from renaming.
✅ Test the Obfuscated Binary
Not the original assembly.
Not the Debug build.
Test the protected output you'll actually distribute.
The analyzed article recommends running validation and integration testing against obfuscated assemblies before release.


A Simple Rule to Remember
If the runtime must discover a component by name, that name is no longer an implementation detail.
It's a contract.
Successful .NET protection isn't about hiding every identifier. It's about preserving the metadata your application needs while aggressively hardening the code that attackers want to understand.
When you treat reflection targets as contracts instead of obfuscation candidates, you can keep dynamic loading, serialization, plugins, and WPF functionality working while still gaining meaningful resistance against reverse engineering. The source article demonstrates this selective-protection philosophy as the recommended solution for reflection-dependent .NET applications.

Top comments (0)