Asi reverse-engineering-dotnet-malware-with-dnspy
install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/asi/skills/reverse-engineering-dotnet-malware-with-dnspy" ~/.claude/skills/plurigrid-asi-reverse-engineering-dotnet-malware-with-dnspy && rm -rf "$T"
manifest:
plugins/asi/skills/reverse-engineering-dotnet-malware-with-dnspy/SKILL.mdsource content
Reverse Engineering .NET Malware with dnSpy
When to Use
- A malware sample is identified as a .NET assembly (C#, VB.NET, F#) requiring decompilation
- Analyzing .NET-based malware families (AgentTesla, AsyncRAT, RedLine Stealer, Quasar RAT)
- Deobfuscating .NET code protected by ConfuserEx, SmartAssembly, or custom obfuscators
- Extracting hardcoded C2 configurations, encryption keys, and credentials from managed assemblies
- Debugging .NET malware at runtime to observe decryption routines and dynamic behavior
Do not use for native (unmanaged) PE binaries; use Ghidra or IDA for native code analysis.
Prerequisites
- dnSpy or dnSpyEx installed (https://github.com/dnSpyEx/dnSpy - community maintained fork)
- de4dot for automated .NET deobfuscation (
)https://github.com/de4dot/de4dot - ILSpy as an alternative decompiler for cross-validation
- .NET SDK installed for recompiling modified assemblies during analysis
- Isolated Windows VM for running dnSpy debugger on live malware
- Detect It Easy (DIE) for identifying the .NET obfuscator used
Workflow
Step 1: Identify .NET Assembly and Obfuscator
Verify the sample is a .NET binary and detect protection:
# Check if file is .NET assembly file suspect.exe # Output should contain "PE32 executable" with .NET metadata # Detect obfuscator with Detect It Easy diec suspect.exe # Python-based .NET detection python3 << 'PYEOF' import pefile pe = pefile.PE("suspect.exe") # Check for .NET COM descriptor if hasattr(pe, 'DIRECTORY_ENTRY_COM_DESCRIPTOR'): print("[*] .NET assembly detected") print(f" Runtime version: {pe.DIRECTORY_ENTRY_COM_DESCRIPTOR}") else: # Check for mscoree.dll import (alternative detection) for entry in pe.DIRECTORY_ENTRY_IMPORT: if entry.dll.decode().lower() == "mscoree.dll": print("[*] .NET assembly detected (mscoree.dll import)") break else: print("[!] Not a .NET assembly") # Check section names for .NET indicators for section in pe.sections: name = section.Name.decode().rstrip('\x00') if name in ['.text', '.rsrc', '.reloc']: print(f" Section: {name} (typical .NET)") PYEOF
Step 2: Deobfuscate with de4dot
Remove common .NET obfuscation before manual analysis:
# Run de4dot to identify and remove obfuscation de4dot suspect.exe -o suspect_cleaned.exe # Force specific deobfuscator de4dot suspect.exe -p cf # ConfuserEx de4dot suspect.exe -p sa # SmartAssembly de4dot suspect.exe -p dr # Dotfuscator de4dot suspect.exe -p rv # Reactor de4dot suspect.exe -p bl # Babel.NET # Verbose output for debugging de4dot -v suspect.exe -o suspect_cleaned.exe # Handle multi-file assemblies de4dot suspect.exe suspect_helper.dll -o cleaned/
Common .NET Obfuscators: ━━━━━━━━━━━━━━━━━━━━━━━ ConfuserEx: String encryption, control flow, anti-debug, anti-tamper SmartAssembly: String encoding, flow obfuscation, pruning Dotfuscator: Renaming, string encryption, control flow .NET Reactor: Native code generation, necrobit, anti-debug Babel.NET: String encryption, resource encryption, code virtualization Crypto Obfuscator: String encryption, anti-debug, watermarking Custom: Malware-specific obfuscation (manual de4dot configuration needed)
Step 3: Open in dnSpy and Analyze Code
Load the deobfuscated assembly in dnSpy for source-level analysis:
dnSpy Analysis Workflow: ━━━━━━━━━━━━━━━━━━━━━━━ 1. File -> Open -> Select cleaned assembly 2. Navigate to the entry point: - Assembly Explorer -> <namespace> -> Program class -> Main method - Or: Right-click assembly -> Go to Entry Point 3. Key areas to examine: - Entry point (Main) for initialization and execution flow - Form classes for UI-based malware (RATs, stealers) - Network/HTTP classes for C2 communication - Crypto/encryption classes for data protection - Resource access for embedded payloads - Timer/Thread classes for persistence and scheduling 4. Navigation shortcuts: Ctrl+G - Go to token/address Ctrl+Shift+K - Search assemblies F12 - Go to definition Ctrl+R - Analyze (find usages) F5 - Start debugging F9 - Toggle breakpoint
Step 4: Extract Configuration and C2 Data
Locate hardcoded configuration in the decompiled source:
// Common .NET malware configuration patterns: // Pattern 1: Static class with hardcoded values public static class Config { public static string Host = "185.220.101.42"; public static int Port = 4782; public static string Key = "GhOsT_RaT_2025"; public static string Mutex = "AsyncMutex_6SI8OkPnk"; public static bool Install = true; public static string InstallFolder = "%AppData%"; } // Pattern 2: Encrypted strings decrypted at runtime public static string Decrypt(string input) { byte[] data = Convert.FromBase64String(input); byte[] key = Encoding.UTF8.GetBytes("SecretKey123"); for (int i = 0; i < data.Length; i++) { data[i] ^= key[i % key.Length]; } return Encoding.UTF8.GetString(data); } // Pattern 3: Resource-embedded configuration byte[] configData = Properties.Resources.config; string config = AES.Decrypt(configData, derivedKey);
# Python script to extract .NET resource strings import subprocess import re import base64 # Use monodis (Mono) or ildasm (.NET SDK) to dump IL result = subprocess.run( ["monodis", "--output=il_dump.il", "suspect_cleaned.exe"], capture_output=True, text=True ) # Search for string literals in IL dump with open("il_dump.il", errors="ignore") as f: il_code = f.read() # Find ldstr (load string) instructions strings = re.findall(r'ldstr\s+"([^"]+)"', il_code) for s in strings: # Check for Base64 encoded strings try: decoded = base64.b64decode(s).decode('utf-8', errors='ignore') if len(decoded) > 3 and decoded.isprintable(): print(f" Base64: {s[:40]}... -> {decoded[:100]}") except: pass # Check for URLs/IPs if re.match(r'https?://', s) or re.match(r'\d+\.\d+\.\d+\.\d+', s): print(f" Network: {s}")
Step 5: Debug with dnSpy
Set breakpoints and debug the malware to observe runtime behavior:
dnSpy Debugging Workflow: ━━━━━━━━━━━━━━━━━━━━━━━ 1. Set breakpoints on key methods: - String decryption functions (to capture decrypted values) - Network connection methods (to capture C2 URLs) - File write operations (to see what is dropped) - Registry modification methods (to see persistence) 2. Debug -> Start Debugging (F5) - Select the assembly to debug - Set command-line arguments if needed - Configure exception handling (break on all CLR exceptions) 3. At each breakpoint: - Inspect local variables (Locals window) - Evaluate expressions (Immediate window) - View call stack to understand execution context - Step over (F10) / Step into (F11) / Step out (Shift+F11) 4. Capture decrypted strings: - Set breakpoint after decryption function returns - Read the return value from the Locals window - Document all decrypted configuration values
Step 6: Document Findings
Compile analysis results into a structured report:
Analysis documentation should include: - .NET assembly metadata (CLR version, target framework, compilation info) - Obfuscator identified and deobfuscation method used - Complete C2 configuration (hosts, ports, encryption keys, mutex names) - Malware capabilities (keylogging, screen capture, file theft, etc.) - Persistence mechanisms (registry, scheduled tasks, startup folder) - Anti-analysis techniques (VM detection, debugger detection, sandbox evasion) - Extracted IOCs (C2 IPs/domains, file hashes, mutex names, registry keys) - YARA rule based on unique code patterns or strings
Key Concepts
| Term | Definition |
|---|---|
| CIL/MSIL | Common Intermediate Language; the bytecode format .NET assemblies compile to, which can be decompiled back to high-level C#/VB.NET |
| Metadata Token | Unique identifier for .NET types, methods, and fields within the assembly metadata tables; used for navigation in dnSpy |
| de4dot | Open-source .NET deobfuscator that identifies and removes protection from many commercial and malware-specific obfuscators |
| ConfuserEx | Popular open-source .NET obfuscator frequently used by malware authors for string encryption and control flow obfuscation |
| String Encryption | Obfuscation technique replacing string literals with encrypted data and runtime decryption calls to hide IOCs from static analysis |
| Resource Embedding | Storing configuration, payloads, or additional assemblies in .NET embedded resources, often encrypted with a key derived from assembly metadata |
| Assembly.Load | .NET method loading assemblies from byte arrays in memory, enabling fileless execution of embedded payloads |
Tools & Systems
- dnSpy/dnSpyEx: Open-source .NET assembly editor, decompiler, and debugger supporting C# and VB.NET decompilation
- de4dot: Automated .NET deobfuscator supporting ConfuserEx, SmartAssembly, Dotfuscator, Reactor, and many other protectors
- ILSpy: Open-source .NET decompiler providing C#, VB.NET, and IL views of assembly code
- dotPeek: JetBrains' free .NET decompiler with symbol server and cross-reference navigation
- Detect It Easy (DIE): Multi-format file analyzer identifying .NET framework version, obfuscator, and compiler information
Common Scenarios
Scenario: Analyzing an AgentTesla Information Stealer
Context: A phishing email delivers a .NET executable identified as AgentTesla. The sample needs analysis to determine what credentials it steals, how it exfiltrates data, and its C2 configuration.
Approach:
- Run Detect It Easy to identify the obfuscator (commonly ConfuserEx or custom)
- Deobfuscate with de4dot to restore readable class/method names and decrypt strings
- Open in dnSpy and navigate to the entry point to understand initialization
- Locate the credential harvesting modules (browser, email, FTP, VPN password theft classes)
- Find the exfiltration method (SMTP email, FTP upload, HTTP POST, Telegram bot API)
- Extract C2 configuration (SMTP server, credentials, recipient email, or HTTP URL)
- Set debugger breakpoints on the decryption function to capture all decrypted strings at once
Pitfalls:
- Analyzing without de4dot first (ConfuserEx makes manual analysis extremely difficult)
- Not checking for multi-stage loading (initial .NET executable may load additional assemblies from resources)
- Missing configuration stored in .NET resources rather than hardcoded strings
- Running the debugger without network isolation (AgentTesla will attempt to exfiltrate immediately)
Output Format
.NET MALWARE ANALYSIS REPORT ================================ Sample: invoice_scanner.exe SHA-256: e3b0c44298fc1c149afbf4c8996fb924... Type: .NET Assembly (C#) Framework: .NET Framework 4.8 Obfuscator: ConfuserEx v1.6 Deobfuscated: Yes (de4dot -p cf) CLASSIFICATION Family: AgentTesla v3 Type: Information Stealer / Keylogger Compile Date: 2025-09-10 C2 CONFIGURATION Exfil Method: SMTP (Email) SMTP Server: smtp.yandex[.]com:587 SMTP User: exfil.account@yandex[.]com SMTP Pass: Str0ngP@ssw0rd2025 Recipient: operator@protonmail[.]com Interval: 30 minutes Encryption: AES-256 with key "AgentTesla_2025_key" CAPABILITIES [*] Browser credential theft (Chrome, Firefox, Edge, Opera) [*] Email client passwords (Outlook, Thunderbird) [*] FTP client credentials (FileZilla, WinSCP) [*] VPN credentials (NordVPN, OpenVPN) [*] Keylogging (SetWindowsHookEx) [*] Screenshot capture (every 30 seconds) [*] Clipboard monitoring PERSISTENCE Method: Registry Run key + Scheduled Task Registry: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\WindowsUpdate Task: \Microsoft\Windows\WindowsUpdate\Updater EXTRACTED IOCs SMTP Server: smtp.yandex[.]com Exfil Email: exfil.account@yandex[.]com Recipient: operator@protonmail[.]com Mutex: AgentTesla_2025_Q3_MUTEX Install Path: %AppData%\Microsoft\Windows\svchost.exe