CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

reverser-dotnet-malware

.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.

Quality

80%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

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.md
SKILL.md
Quality
Evals
Security

.NET Malware Analysis

Analyze .NET malware from initial triage through full decompilation, de-obfuscation, config extraction, and behavioral mapping.

Quick Reference

# 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}')
"

MITRE ATT&CK Mapping

TechniqueIDHow It Appears
Software PackingT1027.002ConfuserEx, .NET Reactor, SmartAssembly, Babel packing
Deobfuscate/DecodeT1140Base64 decode + Assembly.Load, XOR decryption of payloads
PowerShellT1059.001Cradle execution: powershell -enc <base64> from managed code
Obfuscated FilesT1027String encryption, control flow flattening, anti-tamper
Reflective Code LoadingT1620Assembly.Load(byte[]) for in-memory stage loading

1. Identify .NET Assembly

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"

2. Detect Obfuscator

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'}")
EOF

3. De-obfuscate

Strip 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 -50

4. Decompile and Analyze

Full 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

5. PowerShell Cradle Extraction

.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

6. Config Extraction for Known Families

# 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

7. Behavioral Analysis with dnSpy

# 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

Tools & Resources

ToolPurposeInstall
dnSpy.NET debugger + decompiler (GUI)github.com/dnSpy/dnSpy
ILSpy / ilspycmd.NET decompiler (GUI + CLI)github.com/icsharpcode/ILSpy
de4dot.NET de-obfuscatorgithub.com/de4dot/de4dot
dotPeekJetBrains .NET decompilerjetbrains.com/decompiler
dnlib / dnfile.NET metadata parsing (Python/C#)pip install dnfile
Mono.Cecil.NET assembly manipulationnuget Mono.Cecil
NETReactorSlayer.NET Reactor unpackergithub.com/SychicBT/NETReactorSlayer
Detect It EasyPacker/protector identificationgithub.com/horsicq/Detect-It-Easy
AsmResolver.NET PE/metadata librarygithub.com/Washi1337/AsmResolver

Detection Signatures

IndicatorDescriptionSigma/YARA
Assembly.Load(byte[])Reflective loading of in-memory .NET payloadYARA: $s1 = "Assembly" ascii wide + $s2 = "Load" ascii wide
FromBase64String + Assembly.LoadBase64 decode → reflective load chainSigma: process_creation with CommandLine containing -enc
WebClient.DownloadStringStage 2 download via .NET HTTP clientNetwork: HTTP GET to non-standard port
ConfuserEx metadataConfusedBy attribute in assemblyYARA: $conf = "ConfusedByAttribute"
High entropy .text sectionPacked/encrypted .NET payloadYARA: math.entropy(0, filesize) > 7.0
csc.exe compilation at runtimeDynamic code compilation via CodeDomSysmon: Event ID 1, process csc.exe with parent not devenv/msbuild

Error Handling & Edge Cases

IssueResolution
de4dot crashes on sampleTry 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 compiledNot a traditional .NET assembly — treat as native PE, use Ghidra
.NET 5+ single-file bundleExtract 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 initializerPatch <Module>.cctor or use dnSpy's "Edit IL" to NOP checks
Resource encryptionDump at runtime with dnSpy breakpoints on ResourceManager.GetObject

Decision Gate

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/ghidra
Repository

PurpleAILAB/Decepticon

Path

packages/decepticon/decepticon/skills/standard/reverser/dotnet-malware/SKILL.md

Last updated
First committed

Is this your skill?

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.