Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills

Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
This commit is contained in:
mukul975
2026-03-10 21:02:12 +01:00
parent c74d52fa30
commit 27c6414ca5
1390 changed files with 106806 additions and 0 deletions
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,76 @@
# API Reference: Memory Dump Credential Extraction Agent
## Dependencies
| Library | Version | Purpose |
|---------|---------|---------|
| volatility3 | >=2.0 | Memory forensics framework (invoked via subprocess) |
| pypykatz | >=0.6 | Python Mimikatz for LSASS credential extraction |
## CLI Usage
```bash
python scripts/agent.py \
--dump /cases/case-001/memory.raw \
--output-dir /cases/case-001/analysis/ \
--output credential_report.json
```
## Functions
### `verify_dump(dump_path) -> dict`
Checks file existence, computes size and SHA-256 of first 1MB for integrity.
### `run_vol3(dump_path, plugin, extra_args) -> str`
Executes a volatility3 plugin via subprocess with 5-minute timeout. Returns stdout.
### `get_os_info(dump_path) -> dict`
Runs `windows.info` to identify OS version and build from the memory image.
### `find_lsass_pid(dump_path) -> int`
Runs `windows.pslist` and locates the LSASS process PID.
### `extract_hashdump(dump_path) -> list`
Runs `windows.hashdump` to extract SAM database NTLM hashes for local accounts.
### `extract_lsadump(dump_path) -> list`
Runs `windows.lsadump` to extract LSA secrets (service account passwords).
### `extract_cachedump(dump_path) -> list`
Runs `windows.cachedump` to extract DCC2 cached domain credential hashes.
### `run_pypykatz(dump_path, output_dir) -> dict`
Invokes pypykatz in JSON mode against LSASS minidump or full memory image.
### `parse_pypykatz_creds(pypykatz_data) -> list`
Parses pypykatz JSON output into structured credential list with NTLM, Kerberos, WDigest, DPAPI.
### `search_cloud_keys(dump_path) -> list`
Uses `windows.strings` to find AWS keys, JWT tokens, and auth strings in memory.
### `generate_report(dump_path, output_dir) -> dict`
Orchestrates all extraction steps and compiles the final report with summary and actions.
## Volatility3 Plugins Used
| Plugin | Purpose |
|--------|---------|
| `windows.info` | OS identification |
| `windows.pslist` | Process listing (find LSASS PID) |
| `windows.hashdump` | SAM hash extraction |
| `windows.lsadump` | LSA secret extraction |
| `windows.cachedump` | Cached domain credential extraction |
| `windows.strings` | String search for cloud keys and tokens |
## Output Schema
```json
{
"source": "/cases/memory.raw",
"sam_hashes": [{"user": "Administrator", "rid": 500, "ntlm_hash": "fc52..."}],
"lsass_creds": [{"user": "CORP\\admin", "cred_types": [{"type": "NTLM", "hash": "..."}]}],
"cloud_keys": [{"type": "AWS Access Key", "value": "AKIA..."}],
"summary": {"sam_hashes": 4, "lsass_creds": 3, "cloud_keys": 1},
"actions": ["Reset passwords for all local accounts..."]
}
```
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""Memory dump credential extraction agent using volatility3 subprocess and pypykatz."""
import argparse
import hashlib
import json
import logging
import os
import re
import subprocess
import sys
from datetime import datetime
from typing import Dict, List, Optional
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
CLOUD_KEY_PATTERNS = [
(r"AKIA[A-Z0-9]{16}", "AWS Access Key"),
(r"ASIA[A-Z0-9]{16}", "AWS Temp Key"),
(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+", "JWT/Azure Token"),
]
AUTH_STRING_PATTERNS = [
r"(?i)bearer\s+[A-Za-z0-9_\-\.]+",
r"(?i)authorization:\s*\S+",
r"(?i)api[_-]key[=:]\s*\S+",
r"(?i)password[=:]\s*\S+",
]
def verify_dump(dump_path: str) -> dict:
"""Verify memory dump exists and compute hash."""
if not os.path.isfile(dump_path):
logger.error("Memory dump not found: %s", dump_path)
return {"valid": False}
size = os.path.getsize(dump_path)
with open(dump_path, "rb") as f:
sha256 = hashlib.sha256(f.read(1024 * 1024)).hexdigest()
return {"valid": True, "size_bytes": size, "sha256_1mb": sha256}
def run_vol3(dump_path: str, plugin: str, extra_args: Optional[List[str]] = None) -> str:
"""Run a volatility3 plugin and return stdout."""
cmd = ["vol", "-f", dump_path, plugin]
if extra_args:
cmd.extend(extra_args)
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode != 0 and result.stderr:
logger.warning("vol3 %s stderr: %s", plugin, result.stderr[:200])
return result.stdout
except FileNotFoundError:
logger.error("volatility3 (vol) not found in PATH")
return ""
except subprocess.TimeoutExpired:
logger.error("vol3 %s timed out", plugin)
return ""
def get_os_info(dump_path: str) -> dict:
"""Identify OS version from memory dump."""
output = run_vol3(dump_path, "windows.info")
info = {}
for line in output.splitlines():
if "\t" in line:
parts = line.split("\t", 1)
if len(parts) == 2:
info[parts[0].strip()] = parts[1].strip()
return info
def find_lsass_pid(dump_path: str) -> Optional[int]:
"""Find LSASS process PID from process list."""
output = run_vol3(dump_path, "windows.pslist")
for line in output.splitlines():
if "lsass.exe" in line.lower():
parts = line.split()
for p in parts:
if p.isdigit():
return int(p)
return None
def extract_hashdump(dump_path: str) -> List[dict]:
"""Extract SAM hashes using windows.hashdump."""
output = run_vol3(dump_path, "windows.hashdump")
results = []
for line in output.splitlines():
parts = line.split()
if len(parts) >= 4 and parts[1].isdigit():
results.append({
"user": parts[0], "rid": int(parts[1]),
"lm_hash": parts[2], "ntlm_hash": parts[3],
})
logger.info("Extracted %d SAM hashes", len(results))
return results
def extract_lsadump(dump_path: str) -> List[dict]:
"""Extract LSA secrets using windows.lsadump."""
output = run_vol3(dump_path, "windows.lsadump")
results = []
for line in output.splitlines():
line = line.strip()
if line and not line.startswith("Offset") and not line.startswith("-"):
results.append({"raw": line})
logger.info("Extracted %d LSA secret entries", len(results))
return results
def extract_cachedump(dump_path: str) -> List[dict]:
"""Extract cached domain credentials using windows.cachedump."""
output = run_vol3(dump_path, "windows.cachedump")
results = []
for line in output.splitlines():
parts = line.split()
if len(parts) >= 3 and parts[0] not in ("User", "---"):
results.append({"user": parts[0], "domain": parts[1], "dcc2_hash": parts[2] if len(parts) > 2 else ""})
logger.info("Extracted %d cached domain credentials", len(results))
return results
def run_pypykatz(dump_path: str, output_dir: str) -> dict:
"""Run pypykatz against LSASS minidump or full memory for credential extraction."""
lsass_dmp = os.path.join(output_dir, "lsass.dmp")
target = lsass_dmp if os.path.isfile(lsass_dmp) else dump_path
mode = "minidump" if target == lsass_dmp else "rekall"
cmd = ["pypykatz", "lsa", mode, target, "--json"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if result.stdout:
return json.loads(result.stdout)
except FileNotFoundError:
logger.warning("pypykatz not found; skipping LSASS credential extraction")
except (json.JSONDecodeError, subprocess.TimeoutExpired) as exc:
logger.warning("pypykatz error: %s", exc)
return {}
def parse_pypykatz_creds(pypykatz_data: dict) -> List[dict]:
"""Parse pypykatz JSON output into structured credential list."""
creds = []
for session_key, session in pypykatz_data.get("logon_sessions", {}).items():
username = session.get("username", "")
domain = session.get("domainname", "")
if not username or username == "(null)":
continue
entry = {"user": f"{domain}\\{username}", "sid": session.get("sid", ""),
"logon_server": session.get("logon_server", ""),
"logon_time": session.get("logon_time", ""), "cred_types": []}
for msv in session.get("msv_creds", []):
if msv.get("NThash"):
entry["cred_types"].append({"type": "NTLM", "hash": msv["NThash"]})
for kerb in session.get("kerberos_creds", []):
if kerb.get("password"):
entry["cred_types"].append({"type": "Kerberos_password", "value": kerb["password"]})
for ticket in kerb.get("tickets", []):
entry["cred_types"].append({"type": "Kerberos_ticket",
"server": ticket.get("server", ""), "enc_type": ticket.get("enc_type", "")})
for wd in session.get("wdigest_creds", []):
if wd.get("password"):
entry["cred_types"].append({"type": "WDigest", "value": wd["password"]})
for dpapi in session.get("dpapi_creds", []):
if dpapi.get("masterkey"):
entry["cred_types"].append({"type": "DPAPI_masterkey", "key": dpapi["masterkey"][:40]})
if entry["cred_types"]:
creds.append(entry)
return creds
def search_cloud_keys(dump_path: str) -> List[dict]:
"""Search memory strings for cloud credentials and auth tokens."""
output = run_vol3(dump_path, "windows.strings", ["--pid", "0"])
findings = []
for pattern, label in CLOUD_KEY_PATTERNS:
for match in re.findall(pattern, output):
findings.append({"type": label, "value": match[:30] + "..."})
for pattern in AUTH_STRING_PATTERNS:
for match in re.findall(pattern, output):
findings.append({"type": "auth_string", "value": match[:60]})
logger.info("Found %d cloud/auth credential fragments", len(findings))
return findings[:50]
def generate_report(dump_path: str, output_dir: str) -> dict:
"""Generate full credential extraction report."""
os.makedirs(output_dir, exist_ok=True)
report = {"analysis_date": datetime.utcnow().isoformat(), "source": dump_path}
report["dump_info"] = verify_dump(dump_path)
if not report["dump_info"].get("valid"):
return report
report["os_info"] = get_os_info(dump_path)
report["lsass_pid"] = find_lsass_pid(dump_path)
report["sam_hashes"] = extract_hashdump(dump_path)
report["lsa_secrets"] = extract_lsadump(dump_path)
report["cached_creds"] = extract_cachedump(dump_path)
pypykatz_data = run_pypykatz(dump_path, output_dir)
report["lsass_creds"] = parse_pypykatz_creds(pypykatz_data)
report["cloud_keys"] = search_cloud_keys(dump_path)
summary = {
"sam_hashes": len(report["sam_hashes"]),
"lsa_secrets": len(report["lsa_secrets"]),
"cached_creds": len(report["cached_creds"]),
"lsass_creds": len(report["lsass_creds"]),
"cloud_keys": len(report["cloud_keys"]),
}
report["summary"] = summary
report["actions"] = []
if summary["sam_hashes"] > 0:
report["actions"].append("Reset passwords for all local accounts with extracted NTLM hashes")
if summary["lsass_creds"] > 0:
report["actions"].append("Reset domain account passwords and perform double krbtgt rotation")
if summary["cloud_keys"] > 0:
report["actions"].append("Rotate all discovered cloud access keys and revoke active sessions")
logger.info("Report complete: %s", json.dumps(summary))
return report
def main():
parser = argparse.ArgumentParser(description="Memory Dump Credential Extraction Agent")
parser.add_argument("--dump", required=True, help="Path to memory dump file")
parser.add_argument("--output-dir", default=".", help="Output directory")
parser.add_argument("--output", default="credential_report.json")
args = parser.parse_args()
report = generate_report(args.dump, args.output_dir)
out_path = os.path.join(args.output_dir, args.output)
with open(out_path, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Report saved to %s", out_path)
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()