mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
219 lines
8.1 KiB
Python
219 lines
8.1 KiB
Python
#!/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)
|