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.

1

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.

2

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.

3

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 ldstr with 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
4

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.

5

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 EncBaseId to hinder reflection tools
6

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-publics is used)
  • Produces the same outputs given the same inputs
  • Works with NativeAOT, ReadyToRun, and single-file publish