How Demeanor Works
Demeanor operates on compiled .NET assemblies (DLLs and EXEs), not source code. It reads the ECMA-335 metadata and IL, applies layered transformations, and writes a new assembly that executes identically but resists decompilation. No source code access is needed — Demeanor integrates after compilation, either via MSBuild or as a standalone CLI tool.
The Pipeline
Every obfuscation run follows a strict sequence. Each pass builds on the previous one, and the order matters — encryption runs before renaming so injected helper types get renamed too.
Load & Validate
Read the PE file into a fully typed metadata model. Every table row, signature blob, and IL instruction is deserialized into object references — the original file is never read again. Validate the license and derive per-assembly encryption keys from it.
Analyze
Build virtual method override groups across the assembly set. Detect reflection usage (string-based GetMethod, GetProperty, Enum.Parse calls). Identify COM interop types, compiler-generated types, and serializable types. Determine which symbols must keep their original names.
Inject & Transform
Inject infrastructure types and rewrite IL method bodies. This is where the protection techniques are applied — all before renaming, so the injected code gets obfuscated too:
- Call hiding — replace direct calls with delegate-backed relay methods
- Constants encryption — replace integer constants with inline arithmetic
- String encryption — replace
ldstrwith calls to an injected decryptor using per-assembly keys - Resource encryption — compress and encrypt embedded resources, inject a runtime resolver
- Control flow obfuscation — flatten method bodies into switch-dispatched state machines
Rename
Assign obfuscated names to all non-excluded types, methods, fields, properties, events, and parameters. Names auto-propagate across the assembly set — renaming a TypeDef in one assembly automatically updates every TypeRef in every other loaded assembly. Inject MethodImpl rows for renamed virtual overrides.
Strip & Harden
Post-rename metadata cleanup and hardening:
- Strip property/event metadata rows (aggressive mode)
- Strip compiler attributes (
[CompilerGenerated],[Nullable], etc.) - Delete enum literal fields
- Inject watermark GUIDs and
[SuppressIldasm] - Write invalid
EncBaseIdto hinder reflection tools
Write
Rebuild the PE file from scratch — new metadata heaps, new table layout, new .text section with recomputed RVAs. The original PE bytes are never patched. Strong name re-signing is applied if configured. The output is a fully valid .NET assembly.
Why This Order Matters
- Inject before rename — the string decryptor, resource resolver, and call relay types are injected as normal types, then renamed like everything else. An attacker can't search for "DecryptString" — it's been renamed to
a. - Encrypt before CFG — control flow flattening operates on the already-encrypted IL. The constant encryption expressions become part of the flattened switch dispatch, creating compound complexity.
- Rename before strip — property/event metadata is stripped after renaming, so the report can record the original→obfuscated name mapping before the metadata is deleted.
- Rebuild, don't patch — writing the PE from scratch ensures correct table layout, heap compression, and RVA alignment regardless of what transformations were applied. No fragile byte-patching.
What Stays the Same
Obfuscation preserves execution semantics. The obfuscated assembly:
- Passes the CLR verifier (peverify / ILVerify)
- Loads in the same runtime contexts as the original
- Has the same public API surface (unless
--include-publicsis used) - Produces the same outputs given the same inputs
- Works with NativeAOT, ReadyToRun, and single-file publish