typeanalysisfamilyunclassified-dotnet-whisperconfidencelowdotnetobfuscationevasioncode-injectionc2malware-family
SHA-256: d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68

unclassified-dotnet-whisper: d9c0bc24 — Whisper-namespace .NET loader with Unicode control-character obfuscation and BouncyCastle crypto

Executive Summary

A .NET Framework 4 PE32 loader/dropper compiled August 2024, distributed as TTcopy_pdf.exe (double-extension masquerade). The binary carries the internal module name vVth.exe and namespace Whisper.Properties.Resources, embeds a ~919 KB encrypted manifest resource, and imports a custom BouncyCastle.CryptoExt assembly for AES/RSA operations. All type and method names are obfuscated with Unicode control characters and zero-width spaces, rendering standard decompilation noisy. No CAPE detonation was possible (no Windows guest); behavior is inferred from static artifacts and capa hits.

What It Is

Field Value
SHA-256 d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68
File name TTcopy_pdf.exe ^[metadata.json]
File type PE32 executable (GUI) Intel 80386 Mono/.Net assembly, 3 sections ^[file.txt]
Size 1,020,416 bytes (996 KB) ^[metadata.json]
Compile time Tue Aug 20 01:57:42 2024 UTC ^[pefile.txt:34]
.NET version .NETFramework,Version=v4.0 ^[strings.txt:2052]
Module name vVth.exe ^[strings.txt:1668]
Namespace Whisper.Properties.Resources ^[strings.txt:1676]
Signed No ^[rabin2-info.txt:27]
YARA PE_File_Generic only ^[yara.txt]

The binary was pulled from OpenCTI with labels exe, malware-bazaar and no family attribution. ^[metadata.json] Triage promoted it to deep tier because of the lack of family label. ^[triage.json]

How It Works

Build stack

  • Compiler: .NET Framework 4.0, C# (evidenced by mscorlib, System, System.Windows.Forms, System.Drawing, System.Core references) ^[strings.txt:1669-1673]
  • Linker version: 6.0 (PE32 optional header) ^[pefile.txt:46]
  • Obfuscator: Unknown — not ConfuserEx (no #=q…== mangling), not SmartAssembly (no String::Decrypt pattern). Instead, every TypeDef and MethodDef is renamed to sequences of Unicode control characters (U+0002, U+0003, U+0005) combined with zero-width spaces (U+2000U+200F). ^[dnfile analysis] This defeats string-based clustering and makes Ghidra/dnSpy output unreadable.
  • Crypto library: Custom BouncyCastle.CryptoExt assembly referenced (variant of the open-source BouncyCastle C# crypto library). ^[strings.txt:1674] Capa confirms AES encryption, RSA decryption, and SHA256 hashing. ^[capa.txt]

Embedded payload

The binary contains three manifest resources:

  1. \u0002\u2000\u2001.resources — small, private ^[dnfile analysis]
  2. Whisper.Properties.Resources.resources — 67,860 bytes, public ^[dnfile analysis]
  3. A large private resource (name rendered as control-character whitespace) at offset 943,496 — this accounts for roughly 92% of the binary's total size (~919 KB of the 996 KB .text + .rsrc). ^[binwalk.txt:10] ^[dnfile analysis]

The resource at offset 943,496 is the likely encrypted second-stage payload. The capa hit access .NET resource (2 matches) ^[capa.txt] and the presence of GetManifestResourceStream ^[strings.txt:1984] support a runtime decryption → reflective load chain. The pattern matches the dotnet-manifest-resource-decryption concept observed in other .NET loaders.

Static TTPs (inferred — no dynamic execution)

ATT&CK Technique Evidence
T1027 Obfuscated Files or Information Unicode control-character naming; no human-readable symbols ^[dnfile analysis]
T1140 Deobfuscate/Decode Files or Information AES + RSA crypto via BouncyCastle; resource stream retrieval ^[capa.txt]
T1620 Reflective Code Loading invoke .NET assembly method capa hit; GetManifestResourceStream → likely Assembly.Load ^[capa.txt] ^[strings.txt:1984]
T1083 File and Directory Discovery enumerate files in .NET (4 matches) ^[capa.txt]
C0001.008 TCP Client act as TCP client ^[capa.txt]
Process creation create process in .NET ^[capa.txt]
Thread suspension suspend thread (3 matches) ^[capa.txt]

C2 Infrastructure

No hardcoded C2 strings recovered. The TcpClient class is imported ^[strings.txt:1785] and capa flags TCP-client capability, but no IPs, domains, or URLs appear in plaintext strings or decompiled output. C2 is likely runtime-decrypted from the large manifest resource or fetched post-execution. This is consistent with the dotnet-manifest-resource-decryption pattern where configuration lives inside the encrypted resource blob.

Decompiled Behavior

Ghidra analysis of the PE32 wrapper is limited because the entry point is a thunk to MSCOREE.DLL::_CorExeMain ^[ghidra:entry @ 004f9b2a]; the real logic lives in the CLR metadata. dnfile enumeration reveals:

  • Module: vVth.exe ^[dnfile analysis]
  • MVID: 91e700a8-de23-4695-78b2-934b06df8de0 ^[dnfile analysis]
  • TypeDefs: 40+ entries, all renamed to control-character sequences (\u0002, \u0003, \u0005 with zero-width variants). No readable class names remain. ^[dnfile analysis]
  • MethodDefs: 60+ methods, similarly renamed. Only .ctor, .cctor, Dispose, and a few numeric placeholders are recognizable. ^[dnfile analysis]
  • AssemblyRefs: mscorlib, System.Core, System.Windows.Forms, System, System.Drawing, BouncyCastle.CryptoExt ^[dnfile analysis]
  • CustomAttributes: 20+ present; SuppressIldasmAttribute is likely among them (common for obfuscated .NET), though the exact attribute type could not be resolved from raw table dumps. ^[dnfile analysis]

The .text section entropy is 7.90 ^[pefile.txt:92], which is high for managed code and suggests the embedded resource or additional obfuscation layers are mapped into the code section.

Interesting Tidbits

  • Double-extension masquerade: TTcopy_pdf.exe exploits Windows "Hide extensions" to appear as a PDF. This matches the double-extension-masquerade technique and the social-engineering-filename-lure concept. ^[metadata.json]
  • BouncyCastle customization: The assembly is named BouncyCastle.CryptoExt rather than the standard BouncyCastle.Crypto, suggesting the author modified or repacked the library. ^[strings.txt:1674]
  • Empty version info: All VS_VERSIONINFO fields are 0.0.0.0 or blank except InternalName/OriginalFilename = vVth.exe. ^[pefile.txt:233-239] This is a deliberate opsec choice to avoid searchable metadata.
  • No PDB, no debug directory: IMAGE_DIRECTORY_ENTRY_DEBUG is zero-size. ^[pefile.txt:159-160]
  • Large private resource: The ~919 KB resource at offset 943,496 is the dominant payload. Binwalk identifies a JPEG and TIFF signature near that offset ^[binwalk.txt:10-11], but these are likely false positives from encrypted data.
  • TLS / CANARY / NX: radare2 reports canary true, nx true, pic true ^[rabin2-info.txt]; these are default .NET PE32 protections, not anti-analysis additions.

How To Mess With It (Homelab Replication)

Goal: Reproduce a .NET loader with comparable static fingerprint.

  1. Toolchain: Visual Studio 2019/2022, .NET Framework 4.0 Console or WinForms project.
  2. Obfuscation: Use an obfuscator that supports Unicode renaming (e.g., ConfuserEx with custom rename plugin, or Dotfuscator with identifier renaming set to non-ASCII). Alternatively, manually rename types/methods to \u0002, \u0003, etc. using dnlib post-build.
  3. Crypto: Reference BouncyCastle C# nuget. Implement AES-256-CBC encryption of a second-stage assembly, then embed it as a ManifestResource.
  4. Loader stub: At runtime call Assembly.GetManifestResourceStream, decrypt with RijndaelManaged or BouncyCastle BufferedBlockCipher, then Assembly.Load(byte[]).
  5. Network: Add TcpClient connection to a remote host (decrypted from the resource).
  6. Verification: Run capa on the resulting binary. Expected hits: compiled to the .NET platform, encrypt data using AES via .NET, access .NET resource, act as TCP client, invoke .NET assembly method.

Deployable Signatures

YARA rule

rule UnclassifiedDotNetWhisperLoader {
    meta:
        description = ".NET loader with Whisper namespace, BouncyCastle.CryptoExt, and empty version info"
        author = "pp-hermes"
        date = "2026-06-25"
        hash = "d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68"
    strings:
        $whisper = "Whisper.Properties.Resources" ascii wide
        $bc = "BouncyCastle.CryptoExt" ascii wide
        $vVth = "vVth.exe" ascii wide
        $manifest = "GetManifestResourceStream" ascii wide
        $aes = "RijndaelManaged" ascii wide
        $net40 = ".NETFramework,Version=v4.0" ascii wide
    condition:
        uint16(0) == 0x5A4D and
        pe.exports("_CorExeMain") and
        ($whisper or $bc) and
        $manifest and
        filesize > 900KB and filesize < 1100KB
}

Sigma rule (process creation — hypothetical, based on capa TTPs)

title: .NET Whisper Loader Process Tree
logsource:
    category: process_creation
    product: windows
detection:
    selection:
        Image|endswith: 'TTcopy_pdf.exe'
        CommandLine|contains:
            - 'vVth.exe'
    crypto_load:
        LoadedImage|contains: 'BouncyCastle.CryptoExt'
    condition: selection or crypto_load
falsepositives:
    - Unknown
level: high

IOC list

Indicator Value Type
SHA-256 d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68 Hash
File name TTcopy_pdf.exe Filename
Internal name vVth.exe Metadata
Module name vVth.exe .NET metadata
MVID 91e700a8-de23-4695-78b2-934b06df8de0 .NET GUID
Resource namespace Whisper.Properties.Resources .NET resource
Assembly ref BouncyCastle.CryptoExt .NET assembly
Compile time 2024-08-20 01:57:42 UTC Timestamp

Behavioral fingerprint

This binary is a .NET Framework 4.0 PE32 with a high-entropy .text section (~7.9), empty VS_VERSIONINFO except for vVth.exe, no authenticode signature, and a large encrypted manifest resource (~919 KB). It imports a custom BouncyCastle crypto assembly and exposes no human-readable type or method names — all identifiers are Unicode control-character sequences. At runtime it is expected to call GetManifestResourceStream, decrypt the payload via AES/RSA, and reflectively load the resulting assembly, then initiate a TCP connection to a C2 endpoint.

Detection Signatures

capa capability Namespace ATT&CK
encrypt data using AES via .NET data-manipulation/encryption/aes T1140
decrypt data using RSA via WinAPI data-manipulation/encryption/rsa T1140
hash data using SHA256 data-manipulation/hashing/sha256
act as TCP client communication/tcp/client
access .NET resource executable/resource
invoke .NET assembly method load-code/dotnet T1620
create process in .NET host-interaction/process/create
suspend thread host-interaction/thread/suspend
enumerate files in .NET host-interaction/file-system/files T1083

References

Provenance

  • file.txt — file(1) output
  • pefile.txt — pefile 2023.2.3 PE header dump
  • strings.txt — GNU strings (default 4-char min)
  • capa.txt — Mandiant capa v7.0.1 static analysis
  • binwalk.txt — binwalk v2.3.4 embedded file scan
  • rabin2-info.txt — radare2 5.9.8 rabin2 -I
  • exiftool.json — ExifTool 12.76 metadata
  • metadata.json — OpenCTI connector artifact record
  • triage.json — pp-hermes triage pipeline record
  • floss.txt — FireEye floss (failed due to CLI argument error; no decoded strings recovered)
  • dnfile 0.1.0 — .NET metadata table enumeration
  • Ghidra 11.2.1 — import and symbol search (analysis_complete true, strings_indexed false)

No CAPE sandbox report available; dynamic analysis skipped because no Windows guest machine was configured at detonation time. ^[dynamic-analysis.md]