mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-24 13:40:57 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,68 @@
|
||||
# API Reference: .NET Malware Reverse Engineering with dnSpy Agent
|
||||
|
||||
## Overview
|
||||
|
||||
Analyzes .NET malware: validates CLR headers, detects obfuscators (ConfuserEx, SmartAssembly), deobfuscates with de4dot, extracts strings/IOCs, and parses .NET metadata via monodis.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| hashlib | stdlib | Sample hash computation |
|
||||
| struct | stdlib | PE/CLR header parsing |
|
||||
| re | stdlib | String pattern extraction |
|
||||
|
||||
## External Tools (Optional)
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| diec (Detect It Easy) | Obfuscator identification |
|
||||
| de4dot | Automated .NET deobfuscation |
|
||||
| monodis | .NET assembly metadata extraction |
|
||||
|
||||
## Core Functions
|
||||
|
||||
### `detect_dotnet_assembly(filepath)`
|
||||
Validates PE file has CLR header (COM descriptor directory entry).
|
||||
- **Checks**: MZ signature, PE signature, optional header magic, CLR RVA
|
||||
- **Returns**: `dict` with `is_dotnet`, `clr_header_rva`
|
||||
|
||||
### `detect_obfuscator(filepath)`
|
||||
Runs Detect It Easy to identify ConfuserEx, SmartAssembly, .NET Reactor, Dotfuscator, Babel, Eazfuscator, Crypto Obfuscator.
|
||||
- **Returns**: `dict` with `detected` list
|
||||
|
||||
### `deobfuscate_with_de4dot(filepath, output_path)`
|
||||
Runs de4dot to remove obfuscation, producing a cleaner assembly.
|
||||
- **Timeout**: 120 seconds
|
||||
- **Returns**: `dict` with `success`, `output_path`
|
||||
|
||||
### `extract_strings(filepath, min_length)`
|
||||
Extracts ASCII and Unicode strings, classifies into URLs, IPs, emails, registry keys, base64, and suspicious keywords (keylog, stealer, webhook, etc.).
|
||||
- **Returns**: `dict[str, list[str]]` - categorized indicator lists
|
||||
|
||||
### `analyze_dotnet_metadata(filepath)`
|
||||
Uses monodis to extract assembly info, type definitions, and method counts.
|
||||
- **Returns**: `dict` with `type_count`, `method_count`, `types`
|
||||
|
||||
### `analyze_dotnet_malware(filepath, output_dir)`
|
||||
Full pipeline: hashes -> .NET check -> obfuscator detection -> deobfuscation -> strings -> metadata.
|
||||
|
||||
## Obfuscators Detected
|
||||
|
||||
| Obfuscator | Indicator |
|
||||
|------------|-----------|
|
||||
| ConfuserEx | Most common open-source .NET obfuscator |
|
||||
| SmartAssembly | Commercial obfuscator by Redgate |
|
||||
| .NET Reactor | Code protection with native stub |
|
||||
| Dotfuscator | Microsoft-provided obfuscator |
|
||||
| Eazfuscator | Commercial string/flow obfuscation |
|
||||
|
||||
## Suspicious String Keywords
|
||||
|
||||
`keylog`, `screenshot`, `clipboard`, `password`, `credential`, `smtp`, `telegram`, `discord`, `webhook`, `stealer`, `inject`, `hook`, `persist`, `startup`
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python agent.py suspect.exe
|
||||
```
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/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("<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)
|
||||
Reference in New Issue
Block a user