d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68unclassified-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.Corereferences) ^[strings.txt:1669-1673] - Linker version: 6.0 (PE32 optional header) ^[pefile.txt:46]
- Obfuscator: Unknown — not ConfuserEx (no
#=q…==mangling), not SmartAssembly (noString::Decryptpattern). 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+2000–U+200F). ^[dnfile analysis] This defeats string-based clustering and makes Ghidra/dnSpy output unreadable. - Crypto library: Custom
BouncyCastle.CryptoExtassembly 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:
\u0002\u2000\u2001.resources— small, private ^[dnfile analysis]Whisper.Properties.Resources.resources— 67,860 bytes, public ^[dnfile analysis]- 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,\u0005with 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;
SuppressIldasmAttributeis 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.exeexploits 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.CryptoExtrather than the standardBouncyCastle.Crypto, suggesting the author modified or repacked the library. ^[strings.txt:1674] - Empty version info: All VS_VERSIONINFO fields are
0.0.0.0or blank exceptInternalName/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_DEBUGis 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.
- Toolchain: Visual Studio 2019/2022, .NET Framework 4.0 Console or WinForms project.
- 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. - Crypto: Reference BouncyCastle C# nuget. Implement AES-256-CBC encryption of a second-stage assembly, then embed it as a
ManifestResource. - Loader stub: At runtime call
Assembly.GetManifestResourceStream, decrypt withRijndaelManagedor BouncyCastleBufferedBlockCipher, thenAssembly.Load(byte[]). - Network: Add
TcpClientconnection to a remote host (decrypted from the resource). - Verification: Run
capaon 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
- OpenCTI artifact:
b9a6d304-b493-4fb4-ab68-2731be4f1c67^[metadata.json] - MalwareBazaar SHA-256:
d9c0bc24413ef5ebd862ddc69cc74300025f1dd02480073aa361c53357873d68 - Related wiki pages:
- unclassified-dotnet-whisper — entity page for this cluster
- unicode-control-character-name-obfuscation — technique deep-dive
- dotnet-manifest-resource-decryption — generic decryption pattern
- double-extension-masquerade — filename trick
- social-engineering-filename-lure — lure taxonomy
Provenance
file.txt— file(1) outputpefile.txt— pefile 2023.2.3 PE header dumpstrings.txt— GNU strings (default 4-char min)capa.txt— Mandiant capa v7.0.1 static analysisbinwalk.txt— binwalk v2.3.4 embedded file scanrabin2-info.txt— radare2 5.9.8rabin2 -Iexiftool.json— ExifTool 12.76 metadatametadata.json— OpenCTI connector artifact recordtriage.json— pp-hermes triage pipeline recordfloss.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]