--- name: dotnet-malware-analysis description: > Analyze .NET (managed-code) malware — identify obfuscators, deobfuscate with de4dot, decompile to C# with dnSpy/ILSpy, inspect IL metadata tables, extract embedded resources and staged payloads, map capabilities via P/Invoke and reflection patterns, and extract configurations and IOCs. Covers pure IL assemblies, mixed-mode binaries, and single-file .NET deployments across .NET Framework 2.0–4.8 and .NET 5–8. --- # .NET Malware Analysis Analyze Windows malware written in C#, VB.NET, or F# targeting the Common Language Runtime. .NET malware is the dominant platform for commodity threats — AgentTesla, AsyncRAT, RedLine, NjRAT, QuasarRAT, and many more — because it compiles to high-level Intermediate Language (IL) that is trivially decompilable unless protected by an obfuscator. This skill provides the foundational .NET reverse-engineering workflow that family-specific skills (infostealer-analysis, rat-analysis, etc.) build on. ## Prerequisites - **de4dot** — .NET deobfuscator/unpacker ```bash # Pre-built release or build from source git clone https://github.com/de4dot/de4dot && cd de4dot && dotnet build ``` - **dnSpy** (or dnSpyEx fork) — .NET debugger and assembly editor (GUI) ```bash # Download from https://github.com/dnSpyEx/dnSpy/releases ``` - **ILSpy** / **ilspycmd** — .NET decompiler (CLI-friendly) ```bash dotnet tool install -g ilspycmd ``` - **Python 3.10+** with optional packages: ```bash pip install pefile dnfile ``` - **monodis** (Mono) or **ildasm** (.NET SDK) — IL disassembler for raw IL inspection - **7-Zip** or **binwalk** — for extracting resources from single-file bundles - **YARA / YARA-X** — for obfuscator signature scanning (optional) ## Step-by-Step Instructions ### Step 1: Confirm .NET Assembly and Classify Binary Type Before applying .NET-specific tools, verify the sample is actually a managed assembly and determine its variant: 1. **Check for mscoree.dll import** — all .NET executables import `_CorExeMain` from `mscoree.dll` (or `_CorDllMain` for DLLs): ```bash python scripts/dotnet_analyzer.py --input sample.exe --format json ``` The script checks the PE import table for the `mscoree.dll` reference and parses the CLI header. 2. **Identify the .NET version**: - **.NET Framework 2.0–4.8**: `TargetFramework` attribute or metadata version string (`v4.0.30319`) - **.NET 5+/Core**: `System.Runtime` assembly reference, deps.json bundle - **Mixed-mode**: Contains both IL and native code sections (C++/CLI) 3. **Detect single-file bundles** — .NET 5+ can bundle all dependencies into one executable. Look for the bundle signature (`\x00\x64\x6E\x00`) near the end of the file. Extract with `dotnet-bundle-extract` or 7-Zip. 4. **Record key metadata** from the CLI header: - Entry point token (e.g., `0x06000001`) - Metadata RVA and size - Flags: `ILONLY`, `32BITREQUIRED`, `STRONGNAMESIGNED` - Target CPU: AnyCPU, x86, x64 ### Step 2: Inspect .NET Metadata Tables .NET metadata is the richest source of structural information — far more descriptive than native PE imports: 1. **Assembly-level metadata**: - `Assembly` table: name, version, culture, public key token - `AssemblyRef` table: referenced assemblies (reveals framework targets and third-party dependencies) - `Module` table: MVID (Module Version ID, useful as a tracking hash) 2. **Type system**: - `TypeDef` table: all classes/structs/enums defined in the assembly — enumerate namespaces and class names for reconnaissance - `TypeRef` table: external types referenced — reveals API surface used - `MethodDef` table: all methods with signatures — look for suspicious names, P/Invoke entry points, and the module entry point 3. **Special tables to inspect**: - `ImplMap` (P/Invoke): native function imports — `VirtualAlloc`, `WriteProcessMemory`, `CreateRemoteThread` indicate injection - `ManifestResource`: embedded resources with names and offsets - `CustomAttribute`: obfuscator watermarks, `DebuggerHidden`, `SuppressUnmanagedCodeSecurity` attributes Use `dnfile` in Python for programmatic access: ```python import dnfile dn = dnfile.dnPE("sample.exe") for row in dn.net.mdtables.ImplMap: print(f"P/Invoke: {row.ImportName} from {row.ImportScope.row.Name}") ``` Or use `monodis --typedef sample.exe` for quick CLI enumeration. ### Step 3: Detect Obfuscator and Protection Most .NET malware uses commercial or open-source obfuscators. Identifying the protector determines which deobfuscation approach to use: 1. **Automated detection** with de4dot: ```bash de4dot --detect-only sample.exe ``` This prints the detected obfuscator name and confidence. 2. **Common obfuscators and their signatures**: | Obfuscator | Telltale Signs | |-----------|----------------| | **ConfuserEx** | `ConfuserEx` in module attributes, `koi` resource name pattern, control flow with `switch` on computed index | | **.NET Reactor** | `__` prefixed type names, `{GUID}` resource names, embedded native stub in `.rsrc` | | **SmartAssembly** | `SmartAssembly.Attributes` namespace, `{GUID}.resources` entries, PoweredBy attribute | | **Crypto Obfuscator** | `CryptoObfuscator` attribute, `costura` or guid-named resources | | **Eazfuscator.NET** | Single class with many static fields of type `Dictionary`, eval-stack based string decryption | | **Babel Obfuscator** | `Babel.Licensing` attribute, scrambled metadata tokens | | **Dotfuscator** | Preserved type names but renamed members to `a`, `b`, `c`; `DotfuscatorAttribute` | | **Agile.NET (CliSecure)** | `` or `AgileDotNet.Licenser` attribute | 3. **Manual indicators** when automated detection fails: - Module-level `CustomAttribute` entries with obfuscator names - Abnormally large `#Strings` or `#Blob` heap sizes - Methods with no meaningful names (single chars or unicode garbage) - Delegate-based proxy calls replacing direct method calls - `Module.cctor` (module initializer) with decryption/unpacking logic ### Step 4: Deobfuscate Apply the appropriate deobfuscation strategy based on Step 3: 1. **de4dot automated cleaning** (handles most common obfuscators): ```bash # Auto-detect and clean de4dot sample.exe -o sample_clean.exe # Force a specific obfuscator if auto-detect is wrong de4dot sample.exe -p cr -o sample_clean.exe # Crypto Obfuscator de4dot sample.exe -p cf -o sample_clean.exe # ConfuserEx de4dot sample.exe -p sa -o sample_clean.exe # SmartAssembly de4dot sample.exe -p dr -o sample_clean.exe # .NET Reactor ``` 2. **Verify deobfuscation succeeded**: - Re-run `de4dot --detect-only sample_clean.exe` — should report "Unknown/not obfuscated" - Compare TypeDef counts before and after — names should be readable - Check that string decryption methods have been inlined 3. **Handle deobfuscation failures**: - **Anti-tamper protection**: some obfuscators verify assembly integrity at runtime — patch the check or use dnSpy's debugger to dump after initialization - **Control flow flattening**: if de4dot cannot simplify, use dnSpy debugger to trace actual execution paths - **Virtualized code**: .NET Reactor and Agile.NET can convert IL to a custom VM — requires dedicated devirtualizers or dynamic analysis - **Native unpacking stubs**: mixed-mode assemblies may unpack the IL at runtime — attach dnSpy debugger and dump `Assembly.Load` calls 4. **Iterative cleaning** — run de4dot multiple times if layers of obfuscation are nested (common in loaders that unpack a second-stage .NET assembly): ```bash de4dot stage1.exe -o stage1_clean.exe # Extract embedded stage2 from resources (see Step 6) de4dot stage2.dll -o stage2_clean.dll ``` ### Step 5: Decompile and Analyze IL With a clean (or cleaner) assembly, recover C# source code: 1. **Full project decompilation** with ilspycmd: ```bash ilspycmd sample_clean.exe -o ./decompiled/ -p ``` This creates a `.csproj` and `.cs` files — open in any editor or IDE. 2. **Interactive analysis** with dnSpy: - Load `sample_clean.exe` — navigate the type tree - Right-click methods → "Edit IL Instructions" for raw IL view - Set breakpoints on interesting methods and debug under dnSpy 3. **Key IL patterns to watch for**: - **Reflection-based loading** — `Assembly.Load(byte[])` or `Assembly.LoadFrom()` indicate runtime assembly loading (second-stage payloads): ```csharp Assembly asm = Assembly.Load(decryptedBytes); asm.EntryPoint.Invoke(null, null); ``` - **P/Invoke declarations** — native API calls for process injection, privilege escalation, or anti-analysis: ```csharp [DllImport("kernel32.dll")] static extern IntPtr VirtualAllocEx(IntPtr hProcess, ...); ``` - **Dynamic method generation** — `System.Reflection.Emit` namespace usage indicates runtime code generation to evade static analysis - **Unsafe code blocks** — `unsafe` keyword with pointer manipulation, unusual for managed malware and may indicate shellcode injection - **WMI queries** — `ManagementObjectSearcher` with queries like `SELECT * FROM Win32_Processor` for VM detection - **Delegate/proxy patterns** — obfuscators replace direct calls with delegate invocations; trace the delegate initialization in `.cctor` 4. **Compare decompiled output with raw IL** when the decompiler produces suspicious or incorrect code — use `ildasm` or `monodis` for ground truth: ```bash monodis --method "Namespace.Class::MethodName" sample_clean.exe ``` ### Step 6: Extract Embedded Resources and Payloads .NET malware frequently stores encrypted payloads, configuration data, or second-stage assemblies as embedded resources: 1. **Enumerate resources**: ```bash monodis --manifest sample_clean.exe ``` Or use dnSpy: expand Resources node in the assembly tree. 2. **Common resource patterns**: - **Encrypted payloads**: high-entropy resources (> 7.0 bits/byte) named with GUIDs or random strings — find the decryption routine by searching for `ResourceManager` or `GetManifestResourceStream` calls - **Serialized configs**: XML or binary-formatted resources containing C2 URLs, keys, and options - **Embedded DLLs**: Costura.Fody-packed dependencies stored as compressed resources prefixed with `costura.` - **Icon/bitmap steganography**: payloads hidden in image resources using LSB encoding 3. **Extract and analyze**: ```python import dnfile dn = dnfile.dnPE("sample_clean.exe") for resource in dn.net.resources: print(f"{resource.name}: offset={resource.offset}, size={resource.size}") # Dump resource bytes for entropy analysis and decryption ``` 4. **Trace the loading code** — search decompiled source for: - `Assembly.GetManifestResourceStream("resource_name")` - `ResourceManager.GetObject("key")` - `Properties.Resources.ResourceName` - Any method that reads from resources, decrypts, and calls `Assembly.Load()` 5. **Single-file .NET bundles** (.NET 5+): use `dotnet-bundle-extract` or search for the bundle header signature to extract all bundled assemblies and analyze each one individually. ### Step 7: Map Capabilities and Behavior Enumerate what the malware can do by analyzing API usage patterns unique to the .NET ecosystem: 1. **Process injection** (via P/Invoke): - `VirtualAllocEx` + `WriteProcessMemory` + `CreateRemoteThread` - `NtCreateSection` + `NtMapViewOfSection` (process hollowing) - `QueueUserAPC` (APC injection) 2. **Credential theft** (managed APIs): - `System.Security.Cryptography.ProtectedData.Unprotect()` (DPAPI) - `Microsoft.Win32.Registry` reads from browser credential paths - `System.Net.NetworkCredential` and `CredentialCache` - SQLite database reads (browser password stores) 3. **Persistence mechanisms**: - `Microsoft.Win32.Registry.SetValue()` for Run/RunOnce keys - `System.IO.File.Copy()` to startup folders - `System.ServiceProcess.ServiceInstaller` for service creation - `TaskScheduler` COM interop for scheduled tasks 4. **Network communication**: - `System.Net.Http.HttpClient` or `WebClient` for C2 beaconing - `System.Net.Sockets.TcpClient` for raw TCP - `System.Net.Mail.SmtpClient` for SMTP exfiltration (AgentTesla pattern) - `System.Net.FtpWebRequest` for FTP exfiltration 5. **Anti-analysis** (.NET-specific patterns): - `System.Diagnostics.Debugger.IsAttached` check - `Environment.GetEnvironmentVariable("USERNAME")` sandbox detection - `ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem")` for VM manufacturer detection - `DateTime` comparisons for time-based evasion - `Thread.Sleep()` with long delays for sandbox timeout 6. **Data collection**: - `System.Windows.Forms.Clipboard` for clipboard monitoring - `SetWindowsHookEx` P/Invoke for keylogging - `Screen.CaptureScreen()` or GDI+ `Graphics.CopyFromScreen()` for screenshots - `System.IO.Directory.GetFiles()` recursive scans for document theft ### Step 8: Extract Configuration and IOCs Most .NET malware families store configuration in predictable locations: 1. **Hardcoded fields** — search decompiled classes for: - Static string fields with Base64 or hex-encoded values - Classes named `Config`, `Settings`, `Options`, `Vars`, or single-letter names with many string fields - Constructor methods that initialize connection parameters 2. **Encrypted configuration** — common patterns: - **AES/Rijndael**: `RijndaelManaged` or `Aes.Create()` with hardcoded key/IV (often derived from a password via `Rfc2898DeriveBytes`) - **XOR**: byte array XOR with a single-byte or multi-byte key - **Base64 + AES**: `Convert.FromBase64String()` → AES decrypt - **DES/3DES**: older families still use `DESCryptoServiceProvider` 3. **Configuration fields to extract**: - C2 server URLs/IPs and ports - Encryption keys (AES key, XOR key, RSA public key) - Mutex names (for single-instance enforcement) - Installation paths and filenames - Persistence registry keys - Campaign ID / bot ID / group tags - Exfiltration credentials (SMTP user/pass, FTP credentials, Telegram bot tokens) - Kill switch domains or dates 4. **Automated extraction** with the included script: ```bash python scripts/dotnet_analyzer.py --input sample_clean.exe --format json ``` 5. **Map findings to MITRE ATT&CK**: - Process injection → T1055 - Registry Run keys → T1547.001 - Credentials from browsers → T1555.003 - Keylogging → T1056.001 - Screen capture → T1113 - SMTP exfiltration → T1048.003 - Scheduled task → T1053.005 ## Output Format The `dotnet_analyzer.py` script produces a structured JSON report: ```json { "sample": "/samples/sample.exe", "hashes": { "md5": "d41d8cd98f00b204e9800998ecf8427e", "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "is_dotnet": true, "dotnet_info": { "clr_version": "v4.0.30319", "target_framework": ".NETFramework,Version=v4.8", "flags": ["ILONLY", "32BITREQUIRED"], "entry_point_token": "0x06000001", "mvid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "is_mixed_mode": false }, "obfuscator": { "name": "ConfuserEx", "confidence": 0.95, "indicators": [ "ConfuserEx watermark in module attributes", "Control flow flattening in entry point" ] }, "metadata": { "assembly_name": "WindowsService", "assembly_version": "1.0.0.0", "referenced_assemblies": [ "mscorlib, Version=4.0.0.0", "System.Net.Http, Version=4.0.0.0" ], "type_count": 42, "method_count": 187, "pinvoke_imports": [ {"method": "VirtualAllocEx", "module": "kernel32.dll"}, {"method": "WriteProcessMemory", "module": "kernel32.dll"} ] }, "resources": [ { "name": "payload", "size": 45312, "entropy": 7.89, "likely_encrypted": true } ], "capabilities": { "process_injection": true, "credential_theft": true, "keylogging": false, "screen_capture": true, "persistence": true, "network_communication": true, "anti_analysis": true }, "strings_of_interest": [ "http://evil.example.com/gate.php", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "smtp.example.com" ], "mitre_attack": [ {"technique": "T1055", "name": "Process Injection"}, {"technique": "T1547.001", "name": "Registry Run Keys"}, {"technique": "T1113", "name": "Screen Capture"} ], "timestamp": "2026-03-24T12:00:00Z" } ``` ## Tips - **Always deobfuscate before decompiling** — readable names and simplified control flow make the decompiled C# dramatically easier to understand - **Check Module.cctor first** — the module initializer (`.cctor` of ``) often contains decryption, anti-tamper, or unpacking logic that runs before `Main()` - **Use MVID for tracking** — the Module Version ID is a unique GUID per compilation; it clusters related samples even when hashes differ due to minor config changes - **P/Invoke is the attack surface** — most dangerous capabilities require native API calls; enumerate `ImplMap` entries for a quick threat assessment - **Watch for Assembly.Load** — dynamic assembly loading is the primary mechanism for multi-stage .NET malware; set breakpoints on it in dnSpy - **Resources are payloads until proven otherwise** — high-entropy embedded resources in .NET malware are almost always encrypted second-stage payloads or configuration blobs - **De4dot is not perfect** — it struggles with heavily customized ConfuserEx forks and .NET Reactor's native code protection; fall back to dnSpy debugging for runtime unpacking - **Single-file bundles need extraction first** — .NET 5+ single-file apps look like native executables but contain bundled managed assemblies; extract before analyzing - **Mixed-mode is rare but dangerous** — C++/CLI assemblies combine native and managed code; analyze both the IL and native sections - **Combine with family-specific skills**: - `infostealer-analysis` — AgentTesla, RedLine config extraction workflows - `rat-analysis` — AsyncRAT, NjRAT, QuasarRAT protocol analysis - `string-decryption` — .NET string decryption automation - `malware-deobfuscation` — advanced deobfuscation techniques - `config-extraction` — standardized config extraction patterns