.NET malware analysis — dnSpy/ILSpy decompilation, de-obfuscation (ConfuserEx, .NET Reactor, Babel, Crypto Obfuscator), behavioral analysis of managed assemblies, PowerShell cradle extraction, and config decryption for common .NET RAT families.
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
High
Do not use without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/reverser/dotnet-malware/SKILL.mdAnalyze .NET malware from initial triage through full decompilation, de-obfuscation, config extraction, and behavioral mapping.
# Identify .NET assembly
file <TARGET>
python3 -c "import pefile; pe=pefile.PE('<TARGET>'); print('CLR:', pe.OPTIONAL_HEADER.DATA_DIRECTORY[14].VirtualAddress)"
# Fast metadata check
monodis --assembly <TARGET>
strings -n 10 <TARGET> | grep -iE 'System\.Reflection|Assembly\.Load|FromBase64|Invoke|WebClient|DownloadString'
# De-obfuscate with de4dot
de4dot <TARGET> -o <TARGET>.cleaned.exe
# Decompile to C# project
ilspycmd -p -o ./decompiled/ <TARGET>
# Extract embedded resources
python3 -c "
import dnfile
dn = dnfile.dnPE('<TARGET>')
for r in dn.net.resources:
print(f'{r.name} offset={r.offset} size={r.size}')
"| Technique | ID | How It Appears |
|---|---|---|
| Software Packing | T1027.002 | ConfuserEx, .NET Reactor, SmartAssembly, Babel packing |
| Deobfuscate/Decode | T1140 | Base64 decode + Assembly.Load, XOR decryption of payloads |
| PowerShell | T1059.001 | Cradle execution: powershell -enc <base64> from managed code |
| Obfuscated Files | T1027 | String encryption, control flow flattening, anti-tamper |
| Reflective Code Loading | T1620 | Assembly.Load(byte[]) for in-memory stage loading |
Confirm the sample is managed code before committing to .NET tooling.
# Check PE headers for CLR data directory (index 14)
python3 << 'EOF'
import pefile, sys
pe = pefile.PE("<TARGET>")
clr_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[14]
if clr_dir.VirtualAddress > 0:
print(f"[+] .NET assembly confirmed — CLR RVA: {hex(clr_dir.VirtualAddress)}")
# Check runtime version
import struct
clr_offset = pe.get_offset_from_rva(clr_dir.VirtualAddress)
pe.__data__.seek(clr_offset + 8)
major, minor = struct.unpack('<HH', pe.__data__.read(4))
print(f" Runtime: v{major}.{minor}")
else:
print("[-] Not a .NET assembly")
sys.exit(1)
EOF
# Alternative quick checks
file <TARGET> # Look for "Mono/.Net assembly"
exiftool <TARGET> | grep -i "assembly\|\.net\|framework"Identify what protection was applied before attempting decompilation.
# de4dot auto-detects most obfuscators
de4dot --detect-only <TARGET>
# Output example: "Detected .NET Reactor 6.x"
# Detect It Easy (DIE) for packer/protector identification
diec <TARGET>
# Manual signature checks
strings <TARGET> | grep -iE 'ConfuserEx|Reactor|SmartAssembly|Babel|Crypto Obfuscator|Dotfuscator|Eazfuscator|AgileDotNet'
# Check for anti-tamper / anti-debug module initializers
monodis --method <TARGET> | grep -i "cctor\|InitializeComponent\|Module"
# Entropy per section — high entropy in .text = packed/encrypted
python3 << 'EOF'
import pefile, math
pe = pefile.PE("<TARGET>")
for s in pe.sections:
data = s.get_data()
if len(data) == 0: continue
counts = [data.count(bytes([b])) for b in range(256)]
total = len(data)
ent = -sum((c/total)*math.log2(c/total) for c in counts if c)
name = s.Name.decode().rstrip('\x00')
print(f" {name:10s} entropy={ent:.2f} {'PACKED' if ent > 7.2 else 'normal'}")
EOFStrip protections layer by layer.
# de4dot — handles ConfuserEx, .NET Reactor, SmartAssembly, Babel, etc.
de4dot <TARGET> -o <TARGET>.cleaned.exe
# If de4dot detects "Unknown obfuscator", try force mode:
de4dot <TARGET> --strtyp delegate --strtok 0x06000042 -o <TARGET>.cleaned.exe
# For ConfuserEx specifically:
# 1. Remove anti-tamper first (patches module cctor)
de4dot <TARGET> --un-name "!^<Module>$&!^<" -o step1.exe
# 2. Then full clean
de4dot step1.exe -o <TARGET>.cleaned.exe
# For .NET Reactor:
# Use .NET Reactor Slayer or NETReactorSlayer
NETReactorSlayer.CLI <TARGET>
# Verify result decompiles cleanly
ilspycmd <TARGET>.cleaned.exe | head -50Full source recovery from cleaned assembly.
# ILSpy command-line — full project decompilation
ilspycmd -p -o ./decompiled/ <TARGET>.cleaned.exe
# Search decompiled source for key behaviors
grep -rn "WebClient\|HttpClient\|WebRequest" ./decompiled/
grep -rn "Registry\|RegistryKey\|SetValue" ./decompiled/
grep -rn "Process\.Start\|ProcessStartInfo" ./decompiled/
grep -rn "Assembly\.Load\|Activator\.CreateInstance" ./decompiled/
grep -rn "FromBase64String\|Convert\.FromBase64" ./decompiled/
grep -rn "RijndaelManaged\|AesManaged\|DES\|TripleDES\|RC4" ./decompiled/
grep -rn "Socket\|TcpClient\|NetworkStream" ./decompiled/
grep -rn "Clipboard\|Screenshot\|Keylog\|GetAsyncKeyState" ./decompiled/
# Extract hardcoded C2 / config
grep -rn "http://\|https://\|ftp://\|\b[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\b" ./decompiled/
# Dump embedded resources (payloads, configs, second stages)
python3 << 'EOF'
import dnfile, os
dn = dnfile.dnPE("<TARGET>.cleaned.exe")
os.makedirs("resources", exist_ok=True)
for res in dn.net.resources:
if hasattr(res, 'data') and res.data:
path = f"resources/{res.name}"
open(path, 'wb').write(res.data)
print(f"[+] Extracted: {path} ({len(res.data)} bytes)")
EOF.NET loaders frequently spawn encoded PowerShell.
# Find encoded PowerShell invocations in decompiled source
grep -rn "\-enc\|\-EncodedCommand\|\-e " ./decompiled/
# Decode base64 PowerShell payloads
python3 << 'EOF'
import base64, sys
encoded = "<BASE64_PAYLOAD>"
decoded = base64.b64decode(encoded).decode('utf-16-le')
print(decoded)
# Write to file for further analysis
with open("decoded_ps.ps1", "w") as f:
f.write(decoded)
EOF
# Look for staged downloads
grep -iE 'DownloadString|DownloadFile|DownloadData|Invoke-Expression|IEX|Net\.WebClient' decoded_ps.ps1# AsyncRAT config extraction
python3 << 'EOF'
import base64, hashlib
from Crypto.Cipher import AES
# AsyncRAT stores config as encrypted strings in Settings class
# Key is derived from a hardcoded passphrase via PBKDF2
passphrase = "<EXTRACTED_KEY>" # From Settings.Key in decompiled source
salt = b"<EXTRACTED_SALT>" # From Settings.aession
key = hashlib.pbkdf2_hmac('sha1', passphrase.encode(), salt, 50000, dklen=32)
# Each config field is Base64(IV + AES-CBC(data))
config_fields = {
"Host": "<ENC_HOST>",
"Port": "<ENC_PORT>",
"Mutex": "<ENC_MUTEX>",
}
for name, enc in config_fields.items():
raw = base64.b64decode(enc)
iv, ct = raw[:16], raw[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = cipher.decrypt(ct)
pt = pt[:-pt[-1]] # PKCS7 unpad
print(f" {name}: {pt.decode()}")
EOF
# QuasarRAT — config in Settings class, AES-encrypted
# njRAT — config in plain text fields, base64-encoded
# AgentTesla — SMTP exfil config in Settings resource# dnSpy interactive workflow (Windows GUI):
# 1. Load cleaned assembly: File → Open → <TARGET>.cleaned.exe
# 2. Set breakpoints on:
# - EntryPoint (Main method)
# - Any method calling WebClient/HttpClient
# - Any method calling Process.Start
# - Any method calling Assembly.Load
# 3. Debug → Start (F5) — dnSpy hosts the CLR
# 4. Step through, watch locals for decrypted strings/URLs
# 5. Memory window: watch for in-memory PE headers (4D 5A)
# Headless: dump method bodies via Mono.Cecil
python3 << 'EOF'
# pip install dnfile
import dnfile
dn = dnfile.dnPE("<TARGET>.cleaned.exe")
for row in dn.net.mdtables.MethodDef:
if row.ImplFlags.miIL:
print(f" Token 0x{row.row_index:08X} {row.Name} RVA={hex(row.RVA)}")
EOF| Tool | Purpose | Install |
|---|---|---|
| dnSpy | .NET debugger + decompiler (GUI) | github.com/dnSpy/dnSpy |
| ILSpy / ilspycmd | .NET decompiler (GUI + CLI) | github.com/icsharpcode/ILSpy |
| de4dot | .NET de-obfuscator | github.com/de4dot/de4dot |
| dotPeek | JetBrains .NET decompiler | jetbrains.com/decompiler |
| dnlib / dnfile | .NET metadata parsing (Python/C#) | pip install dnfile |
| Mono.Cecil | .NET assembly manipulation | nuget Mono.Cecil |
| NETReactorSlayer | .NET Reactor unpacker | github.com/SychicBT/NETReactorSlayer |
| Detect It Easy | Packer/protector identification | github.com/horsicq/Detect-It-Easy |
| AsmResolver | .NET PE/metadata library | github.com/Washi1337/AsmResolver |
| Indicator | Description | Sigma/YARA |
|---|---|---|
Assembly.Load(byte[]) | Reflective loading of in-memory .NET payload | YARA: $s1 = "Assembly" ascii wide + $s2 = "Load" ascii wide |
FromBase64String + Assembly.Load | Base64 decode → reflective load chain | Sigma: process_creation with CommandLine containing -enc |
WebClient.DownloadString | Stage 2 download via .NET HTTP client | Network: HTTP GET to non-standard port |
| ConfuserEx metadata | ConfusedBy attribute in assembly | YARA: $conf = "ConfusedByAttribute" |
| High entropy .text section | Packed/encrypted .NET payload | YARA: math.entropy(0, filesize) > 7.0 |
csc.exe compilation at runtime | Dynamic code compilation via CodeDom | Sysmon: Event ID 1, process csc.exe with parent not devenv/msbuild |
| Issue | Resolution |
|---|---|
| de4dot crashes on sample | Try older de4dot fork, or manually patch anti-tamper (NOP the module cctor) |
| Mixed mode assembly (native + managed) | Use dnSpy for managed parts, IDA/Ghidra for native stubs |
| AOT/NativeAOT compiled | Not a traditional .NET assembly — treat as native PE, use Ghidra |
| .NET 5+ single-file bundle | Extract with dotnet-dump or manually parse bundle header at end of PE |
| Heavily virtualized (KoiVM/EazVM) | Use specialized devirtualizers: OldRod (KoiVM), EazFixer (Eazfuscator) |
| Anti-debug in module initializer | Patch <Module>.cctor or use dnSpy's "Edit IL" to NOP checks |
| Resource encryption | Dump at runtime with dnSpy breakpoints on ResourceManager.GetObject |
IF sample is .NET assembly (CLR header present):
→ Run de4dot to identify + strip obfuscation
→ Decompile with ILSpy/dnSpy
→ Extract configs for known RAT families
→ Map behaviors to ATT&CK techniques
→ Extract IOCs (C2, mutex, registry keys)
ELSE IF .NET single-file or AOT:
→ Extract bundled assemblies first, then proceed above
ELSE:
→ Not .NET — use reverser/malware-triage or reverser/ghidraPurpleAILAB/Decepticon
49888ee
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.