Home / Documentation / Reflection-Safe Obfuscation with Skater
Practical Field Guide

Safeguarding Dynamic Type Resolution When Obfuscating .NET Assemblies with Skater

A field-oriented walkthrough showing how to keep Type.GetType, GetMethod, and Activator.CreateInstance functional after Skater renames symbols.

Rustemsoft Field Engineering • Target: .NET reflection + obfuscation • Updated for Skater 26.1

Core Principle

Reflection resolves types and members by string name at runtime. If Skater renames those names, the lookup fails. The solution is selective exclusion: tell Skater to leave only the reflection-critical identifiers alone, while still encrypting strings and scrambling control flow everywhere else. The rest of this guide shows exactly how to do that inside the Skater UI or via attributes.

1 Typical Breakage Scenarios

Most production .NET codebases contain at least one of the following patterns. Each of them becomes a runtime landmine the moment an obfuscator rewrites the underlying metadata names.

Plugin / Extension Loaders

Assemblies discovered at runtime and instantiated via Assembly.Load + Type.GetType.

Factory / DI Containers

Configuration-driven type creation that stores fully-qualified names in JSON, XML, or databases.

Serialization Contracts

System.Text.Json, Newtonsoft.Json, or DataContractSerializer mapping properties by name.

WPF / XAML Bindings

Code-behind types and event handlers referenced by string inside BAML resources.

2 Why Reflection Is Fragile Under Renaming

Static calls are rewritten by the obfuscator in both the caller and the callee, so they continue to work. Reflection, however, carries the original name as a literal string (or as a value loaded from configuration). When the metadata table no longer contains that string, the runtime returns null or throws.

The Mismatch in One Sentence

Your source still asks for "MyApp.PaymentGateway", but after a full rename the assembly only contains "a.b". The lookup fails.

3 Representative Code That Will Break

The following factory is intentionally simple yet realistic. It is the kind of code that works perfectly in Debug and Release builds and then crashes the moment aggressive renaming is applied.

PluginFactory.cs
using System;
using System.Reflection;

namespace MyApp.Plugins
{
    public interface IGateway
    {
        void Send(string payload);
    }

    public class PaymentGateway : IGateway
    {
        public void Send(string payload)
        {
            Console.WriteLine($"Gateway received: {payload}");
        }
    }

    public static class PluginFactory
    {
        public static IGateway CreateFromConfig(string typeName)
        {
            // string is often loaded from config or a database
            Type t = Type.GetType(typeName);
            if (t == null)
                throw new TypeLoadException($"Cannot resolve {typeName}");

            return (IGateway)Activator.CreateInstance(t);
        }
    }
}

After a naïve rename, Type.GetType("MyApp.Plugins.PaymentGateway") returns null and the factory throws.

4 Skater Exclusion Workflow

Skater gives you two complementary ways to keep the critical names intact. Use whichever fits your process.

A

Attribute-Driven (Preferred for Teams)

Decorate the types and members that must remain discoverable:

[System.Reflection.Obfuscation(Exclude = true, ApplyToMembers = true)]
public class PaymentGateway : IGateway { ... }

Skater honors the attribute automatically; no further UI action is required for those symbols.

B

UI Tree Exclusion

1. Open the assembly in Skater.
2. Expand the namespace tree until you reach the reflection targets.
3. Clear the “Rename” checkbox on the type and on each required member.
4. Leave string encryption and control-flow options enabled for everything else.

C

Project File Persistence

Save the exclusion rules into a Skater project file so the same settings can be replayed from the command line or a CI pipeline.

5 Decompiler Side-by-Side

After selective exclusion the public surface that reflection needs remains readable, while the method bodies are still heavily transformed.

Full Rename (Broken)
// names gone, reflection fails
namespace a
{
public class b : c
{
public void d(string A_0) { ... }
}
}
Selective Exclusion (Working)
// names kept, bodies scrambled
namespace MyApp.Plugins
{
public class PaymentGateway : IGateway
{
public void Send(string payload)
{
// opaque predicates + encrypted strings
int n = 0x8F2A1;
while (true) { switch (n ^ 0x3C) { ... } }
}
}
}

6 Validation Checklist

  • ✓ Run the full test suite against the obfuscated output, not the original Release build.
  • ✓ Force every reflection path (plugin load, serializer round-trip, dynamic factory) to execute.
  • ✓ Watch for TypeLoadException, MissingMethodException, and null returns from GetType/GetMethod.
  • ✓ If you ship plugins, verify that externally supplied type-name strings still resolve after the host is obfuscated.

7 Failure Matrix

Observed Exception Likely Cause Skater Remedy
TypeLoadException Type name renamed Exclude type via attribute or UI checkbox
MissingMethodException Method name renamed Exclude the specific method node
JsonException / SerializationException Property names no longer match JSON keys Exclude DTO properties or add explicit name attributes
XamlParseException BAML still points at old type names Enable Skater’s WPF/XAML protection option

8 Hardening Recommendations

Prefer nameof() where possible

Even when you still need a string, starting from nameof(PaymentGateway) makes the exclusion surface easier to maintain.

Keep public contracts thin

Put reflection-facing types in a small, well-documented surface area and apply maximum protection to everything behind it.

Automate the exclusion list

Store the Skater project file in source control and invoke the CLI from your build pipeline so exclusions never drift.

9 Frequently Asked Questions

Further Reading on skaterpro.net