Help us improve Softanics
We use analytics cookies to understand which pages and downloads are useful. No ads. Privacy Policy
Artem Razin
Low-level software protection engineer with 20+ years in native and managed code security. Creator of ArmDot, protecting commercial .NET applications since 2014.

Embedded Resource Protection in .NET: Encrypting Assembly Resources

Embedded resources are one of the most overlooked attack surfaces in .NET assemblies. Developers protect their code with obfuscation, encrypt their strings, and think about license validation - then ship an assembly where every image, string table, localization file, and configuration resource sits in plaintext, fully extractable by anyone with a standard tool.

This page covers what embedded resources are in the .NET context, what an attacker can do with unprotected ones, and how resource encryption removes them from the assembly's visible surface.

What embedded resources are - and what they are not

A clarification first, because the term "resources" means different things in different contexts.

Native PE resources are part of the Windows Portable Executable format itself: version information, the application icon visible in Windows Explorer, the application manifest. These are handled by the operating system and are not what this page covers.

.NET embedded resources are managed resources stored inside the assembly's metadata and accessed at runtime via Assembly.GetManifestResourceStream(). These are the resources a .NET developer embeds by setting a file's Build Action to "Embedded Resource" in Visual Studio. Common examples include string tables used for UI text, images used in the interface, localization files for different languages, audio files in game applications, binary data files, and license-related assets.

These managed embedded resources live in the assembly file alongside the CIL code. Without protection they are fully visible - any tool that can open a .NET assembly can list and extract them.

A category developers often overlook: compiled XAML

WPF, .NET MAUI, Avalonia, and other XAML-based frameworks add a category of embedded resources that most developers do not think about: compiled XAML.

When you build a WPF or MAUI project, every XAML file with Build Action set to "Page" or "Resource" is compiled into BAML - Binary Application Markup Language - and stored as an embedded resource inside the assembly. At runtime, the framework extracts the BAML and uses it to reconstruct the visual tree.

BAML is not opaque. Tools like ILSpy and JetBrains dotPeek include BAML viewers that decompile it back to readable XAML. An attacker who opens your WPF assembly can see your complete window and control hierarchy, all data binding expressions, style and template definitions, and any hardcoded strings or values in your markup - without touching any CIL code at all.

For applications where the UI design is proprietary - custom controls, carefully crafted user experiences, or UI that reflects business logic - protecting embedded resources also protects the compiled XAML. The BAML disappears from the resource table along with everything else.

What an attacker can do with unprotected resources

The most direct concern is intellectual property theft. Images, icons, and graphical assets embedded in an assembly represent real design work. An attacker can extract them and reuse them in competing software or counterfeit versions of your product.

Localization files and string tables often reveal more about an application's internals than developers realize. Feature names, internal error codes, unreleased UI strings, administrative options that are hidden from end users - all of this can be read directly from string table resources without touching any code.

For WPF, MAUI, and Avalonia applications, compiled XAML in the form of BAML is equally exposed. An attacker can decompile it back to readable XAML and see the complete window structure, data binding expressions, style definitions, and any hardcoded values in the markup - a detailed map of the UI that can accelerate understanding of the application's logic and structure.

There is also a modification threat. An attacker who wants to show the community they have cracked a piece of software can replace the application icon or splash screen with their own image and redistribute the modified binary. Protecting resources removes this option by making the resources inaccessible to standard modification tools.

For applications that embed license-related binary data - validation tokens, certificate data, encrypted reference values - resource protection adds a layer between that data and anyone who wants to analyze or tamper with it.

How resource encryption works in ArmDot

ArmDot's [ProtectEmbeddedResources] attribute removes the embedded resources from their standard location in the assembly and provides them on demand at runtime through an interception layer. When the .NET runtime requests a resource via GetManifestResourceStream(), ArmDot's protection code intercepts the request and returns the resource transparently - the application code does not know anything has changed.

From the developer's perspective the change is completely transparent. No code needs to be modified. Resource access works identically at runtime. The only difference is what someone sees when they open the assembly in a tool like ILSpy: the resources are no longer present in the standard resource table. An analyst can see that the assembly references resources and that something intercepts those requests - but the resources themselves are not available for extraction or inspection.

The implementation details are intentionally not documented, particularly because the underlying approach has had to evolve. .NET 9 introduced a breaking change that affected one resource protection mechanism - a runtime update changed behavior that had previously worked reliably across .NET versions. Adapting to that change required finding an alternative approach. This experience illustrates something worth understanding about obfuscation tool development in general.

The standard vs implementation gap

Building a .NET obfuscation tool means working at the intersection of what ECMA-335 documents and what actual .NET runtime implementations support. These are not always the same thing.

The standard describes a wide range of mechanisms and metadata structures. Not all of them are used by mainstream compilers. Not all of them are implemented identically across .NET Framework, .NET Core, and modern .NET. Some documented behaviors are supported in theory but have never been exercised by any major tool - which means runtime teams may not have tested edge cases, and behavior that works in one version may silently break in another.

For resource protection specifically, I have had to navigate exactly this gap. The approaches that work reliably are ones that are supported across the runtime implementations developers actually use - not just ones that are documented. Finding mechanisms that are unusual enough to confuse analysis tools while still being robustly supported by the runtime is the core engineering challenge. When the .NET 9 update broke an approach that had worked for years, it was a direct consequence of relying on behavior that the runtime team changed without it being considered a breaking change in the conventional sense.

This is part of why obfuscation tool maintenance is ongoing work rather than a solved problem. Every .NET release is a potential source of new constraints.

Applying resource protection in ArmDot

[ProtectEmbeddedResources] is an assembly-level attribute - it protects all embedded resources in the assembly. It cannot be applied to individual resources selectively.

// In AssemblyInfo.cs or any source file
[assembly: ArmDot.Client.ProtectEmbeddedResourcesAttribute()]

That is the complete configuration. All managed embedded resources in the assembly are protected. No other code changes are needed.

The attribute is assembly-level only because embedded resources belong to the assembly, not to specific types or methods. There is no per-resource granularity - it is all or nothing.

ArmDot removes the attribute from the output assembly after processing, as with all obfuscation attributes.

Resource protection as part of the obfuscation stack

Resource protection is a natural complement to string encryption. String encryption hides string literals embedded in the CIL; resource protection hides the content of files embedded in the assembly. Together they strip the assembly of the readable text and binary data that helps attackers understand what the application does and what assets it contains.

For applications where the embedded resources have genuine commercial value - professionally designed UI assets, proprietary audio, carefully crafted localization that represents significant translation investment - resource protection is worth applying as a default. The attribute takes one line to add and costs nothing at runtime.

For applications that do not embed sensitive resources, it is still worth applying - an attacker examining the assembly to understand it will find fewer useful signals if the resource table is empty.

Back to: .NET Obfuscation Techniques: A Technical Overview →

Protect your embedded resources with ArmDot

ArmDot encrypts and hides embedded resources with a single assembly-level attribute, with no changes required to your resource access code. Free trial available - protected assemblies stop working after two weeks.