| name | reverser-dotnet-malware |
| description | .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. |
| allowed-tools | Bash Read Write |
| metadata | {"when_to_use":"dotnet .net malware dnspy ilspy de4dot deobfuscate confuserex reactor obfuscation managed assembly csharp vbnet msil cil rat stealer loader","subdomain":"reverser","tags":".NET, malware, deobfuscation, dnSpy, ILSpy, de4dot","mitre_attack":"T1027.002, T1140, T1059.001"} |
.NET Malware Analysis
Analyze .NET malware from initial triage through full decompilation, de-obfuscation, config extraction, and behavioral mapping.
Quick Reference
file <TARGET>
python3 -c "import pefile; pe=pefile.PE('<TARGET>'); print('CLR:', pe.OPTIONAL_HEADER.DATA_DIRECTORY[14].VirtualAddress)"
monodis --assembly <TARGET>
strings -n 10 <TARGET> | grep -iE 'System\.Reflection|Assembly\.Load|FromBase64|Invoke|WebClient|DownloadString'
de4dot <TARGET> -o <TARGET>.cleaned.exe
ilspycmd -p -o ./decompiled/ <TARGET>
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
| 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 |
1. Identify .NET Assembly
Confirm the sample is managed code before committing to .NET tooling.
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)}")
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
file <TARGET>
exiftool <TARGET> | grep -i "assembly\|\.net\|framework"
2. Detect Obfuscator
Identify what protection was applied before attempting decompilation.
de4dot --detect-only <TARGET>
diec <TARGET>
strings <TARGET> | grep -iE 'ConfuserEx|Reactor|SmartAssembly|Babel|Crypto Obfuscator|Dotfuscator|Eazfuscator|AgileDotNet'
monodis --method <TARGET> | grep -i "cctor\|InitializeComponent\|Module"
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 <TARGET> -o <TARGET>.cleaned.exe
de4dot <TARGET> --strtyp delegate --strtok 0x06000042 -o <TARGET>.cleaned.exe
de4dot <TARGET> --un-name "!^<Module>$&!^<" -o step1.exe
de4dot step1.exe -o <TARGET>.cleaned.exe
NETReactorSlayer.CLI <TARGET>
ilspycmd <TARGET>.cleaned.exe | head -50
4. Decompile and Analyze
Full source recovery from cleaned assembly.
ilspycmd -p -o ./decompiled/ <TARGET>.cleaned.exe
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/
grep -rn "http://\|https://\|ftp://\|\b[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\b" ./decompiled/
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.
grep -rn "\-enc\|\-EncodedCommand\|\-e " ./decompiled/
python3 << 'EOF'
import base64, sys
encoded = "<BASE64_PAYLOAD>"
decoded = base64.b64decode(encoded).decode('utf-16-le')
print(decoded)
with open("decoded_ps.ps1", "w") as f:
f.write(decoded)
EOF
grep -iE 'DownloadString|DownloadFile|DownloadData|Invoke-Expression|IEX|Net\.WebClient' decoded_ps.ps1
6. Config Extraction for Known Families
python3 << 'EOF'
import base64, hashlib
from Crypto.Cipher import AES
passphrase = "<EXTRACTED_KEY>"
salt = b"<EXTRACTED_SALT>"
key = hashlib.pbkdf2_hmac('sha1', passphrase.encode(), salt, 50000, dklen=32)
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]]
print(f" {name}: {pt.decode()}")
EOF
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
| 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 |
Detection Signatures
| 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 |
Error Handling & Edge Cases
| 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 |
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