#!/usr/bin/env python3 """.NET malware reverse engineering agent using subprocess wrappers for dnSpy/de4dot.""" import subprocess import os import sys import re import json 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(" 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(" 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 ") sys.exit(1) result = analyze_dotnet_malware(sys.argv[1]) print_report(result)