Symbol Renaming in .NET Obfuscation: The Permanent First Layer
Symbol renaming is the first thing any .NET obfuscator does and, in one specific sense, the most permanent protection available. It does not hide the structural logic of your instructions - the branching, the data flow, the call graph - but it hides the intent that names communicate. A method named ValidateSerialKey tells an attacker exactly what it does before they read a single instruction. The same method renamed to getCompilationRelaxationsStaticIndexRangePartitionForArray actively misleads - the name suggests something about compilation and array partitioning, which has nothing to do with what the method actually does. Renaming makes the code harder to navigate and understand, removes the developer's own documentation from the binary, and does so permanently - unlike every other obfuscation technique, the transformation cannot be undone.
Why names matter so much in .NET assemblies
A .NET assembly compiled from C# source preserves the names of every type, method, property, field, event, and parameter in its metadata. These names are not incidental - they are required by the runtime for reflection, serialization, data binding, and interoperability. They are stored as plaintext strings in the metadata tables and are immediately visible to anyone who opens the assembly in a decompiler.
The practical consequence is that unobfuscated .NET code is nearly self-documenting. A class named LicenseManager with a method named ValidateSerialKey and a field named _trialDaysRemaining tells an attacker exactly where to look, what to look for, and what the code is trying to accomplish - before they have read a single instruction. The decompiler output is essentially commented code, with the developer's own intent expressed through their naming choices.
Remove the names and that map disappears. A class named getCompilationRelaxationsStaticIndexRangePartitionForArray with methods named getSinglePassForwardIteratorBlockingStateDispatchTableEntry tells an attacker nothing. The code still executes correctly - the runtime uses numeric metadata tokens for all internal references, not string names - but a human trying to navigate it has no landmarks.
The contrast that makes renaming obvious
When I first encountered .NET at university - Microsoft was handing out books, promoting the platform - I had spent years doing security research on native unmanaged applications. In native code, a reverse engineer sees registers and memory addresses. There are no names. Reconstructing what a function does requires reading raw instructions, understanding calling conventions, tracing data through memory. It is slow, expert work.
Then I looked at compiled .NET code for the first time. Class names. Method names. Parameter names. Field names. All of it sitting there in plaintext, exactly as the developer typed it. The code was essentially self-documented. My immediate thought was: who would ever use .NET for commercial applications? Anyone could crack a program like this in an afternoon.
The first and most obvious response to that concern is also the simplest: remove the names. If the names are what make the code readable, take them away. What remains is still CIL - still structurally more readable than native assembly - but the navigational layer that makes reverse engineering fast and accessible is gone. Symbol renaming is the direct answer to the self-documentation problem that .NET introduced.
How symbol renaming works
The .NET runtime resolves method calls using metadata tokens - numeric indices into the assembly's metadata tables - rather than by name. When a method calls another, the CIL contains a token reference, not a string. This means you can change every name in the assembly to anything at all, and as long as you update all the references consistently, the assembly will execute identically.
ArmDot scans the assembly and builds a complete map of every renameable identifier. It then replaces each name with one drawn from a dictionary built from .NET runtime internals - actual class names, method names, and field names from the .NET framework itself, combined and recombined into new identifiers.
The choice of this approach was deliberate. Many obfuscators replace names with non-printable Unicode characters or single-character tokens. That works technically, but it is immediately obvious - any developer who opens the assembly in ILSpy and sees a class named \u0001 knows instantly that the code has been obfuscated. ArmDot's names look like real .NET identifiers. They have structure, reasonable length, and plausible-sounding content. The method is not doing what its name suggests - but the name looks like it could be real, which makes the obfuscation less conspicuous and the code more confusing to navigate.
What renaming protects and what it does not
Renaming is the most effective technique against the specific attack of navigating code by identifier. An analyst trying to find the license validation routine by searching for ValidateSerialKey will find nothing. The method exists - it is there, doing exactly what it did before - but its name no longer reveals its purpose. Finding it requires actually reading the code, which is significantly slower and more difficult than following named landmarks.
What renaming does not protect: the logic of your code. The instructions, the branching structure, the data flow - all of that is still present in CIL form and still reconstructable by a decompiler. An analyst who is willing to read nameless code can still understand what a method does. Renaming raises the entry cost for reverse engineering; it does not prevent it.
The one property that distinguishes renaming from every other obfuscation technique is that it is truly irreversible. String encryption can be undone by running the application and observing decrypted values. Control flow obfuscation can theoretically be reversed by reconstructing the original branching structure. Code virtualization can be analyzed by studying the VM interpreter. Renaming cannot be reversed at all - the original names are not stored anywhere in the protected assembly. There is nothing to restore from.
What is safe to rename and what is not
Not everything in a .NET assembly can be renamed safely. The key question is whether any external code - code not included in the obfuscation run - references an identifier by name.
Safe to rename: private and internal types, methods, fields, and properties. Anything that is only ever referenced by other code in the same assembly. Namespace names in application code.
Never ship your PDB files. PDB (program database) files contain debug symbols - including the original names of types, methods, and fields before renaming, mapped back to the obfuscated identifiers. If you ship a PDB alongside your protected assembly, an attacker can load both in a debugger and recover the original names, undoing the primary benefit of renaming. PDB files belong in your build artifacts and your crash reporting pipeline - never in your distribution package.
Requires care: public members of types that are used only internally. If a public method is only called from within the same assembly, renaming it is safe. If it is called from another assembly that is not being obfuscated at the same time, renaming it will break the caller.
Not safe to rename by default: public API surface of libraries. If you are shipping a NuGet package or a DLL that other developers reference, your public types and members are part of a contract. Renaming them breaks every project that depends on you. The same applies to any names resolved by reflection at runtime - types loaded by Type.GetType("MyNamespace.MyClass"), methods invoked via MethodInfo, WPF and MAUI data binding targets that resolve property names at runtime.
ArmDot does not rename public members by default for exactly this reason. The safe default is to leave the public API surface intact and rename everything internal. When you explicitly want to rename a public member - for example, a public method in an application that has no external callers - you can opt in with [ObfuscateNames] applied directly to that member.
Applying symbol renaming in ArmDot
ArmDot uses the [ObfuscateNames] attribute from the ArmDot.Client NuGet package.
Renaming all internal identifiers in the assembly:
[assembly: ArmDot.Client.ObfuscateNames]This is the standard starting point. All non-public types, methods, fields, and properties across the assembly will be renamed. Public members are left intact.
Explicitly renaming a public member:
public class ProductActivation
{
// Public method with no external callers - safe to rename explicitly
[ArmDot.Client.ObfuscateNames]
public bool CheckActivationStatus(string key)
{
...
}
}Excluding a specific member from renaming:
[ArmDot.Client.ObfuscateNames]
class InternalProcessor
{
// This field is accessed by name via reflection - must not be renamed
[ArmDot.Client.ObfuscateNames(Enable = false)]
private string _configurationKey;
}The most important thing to verify after applying assembly-wide renaming is that any member accessed by name at runtime - via reflection, a serializer, or a data binding - has been explicitly excluded with [ObfuscateNames(Enable = false)]. ArmDot outputs a warning when it detects common patterns that suggest a name might be used reflectively, but it cannot catch every case. Testing the protected assembly thoroughly before shipping is always necessary.
Renaming as the baseline for every obfuscation strategy
Symbol renaming is never sufficient on its own for protecting valuable logic, but it should always be part of the stack. The performance cost is essentially zero - the renamed assembly runs at the same speed as the original. The protection it provides - removing every developer-supplied name from the binary permanently - is a baseline that every other technique builds on.
Apply [assembly: ObfuscateNames] to every commercial .NET project. Then layer string encryption, control flow obfuscation, and virtualization on top for the code that needs stronger protection.
Back to: .NET Obfuscation Techniques: A Technical Overview →
Protect your .NET identifiers with ArmDot
ArmDot renames types, methods, fields, and properties using dictionary-based names drawn from .NET runtime internals - obfuscated names that look natural rather than obviously mangled. The [ObfuscateNames] attribute integrates into your build via NuGet. Free trial available.
