typetechniqueconfidencehighcreated2026-06-25updated2026-06-25obfuscationdotnetevasionanti-analysisresearch-target

Unicode Control-Character Name Obfuscation

.NET malware technique where all type names, method names, and sometimes field names are replaced with sequences of Unicode control characters (C0/C1 controls such as U+0002, U+0003, U+0005) and zero-width format characters (U+2000U+200F). This breaks naive string-based clustering, renders decompiler output unreadable, and defeats tools that expect printable identifiers.

Detection / Fingerprint

  • dnfile / dnlib enumeration shows TypeDef and MethodDef tables filled with \u0002, \u0003, \u0005 combined with zero-width spaces
  • No human-readable class or method names remain after obfuscation
  • Strings output may show the raw control bytes as unprintable sequences
  • Ghidra / ILSpy / dnSpy output is dominated by identical-looking or unrenderable identifiers

Implementation Patterns Observed

In sample d9c0bc24 (unclassified-dotnet-whisper):

  • 40+ TypeDefs, all named \u0002, \u0003, \u0005 with zero-width variants
  • 60+ MethodDefs, similarly renamed; only .ctor, .cctor, Dispose survive
  • Module name vVth.exe is preserved (likely needed for PE loader compatibility)
  • Namespace Whisper.Properties.Resources is preserved (likely referenced by the obfuscator's resource handling)

In sample 5f54948e (unclassified-dotnet-crypter-loader sibling):

  • 7 TypeDefs, all renamed to Hangul jamo / CJK Unified Ideograph Extension A / Arabic Extended-A sequences (e.g., ㇣ㇿᄊᇧᇕㅩ㈢ꦅᅜᅂ)
  • 32 MethodDefs; most are obfuscated, but plaintext .ctor, get_Tag, set_Tag, HookProc, RunDialog, OnHelpRequest, OwnerWndProc, Reset, VirtualProtect, LoadLibrary, GetProcAddress, and .cctor survive as unobfuscated symbols
  • Assembly name ㇣ㇿᄊᇧᇕㅩ㈢ꦅᅜᅂ — same character-set family as the type names
  • Module name ᅮㅾ㇂㇗ᇩㅛᇾㆲᆔᆄᆃ㈭ᇱᇊᇅ㇔ㆵힰꦫ㈛ᅶ — extended Unicode block mixing Hangul, Khmer, Myanmar, Georgian, and CJK extensions
  • This is the same crypter-loader codebase as siblings 4e31a886 and 673e6738 (shared method names ComplementaryStrings, Get_AmsiOpenSession_Byte, etc.), demonstrating that Unicode name obfuscation can be layered on top of an existing unobfuscated builder without changing the underlying logic ^[/intel/analyses/5f54948ef4ea19feac07408f4109df54c07137d35b34804250cd20eeccfcd254.html]

Reproduce on Your Own VMs

  1. Build a simple .NET Framework 4.0 Console app in Visual Studio.
  2. Download dnlib (https://github.com/0xd4d/dnlib) or install via NuGet.
  3. Post-build, run a dnlib script to rename all types and methods:
// dnlib rename script (run as post-build step)
var mod = ModuleDefMD.Load("YourApp.exe");
uint cp = 0x0002;
foreach (var type in mod.Types) {
    if (type.IsGlobalModuleType) continue;
    type.Name = new UTF8String($"\u0002\u2000\u2001{(char)(cp++)}");
    foreach (var method in type.Methods) {
        if (method.IsConstructor || method.IsStaticConstructor) continue;
        method.Name = new UTF8String($"\u0003\u2002\u2003{(char)(cp++)}");
    }
}
mod.Write("YourApp_obf.exe");
  1. Verify with dnfile or dnlib: all TypeDefs and MethodDefs should show control-character names.
  2. Open in dnSpy — the tree view should be unreadable.

Defensive Countermeasures

  • Do not rely on string-based IOCs for .NET malware when this obfuscation is present
  • Use capa / behavioral detection (API call patterns) instead of symbol names
  • Monitor for GetManifestResourceStream + RijndaelManaged / AesManaged sequences in .NET process telemetry
  • Track by MVID (.NET module GUID) rather than type names

Pages Where Observed