Extract and read embedded resources from a .NET assembly with Glass.NET
A DLL carries more than code. Baked into the same file are embedded .resources and ResX strings, localized satellite strings, whole files like config, JSON, SQL and certificates, and string and byte constants. Here is how to find and read all of it in Glass.NET — the free, static-only decompiler that never runs the assembly — and export it to disk for inspection outside the tool.
A compiled .NET assembly is not only code. Baked into the same file, next to the IL, is a surprising amount of data: .resources string tables compiled from .resx, localized strings in satellite assemblies, whole files someone chose to embed — a config, a JSON seed, a SQL script, a certificate, an icon — and the string and byte constants written straight into the code. When you need to get at what a DLL ships besides its methods, that is the material you are after.
The reasons come up constantly. You suspect a vendor left a hardcoded connection string or an internal endpoint in a release build, and you want to confirm it. You lost the source for an old tool and need to recover the config or asset it was built with. You are vetting a dependency and want to see exactly what files it carries. Or you simply want to read a library’s localized UI strings to check a translation. All of that is sitting in the binary, readable without the source.
Glass.NET reads it. It is free, and it is strictly static — it reads metadata, IL and the resource blob and reconstructs C#, and never executes the assembly it inspects. That distinction matters more here than anywhere: reading an embedded resource is reading stored bytes out of the file, not watching the code run. So extracting resources from a binary you would never dare execute is completely safe — nothing in it runs when you open it, read a resource, or export it.
What is actually in there
It helps to know the shapes you will meet before you go looking:
The first three are managed resources, stored in the assembly manifest; the fourth lives in the IL itself and shows up in the decompiled C#. Glass handles both, by slightly different routes.
Open the assembly
Find the DLL first. For a NuGet dependency it is in the global packages cache — %USERPROFILE%\.nuget\packages\<id>\<version>\lib\<tfm>\ on Windows, ~/.nuget/packages/... elsewhere. For a vendor drop or an old build of your own, it is wherever it landed, usually bin/. Open it in Glass.NET by dragging it onto the window, or File ▸ Open Assembly. A plain managed .dll is enough — no PDB, no source. And because opening and reading are static, a binary you do not trust is safe to open: Glass never runs it.
Find the resources in the tree
Expand the assembly and go to its resources node — it sits alongside the namespace ▸ type ▸ member tree. Glass lists every embedded resource the manifest declares: the .resources streams compiled from .resx, and any raw files the author embedded. This list alone is informative. A Strings.resources is expected; an appsettings.Production.json, a migrations.sql, a private.pfx or an unexplained .bin is the kind of thing you opened the assembly to find.
Select a .resources stream and Glass shows its string table — the resource names and their values — so you can read the embedded strings directly: error messages, templates, UI labels, and the occasional connection-string template someone parked in a resource instead of config. Select a raw embedded file and you can inspect its contents in place; for a text file like JSON or SQL that is often all you need to read it.
For localized strings, the culture-specific values are not in the main DLL at all — they are in satellite assemblies, a MyApp.resources.dll under a culture folder like fr/ or de/. A satellite is just an assembly with its own .resources stream, so open it in Glass exactly as you opened the main one and read its string table the same way. That is how you check what a given translation actually ships.
Read the string and byte constants in code
Not everything “embedded” is a managed resource. A hardcoded endpoint, a default password, a license salt or an embedded byte array often lives as a constant in the code — a string literal or a byte[] initializer in the IL, not an entry in the resource blob. These surface when you decompile the type: click the member and read the reconstructed C#, and the literal is right there in the source. To sweep for them, decompile the type that looks relevant and read it, or use Find All References (Shift+F12) on the API that consumes the value — every new SqlConnection, every HttpClient base address — to land on each call site and see the literal in context. That is how a connection string a vendor “hid” in a const string comes straight out. (How to decompile a .NET DLL to C# covers the reading workflow in full.)
Export to a project to get the files on disk
Reading a resource in the tree answers “what is in here?” The moment you want the resource files themselves — to open a certificate in your own tooling, run a SQL script through a real client, diff a recovered config against your current one, or feed a JSON seed into a test — you want them written to disk, outside Glass, as ordinary files. That is what export does.
File ▸ Export to Project reconstructs the whole assembly to a buildable .csproj, and it extracts the embedded resources alongside the source — the .resources and the raw embedded files are written out as files in the project folder. From a script, the glass export subcommand does the same headlessly:
glass export Vendor.Sync.dll --output ./Vendor.Sync.Source
Now the resources are on disk next to the reconstructed .cs, and your normal tools are back: open the folder, grep the extracted files for a suspected secret, load the certificate, run the SQL. Because the export is still static reconstruction, it never ran the binary to produce any of it — you can export an untrusted DLL and inspect its payload entirely on your own terms. The full deep-dive on export, including where a large assembly needs a short manual pass, is in turning a DLL back into a buildable project.
The honest part: stored is not protected
Everything above works because the CLR does not encrypt embedded resources. A .resources string table is UTF-8 text with length prefixes; a raw embedded file is its exact bytes; a constant is a literal in the IL. The framework’s own ResourceReader and GetManifestResourceStream exist to walk this, and any resource viewer — Glass included — is a front end over the same stored data. “I embedded it in the DLL” is not the same as “I protected it.” Reading it is a double-click, not a crack.
The practical consequence cuts both ways. When you are auditing someone else’s binary, it means the payload is trivially recoverable, which is exactly what makes a static reader useful — see inspecting a NuGet package before you trust it for the broader vetting workflow. But turn the lens on your own shipped assemblies: if you point Glass at your release build and find a live connection string, an API key, or a certificate in a resource or a const, that is a finding, not a curiosity. Treat it the way you would any leaked credential — rotate the secret and move it out of the binary. Shipping a secret in an assembly is shipping it to everyone who downloads the assembly.
If you genuinely must carry data in the binary that you would rather not hand out in the clear — a licensed data file, templates you consider proprietary — that is where the sibling obfuscator Nebula.NET earns its place: it can encrypt embedded resources and strings at build time and decrypt them transparently behind your existing ResourceManager and GetManifestResourceStream calls, so a resource viewer sees ciphertext instead of your SQL. The deep-dive on that is encrypting embedded resources, and the matching treatment for literals in code is string encryption in .NET. This is standard Nebula protection — resource and string encryption, not a high tier. Just be clear about what it buys: it raises recovery from “read” to “reverse-engineer.” It does not make a secret you already shipped safe. A rotated secret is the fix; encryption is how you avoid shipping the next one in the clear.
The point
A DLL ships its resources as plainly as its code. Open it in Glass.NET, expand the resources node to list the .resources, satellite strings and embedded files, read the strings and inspect the files in place, decompile the types to catch the constants baked into the IL, and export to a project when you want the files on disk to inspect outside the tool. In minutes you can recover a lost config, read a dependency’s payload, or confirm the hardcoded endpoint you suspected — all without running a single line, because reading a stored resource is reading bytes, not behaviour. And if the thing you find is a secret in your own build, you have found the bug worth fixing. Download Glass.NET, open the assembly, and see what it is really carrying.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.