Skip to content

fix: obfuscated assemblies crash on .NET 9+ with resources/constants/compressor (#101) - #104

Merged
mcpolo99 merged 2 commits into
developfrom
101-resources-badimageformat-net9
Sep 21, 2026
Merged

mcpolo99 merged 2 commits into
developfrom
101-resources-badimageformat-net9

Conversation

@mcpolo99

Copy link
Copy Markdown
Owner

Fixes #101

Problem

Obfuscating an assembly with the resources protection produced a binary that crashed at startup on .NET 9 and later:

System.BadImageFormatException: Enclosing type(s) not found for type '…' in assembly '…'

The runtime no longer accepts types nested directly under the module type <Module> (see dotnet/runtime#111164). ConfuserEx relied on that previously-tolerated layout for its injected helper types, so any assembly using the affected protections became unloadable on modern .NET.

Root cause

Two separate sources of <Module>-nested types:

  1. Directly-created data types — the resources, constants, and compressor protections each add a FieldRVA-backed ValueType as a nested type of the global type.
  2. Injected runtime helpers — InjectHelper.Inject(runtimeType, GlobalType, module) copies a runtime helper's members into <Module>. When that helper itself contains nested types (the LZMA decompressor Confuser.Runtime.Lzma has six), those nested types are recreated nested under <Module>. This path is shared by resources, constants, and the packer, and is the reason a data-type-only fix is insufficient.

Fix

  • InjectHelper: when injecting into the global type, promote directly-nested helper types to top-level module types (with adjusted visibility). This centrally covers every protection that injects runtime helpers.
  • resources / constants / compressor: create the data ValueType as a top-level type (NotPublic) instead of nested under <Module>.
  • RickRoller (renamer easter-egg): same defect, same fix — its trap type is now top-level.
  • Confuser.Runtime.Resource: also hook ResourceResolve and match manifest resource names, so GetManifestResourceStream works after obfuscation. This mirrors the already-present Resource_Packer runtime.

All changes keep the output compatible with every target framework (net2.0 through net10).

Test

Adds Console_Net10_ResourceProtection, the first cross-framework test that obfuscates and runs a modern-.NET app: it embeds a manifest resource, applies resources, executes the obfuscated net10.0 assembly, and asserts it exits 42 with the resource read back correctly. Without the fix this test reproduces the exact BadImageFormatException from the report.

TestBase gains support for passing non-assembly external files (e.g. runtimeconfig.json) through to the output without treating them as modules.

Verification

  • New net10 resource test passes; confirmed it fails with the reported exception before the fix.
  • Full local CI green: lint, build (incl. C++/CLI), 189 tests passed / 0 failed, packaging.

Credit

Root cause originally reported by @jeremy-visionaid, who also provided an initial draft and regression-test scaffold.

@mcpolo99
mcpolo99 merged commit ec95a20 into develop Sep 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant