npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
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)")
PYEOFStep 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 breakpointStep 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 valuesStep 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 stringsKey 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.exeReferences and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.5 KB
API Reference: .NET Malware Reverse Engineering with dnSpy Agent
Overview
Analyzes .NET malware: validates CLR headers, detects obfuscators (ConfuserEx, SmartAssembly), deobfuscates with de4dot, extracts strings/IOCs, and parses .NET metadata via monodis.
Dependencies
| Package | Version | Purpose |
|---|---|---|
| hashlib | stdlib | Sample hash computation |
| struct | stdlib | PE/CLR header parsing |
| re | stdlib | String pattern extraction |
External Tools (Optional)
| Tool | Purpose |
|---|---|
| diec (Detect It Easy) | Obfuscator identification |
| de4dot | Automated .NET deobfuscation |
| monodis | .NET assembly metadata extraction |
Core Functions
detect_dotnet_assembly(filepath)
Validates PE file has CLR header (COM descriptor directory entry).
- Checks: MZ signature, PE signature, optional header magic, CLR RVA
- Returns:
dictwithis_dotnet,clr_header_rva
detect_obfuscator(filepath)
Runs Detect It Easy to identify ConfuserEx, SmartAssembly, .NET Reactor, Dotfuscator, Babel, Eazfuscator, Crypto Obfuscator.
- Returns:
dictwithdetectedlist
deobfuscate_with_de4dot(filepath, output_path)
Runs de4dot to remove obfuscation, producing a cleaner assembly.
- Timeout: 120 seconds
- Returns:
dictwithsuccess,output_path
extract_strings(filepath, min_length)
Extracts ASCII and Unicode strings, classifies into URLs, IPs, emails, registry keys, base64, and suspicious keywords (keylog, stealer, webhook, etc.).
- Returns:
dict[str, list[str]]- categorized indicator lists
analyze_dotnet_metadata(filepath)
Uses monodis to extract assembly info, type definitions, and method counts.
- Returns:
dictwithtype_count,method_count,types
analyze_dotnet_malware(filepath, output_dir)
Full pipeline: hashes -> .NET check -> obfuscator detection -> deobfuscation -> strings -> metadata.
Obfuscators Detected
| Obfuscator | Indicator |
|---|---|
| ConfuserEx | Most common open-source .NET obfuscator |
| SmartAssembly | Commercial obfuscator by Redgate |
| .NET Reactor | Code protection with native stub |
| Dotfuscator | Microsoft-provided obfuscator |
| Eazfuscator | Commercial string/flow obfuscation |
Suspicious String Keywords
keylog, screenshot, clipboard, password, credential, smtp, telegram, discord, webhook, stealer, inject, hook, persist, startup
Usage
python agent.py suspect.exeScripts 1
agent.py8.1 KB
#!/usr/bin/env python3
""".NET malware reverse engineering agent using subprocess wrappers for dnSpy/de4dot."""
import subprocess
import os
import sys
import re
import hashlib
import struct
def compute_hashes(filepath):
"""Compute hashes for sample identification."""
with open(filepath, "rb") as f:
data = f.read()
return {
"md5": hashlib.md5(data).hexdigest(),
"sha256": hashlib.sha256(data).hexdigest(),
"size": len(data),
}
def detect_dotnet_assembly(filepath):
"""Check if file is a .NET assembly by looking for CLI header."""
with open(filepath, "rb") as f:
data = f.read(512)
if data[:2] != b"MZ":
return {"is_dotnet": False, "reason": "Not a PE file"}
try:
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
if pe_offset + 4 > len(data):
return {"is_dotnet": False, "reason": "Invalid PE header"}
if data[pe_offset:pe_offset + 4] != b"PE\x00\x00":
return {"is_dotnet": False, "reason": "Invalid PE signature"}
opt_offset = pe_offset + 24
magic = struct.unpack_from("<H", data, opt_offset)[0]
if magic == 0x10B:
clr_offset = opt_offset + 208
elif magic == 0x20B:
clr_offset = opt_offset + 224
else:
return {"is_dotnet": False, "reason": "Unknown PE format"}
if clr_offset + 8 <= len(data):
clr_rva, clr_size = struct.unpack_from("<II", data, clr_offset)
if clr_rva > 0 and clr_size > 0:
return {"is_dotnet": True, "clr_header_rva": clr_rva, "clr_size": clr_size}
return {"is_dotnet": False, "reason": "No CLR header"}
except (struct.error, IndexError):
return {"is_dotnet": False, "reason": "Parse error"}
def detect_obfuscator(filepath):
"""Detect .NET obfuscator using Detect It Easy."""
try:
result = subprocess.run(
["diec", filepath], capture_output=True, text=True, timeout=30
)
output = result.stdout
obfuscators = {
"ConfuserEx": "confuser" in output.lower(),
"SmartAssembly": "smartassembly" in output.lower(),
".NET Reactor": "reactor" in output.lower(),
"Dotfuscator": "dotfuscator" in output.lower(),
"Babel": "babel" in output.lower(),
"Eazfuscator": "eazfuscator" in output.lower(),
"Crypto Obfuscator": "crypto" in output.lower() and "obfuscator" in output.lower(),
}
detected = [name for name, found in obfuscators.items() if found]
return {"detected": detected, "raw_output": output.strip()}
except FileNotFoundError:
return {"detected": [], "raw_output": "diec not installed"}
def deobfuscate_with_de4dot(filepath, output_path):
"""Run de4dot to deobfuscate .NET assembly."""
try:
result = subprocess.run(
["de4dot", filepath, "-o", output_path],
capture_output=True, text=True, timeout=120
)
return {
"success": result.returncode == 0,
"output_path": output_path,
"stdout": result.stdout[-500:] if result.stdout else "",
}
except FileNotFoundError:
return {"success": False, "error": "de4dot not installed"}
def extract_strings(filepath, min_length=8):
"""Extract strings and classify for IOCs."""
with open(filepath, "rb") as f:
data = f.read()
unicode_strings = re.findall(
rb"(?:[\x20-\x7e]\x00){%d,}" % min_length, data
)
ascii_strings = re.findall(
rb"[\x20-\x7e]{%d,}" % min_length, data
)
all_strings = set()
for s in ascii_strings:
all_strings.add(s.decode("ascii", errors="ignore"))
for s in unicode_strings:
all_strings.add(s.decode("utf-16-le", errors="ignore"))
indicators = {
"urls": [], "ips": [], "emails": [],
"registry_keys": [], "file_paths": [],
"base64_strings": [], "suspicious_strings": [],
}
suspicious_keywords = [
"keylog", "screenshot", "clipboard", "password", "credential",
"smtp", "telegram", "discord", "webhook", "stealer",
"inject", "hook", "persist", "startup",
]
for s in all_strings:
if re.search(r"https?://", s):
indicators["urls"].append(s)
if re.search(r"\b(\d{1,3}\.){3}\d{1,3}\b", s):
indicators["ips"].append(s)
if re.search(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", s):
indicators["emails"].append(s)
if re.search(r"HKLM|HKCU|SOFTWARE\\", s, re.IGNORECASE):
indicators["registry_keys"].append(s)
if re.search(r"[A-Za-z0-9+/]{40,}={0,2}$", s):
indicators["base64_strings"].append(s[:100])
for kw in suspicious_keywords:
if kw in s.lower():
indicators["suspicious_strings"].append(s[:100])
break
for key in indicators:
indicators[key] = list(set(indicators[key]))[:20]
return indicators
def analyze_dotnet_metadata(filepath):
"""Extract .NET metadata using monodis or ilspy CLI if available."""
metadata = {}
try:
result = subprocess.run(
["monodis", "--assembly", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
metadata["assembly_info"] = result.stdout.strip()
except FileNotFoundError:
pass
try:
result = subprocess.run(
["monodis", "--typedef", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
types = re.findall(r"(\S+)\s+flags", result.stdout)
metadata["type_count"] = len(types)
metadata["types"] = types[:30]
except FileNotFoundError:
pass
try:
result = subprocess.run(
["monodis", "--method", filepath],
capture_output=True, text=True, timeout=15
)
if result.returncode == 0:
methods = re.findall(r"(\S+)\s+\(", result.stdout)
metadata["method_count"] = len(methods)
except FileNotFoundError:
pass
return metadata
def analyze_dotnet_malware(filepath, output_dir="/tmp/dotnet_analysis"):
"""Full .NET malware analysis pipeline."""
os.makedirs(output_dir, exist_ok=True)
report = {"file": filepath}
report["hashes"] = compute_hashes(filepath)
report["dotnet_check"] = detect_dotnet_assembly(filepath)
if not report["dotnet_check"].get("is_dotnet"):
report["error"] = "Not a .NET assembly"
return report
report["obfuscator"] = detect_obfuscator(filepath)
deobf_path = os.path.join(output_dir, "deobfuscated.exe")
report["deobfuscation"] = deobfuscate_with_de4dot(filepath, deobf_path)
analysis_target = deobf_path if report["deobfuscation"].get("success") else filepath
report["strings"] = extract_strings(analysis_target)
report["metadata"] = analyze_dotnet_metadata(analysis_target)
return report
def print_report(report):
print(".NET Malware Analysis Report")
print("=" * 50)
print(f"File: {report['file']}")
print(f"SHA-256: {report['hashes']['sha256']}")
print(f".NET Assembly: {report['dotnet_check'].get('is_dotnet', False)}")
obf = report.get("obfuscator", {})
if obf.get("detected"):
print(f"Obfuscator: {', '.join(obf['detected'])}")
deobf = report.get("deobfuscation", {})
print(f"Deobfuscation: {'Success' if deobf.get('success') else 'Failed/Skipped'}")
meta = report.get("metadata", {})
if meta:
print(f"Types: {meta.get('type_count', 'N/A')}, Methods: {meta.get('method_count', 'N/A')}")
strings = report.get("strings", {})
if strings:
print("\nExtracted Indicators:")
for cat, values in strings.items():
if values:
print(f" {cat}: {len(values)}")
for v in values[:3]:
print(f" - {v[:80]}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python agent.py <dotnet_executable>")
sys.exit(1)
result = analyze_dotnet_malware(sys.argv[1])
print_report(result)